diff --git a/.github/workflows/brakeman-scan-core.yml b/.github/workflows/brakeman-scan-core.yml index 998d0bedbdf..8ee3db571bd 100644 --- a/.github/workflows/brakeman-scan-core.yml +++ b/.github/workflows/brakeman-scan-core.yml @@ -25,10 +25,12 @@ jobs: RUBY_GC_HEAP_INIT_SLOTS: 100000 steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Setup Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1 - name: Setup Brakeman run: | @@ -44,6 +46,6 @@ jobs: --output output.sarif.json - name: Upload SARIF - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4 with: sarif_file: output.sarif.json diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 14dd8d62884..8889a071a85 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -19,7 +19,7 @@ jobs: steps: - name: "CLA Assistant" if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' - uses: contributor-assistant/github-action@v2.6.1 + uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1 env: # https://github.com/contributor-assistant/github-action?tab=readme-ov-file#environmental-variables # Built-in GitHub token to make the API calls for interacting with GitHub. Does not need to be specified the secrets store. diff --git a/.github/workflows/codeql-scan-core.yml b/.github/workflows/codeql-scan-core.yml index 69b60912baa..5a3b4699184 100644 --- a/.github/workflows/codeql-scan-core.yml +++ b/.github/workflows/codeql-scan-core.yml @@ -31,10 +31,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4 with: config-file: ./.github/codeql/config.yml languages: ${{ matrix.language }} @@ -42,6 +44,6 @@ jobs: queries: security-extended,security-and-quality - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/create-merge-from-previous-release-branch-pr.yml b/.github/workflows/create-merge-from-previous-release-branch-pr.yml index 688ce09e215..0862bea6c7f 100644 --- a/.github/workflows/create-merge-from-previous-release-branch-pr.yml +++ b/.github/workflows/create-merge-from-previous-release-branch-pr.yml @@ -59,7 +59,7 @@ jobs: timeout-minutes: 5 steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ env.RELEASE_BRANCH }} diff --git a/.github/workflows/create-merge-release-into-dev-pr.yml b/.github/workflows/create-merge-release-into-dev-pr.yml index 853e71493be..57e27fb2ac1 100644 --- a/.github/workflows/create-merge-release-into-dev-pr.yml +++ b/.github/workflows/create-merge-release-into-dev-pr.yml @@ -44,7 +44,7 @@ jobs: timeout-minutes: 5 steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ env.BASE_BRANCH }} diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml index 03f3c5ae822..165df9c1f9d 100644 --- a/.github/workflows/crowdin.yml +++ b/.github/workflows/crowdin.yml @@ -42,11 +42,11 @@ jobs: - dev - "${{ needs.setup.outputs.latest_release_branch }}" steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ matrix.branch }} fetch-depth: 1 - - uses: ruby/setup-ruby@v1 + - uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1 with: bundler-cache: true - name: "Set crowdin branch name" @@ -66,7 +66,7 @@ jobs: bundle exec script/i18n/generate_seeders_i18n_source_file fi - name: "Crowdin: upload sources and download translations" - uses: crowdin/github-action@v2 + uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2 with: # Upload current source files upload_sources: true diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 0c52141f129..1fe39a33836 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -14,11 +14,15 @@ jobs: if: github.repository_owner == 'opf' runs-on: [ubuntu-latest] timeout-minutes: 10 + permissions: + contents: read + pull-requests: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 - - uses: ruby/setup-ruby@v1 + persist-credentials: false + - uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1 with: ruby-version: .ruby-version bundler-cache: true diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index e0eddeffe3e..0d3ed9ba3a2 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -34,9 +34,11 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout repository' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: 'Dependency Review' - uses: actions/dependency-review-action@v4.9.0 + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5 # Commonly enabled options, see https://github.com/actions/dependency-review-action#configuration-options for all available options. with: comment-summary-in-pr: on-failure diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index dc7c7bf8eef..8ff82e2ac7f 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -10,18 +10,23 @@ on: description: "The tag to release. Note that this happens by default on the tag push. Only run this action when something went wrong!" required: false +permissions: {} + jobs: compute-inputs: if: github.repository == 'opf/openproject' runs-on: ubuntu-latest + permissions: + contents: read outputs: tag: ${{ steps.compute.outputs.tag }} branch: ${{ steps.compute.outputs.branch }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.ref }} + persist-credentials: false - name: Compute tags and branch id: compute env: @@ -47,6 +52,8 @@ jobs: build: needs: [compute-inputs] + permissions: + contents: read uses: ./.github/workflows/docker.yml with: branch: ${{ needs.compute-inputs.outputs.branch }} diff --git a/.github/workflows/docker-scheduled.yml b/.github/workflows/docker-scheduled.yml index ef7d7ac25d8..fa87a58375b 100644 --- a/.github/workflows/docker-scheduled.yml +++ b/.github/workflows/docker-scheduled.yml @@ -6,9 +6,13 @@ on: - cron: "20 2 * * *" # Daily at 02:20 workflow_dispatch: # Allow manual trigger +permissions: {} + jobs: build-dev: if: github.repository == 'opf/openproject' + permissions: + contents: read uses: ./.github/workflows/docker.yml with: branch: dev @@ -20,9 +24,12 @@ jobs: OPS_MAIL_SMTP_TOKEN: ${{ secrets.OPS_MAIL_SMTP_TOKEN }} build-release-candidate: if: github.repository == 'opf/openproject' + permissions: + contents: read # References to release/X.Y and X.Y-rc are being # updated from the devkit (UpdateWorkflows step) whenever a new release branch is created - uses: opf/openproject/.github/workflows/docker.yml@release/17.4 + # Ignore unpinned uses as we reference the latest release branch and change it + uses: opf/openproject/.github/workflows/docker.yml@release/17.4 # zizmor: ignore[unpinned-uses] with: branch: release/17.4 tag: 17.4-rc diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 899cc9f90e0..af04f3407ff 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -62,9 +62,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ inputs.branch }} + persist-credentials: false - name: Set version outputs and convert tags id: extract_version env: @@ -85,23 +86,27 @@ jobs: fi - name: Verify outputs run: | - if [ -z "${{ steps.extract_version.outputs.version }}" ]; then + if [ -z "${STEPS_EXTRACT_VERSION_OUTPUTS_VERSION}" ]; then echo "Error: version output is empty" exit 1 fi - if [ -z "${{ steps.extract_version.outputs.docker_tags }}" ]; then + if [ -z "${STEPS_EXTRACT_VERSION_OUTPUTS_DOCKER_TAGS}" ]; then echo "Error: docker_tags output is empty" exit 1 fi - if [ -z "${{ steps.extract_version.outputs.registry_image }}" ]; then + if [ -z "${STEPS_EXTRACT_VERSION_OUTPUTS_REGISTRY_IMAGE}" ]; then echo "Error: registry_image output is empty" exit 1 fi - echo "✓ version: ${{ steps.extract_version.outputs.version }}" - echo "✓ docker_tags: ${{ steps.extract_version.outputs.docker_tags }}" - echo "✓ registry_image: ${{ steps.extract_version.outputs.registry_image }}" + echo "✓ version: ${STEPS_EXTRACT_VERSION_OUTPUTS_VERSION}" + echo "✓ docker_tags: ${STEPS_EXTRACT_VERSION_OUTPUTS_DOCKER_TAGS}" + echo "✓ registry_image: ${STEPS_EXTRACT_VERSION_OUTPUTS_REGISTRY_IMAGE}" + env: + STEPS_EXTRACT_VERSION_OUTPUTS_VERSION: ${{ steps.extract_version.outputs.version }} + STEPS_EXTRACT_VERSION_OUTPUTS_DOCKER_TAGS: ${{ steps.extract_version.outputs.docker_tags }} + STEPS_EXTRACT_VERSION_OUTPUTS_REGISTRY_IMAGE: ${{ steps.extract_version.outputs.registry_image }} - name: Cache NPM - uses: runs-on/cache@v5 + uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5 with: path: | frontend/node_modules @@ -109,17 +114,18 @@ jobs: key: nodejs-x64-${{ hashFiles('**/package-lock.json') }} restore-keys: nodejs-x64- - name: Cache angular - uses: runs-on/cache@v5 + uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5 with: path: frontend/.angular key: angular-${{ github.ref }} restore-keys: angular- - - uses: ruby/setup-ruby@v1 + - uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1 with: bundler-cache: true - - uses: actions/setup-node@v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 22.15 + package-manager-cache: false cache: npm cache-dependency-path: | package-lock.json @@ -129,7 +135,7 @@ jobs: ./docker/prod/setup/precompile-assets.sh # public/assets will be saved as artifact, so temporarily copying config file there as well cp config/frontend_assets.manifest.json public/assets/frontend_assets.manifest.json - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: public/ name: public-assets-${{ inputs.tag }}-${{ github.sha }} @@ -146,7 +152,7 @@ jobs: needs: - setup runs-on: - labels: "runs-on=${{ github.run_id }}/ssh=false/${{ matrix.runner }}/volume=100g" + labels: "runs-on=${{ github.run_id }}/ssh=false/${{ matrix.runner }}/volume=200g" strategy: matrix: include: @@ -177,14 +183,15 @@ jobs: runner: runner=4cpu-linux-arm64 steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ inputs.branch }} + persist-credentials: false - name: Prepare docker files run: | cp ./docker/prod/Dockerfile ./Dockerfile - name: Download precompiled public assets - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: public-assets-${{ inputs.tag }}-${{ github.sha }} path: public/ @@ -198,23 +205,25 @@ jobs: echo '!/public/assets' >> .dockerignore echo '!/config/frontend_assets.manifest.json' >> .dockerignore - name: Add build information + env: + INPUTS_BRANCH: ${{ inputs.branch }} run: | - echo "${{ inputs.branch }}" > PRODUCT_VERSION - echo "https://github.com/opf/openproject/commits/${{ inputs.branch }}" > PRODUCT_URL + echo "$INPUTS_BRANCH" > PRODUCT_VERSION + echo "https://github.com/opf/openproject/commits/$INPUTS_BRANCH" > PRODUCT_URL date -u +"%Y-%m-%dT%H:%M:%SZ" > RELEASE_DATE - name: Set up QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 - name: Login to Docker Hub - uses: docker/login-action@v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Docker meta id: meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6 with: context: git labels: | @@ -229,7 +238,7 @@ jobs: ${{ needs.setup.outputs.registry_image }} - name: Restore vendor/bundle id: restore-vendor-bundle - uses: actions/cache/restore@v5 + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: | vendor/bundle @@ -239,7 +248,7 @@ jobs: sed -i 's/vendor\/bundle//g' .dockerignore - name: Build image id: build - uses: docker/build-push-action@v7 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 with: context: . platforms: ${{ matrix.platform }} @@ -254,8 +263,10 @@ jobs: cache-from: type=s3,blobs_prefix=cache/${{ github.repository }}/trixie,manifests_prefix=cache/${{ github.repository }}/trixie,region=${{ env.RUNS_ON_AWS_REGION }},bucket=${{ env.RUNS_ON_S3_BUCKET_CACHE }} cache-to: type=s3,blobs_prefix=cache/${{ github.repository }}/trixie,manifests_prefix=cache/${{ github.repository }}/trixie,region=${{ env.RUNS_ON_AWS_REGION }},bucket=${{ env.RUNS_ON_S3_BUCKET_CACHE }},mode=max - name: Extract vendor/bundle from container + env: + STEPS_BUILD_OUTPUTS_IMAGEID: ${{ steps.build.outputs.imageid }} run: | - docker create --name bundle ${{ steps.build.outputs.imageid }} + docker create --name bundle $STEPS_BUILD_OUTPUTS_IMAGEID if [ -d vendor/bundle ]; then mv vendor/bundle vendor/bundle.bak fi @@ -263,7 +274,7 @@ jobs: docker rm bundle - name: Save vendor/bundle id: save-vendor-bundle - uses: actions/cache/save@v5 + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: | vendor/bundle @@ -276,14 +287,16 @@ jobs: mv vendor/bundle.bak vendor/bundle fi - name: Validate image + env: + STEPS_BUILD_OUTPUTS_IMAGEID: ${{ steps.build.outputs.imageid }} run: | ./script/ci/docker_validate_image.sh \ - --image "${{ steps.build.outputs.imageid }}" \ + --image "$STEPS_BUILD_OUTPUTS_IMAGEID" \ --target "${{ matrix.target }}" \ --platform "${{ matrix.platform }}" - name: Push image id: push - uses: docker/build-push-action@v7 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 with: context: . platforms: ${{ matrix.platform }} @@ -295,12 +308,14 @@ jobs: cache-from: type=s3,blobs_prefix=cache/${{ github.repository }}/trixie,manifests_prefix=cache/${{ github.repository }}/trixie,region=${{ env.RUNS_ON_AWS_REGION }},bucket=${{ env.RUNS_ON_S3_BUCKET_CACHE }} cache-to: type=s3,blobs_prefix=cache/${{ github.repository }}/trixie,manifests_prefix=cache/${{ github.repository }}/trixie,region=${{ env.RUNS_ON_AWS_REGION }},bucket=${{ env.RUNS_ON_S3_BUCKET_CACHE }},mode=max - name: Export digest + env: + STEPS_PUSH_OUTPUTS_DIGEST: ${{ steps.push.outputs.digest }} run: | mkdir -p /tmp/digests - digest="${{ steps.push.outputs.digest }}" + digest="$STEPS_PUSH_OUTPUTS_DIGEST" touch "/tmp/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: digests-${{ inputs.tag }}-${{ matrix.target }}--${{ matrix.digest }} path: /tmp/digests/* @@ -316,13 +331,13 @@ jobs: - build steps: - name: Merge digests - uses: actions/upload-artifact/merge@v7 + uses: actions/upload-artifact/merge@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: pattern: "digests-${{ inputs.tag }}-${{ matrix.target }}--*" overwrite: true name: "merged-digests-${{ inputs.tag }}-${{ matrix.target }}-${{ github.run_number }}-${{ github.run_attempt }}" - name: Download digests - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: "merged-digests-${{ inputs.tag }}-${{ matrix.target }}-${{ github.run_number }}-${{ github.run_attempt }}" path: /tmp/digests @@ -333,10 +348,10 @@ jobs: if [ "$suffix" = "-all-in-one" ]; then suffix="" ; fi echo "suffix=$suffix" >> "$GITHUB_OUTPUT" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 - name: Docker meta id: meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6 with: images: ${{ needs.setup.outputs.registry_image }} labels: | @@ -351,7 +366,7 @@ jobs: tags: | ${{ needs.setup.outputs.docker_tags }} - name: Login to Docker Hub - uses: docker/login-action@v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -359,10 +374,15 @@ jobs: working-directory: /tmp/digests run: | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ needs.setup.outputs.registry_image }}@sha256:%s ' *) + $(printf '${NEEDS_SETUP_OUTPUTS_REGISTRY_IMAGE}@sha256:%s ' *) + env: + NEEDS_SETUP_OUTPUTS_REGISTRY_IMAGE: ${{ needs.setup.outputs.registry_image }} - name: Inspect image run: | - docker buildx imagetools inspect ${{ needs.setup.outputs.registry_image }}:${{ steps.meta.outputs.version }} + docker buildx imagetools inspect ${NEEDS_SETUP_OUTPUTS_REGISTRY_IMAGE}:${STEPS_META_OUTPUTS_VERSION} + env: + NEEDS_SETUP_OUTPUTS_REGISTRY_IMAGE: ${{ needs.setup.outputs.registry_image }} + STEPS_META_OUTPUTS_VERSION: ${{ steps.meta.outputs.version }} notify-failure: needs: [setup, build, merge] if: ${{ always() && contains(needs.*.result, 'failure') }} diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 3de0e880f00..b9545e43ed1 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -17,8 +17,10 @@ jobs: name: Check internal links in documentation runs-on: [ubuntu-latest] steps: - - uses: actions/checkout@v6 - - uses: ruby/setup-ruby@v1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + - uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1 with: bundler-cache: true - run: bundle exec ./script/docs/check_links @@ -26,14 +28,18 @@ jobs: name: Check README.md case in documentation runs-on: [ubuntu-latest] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - run: script/docs/check_readme_case docs-readme-yaml-header-syntax-check: name: Check README.md YAML header syntax in documentation runs-on: [ubuntu-latest] steps: - - uses: actions/checkout@v6 - - uses: ruby/setup-ruby@v1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + - uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1 with: bundler-cache: true - run: script/docs/check_readme_yaml_header_syntax diff --git a/.github/workflows/email-notification.yml b/.github/workflows/email-notification.yml index 99326b91b4e..cf5dba17ae0 100644 --- a/.github/workflows/email-notification.yml +++ b/.github/workflows/email-notification.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Send mail - uses: dawidd6/action-send-mail@v16 + uses: dawidd6/action-send-mail@d38f3f7cd391cdebfe0d38efc3998b935e951c4f # v16 with: subject: ${{ inputs.subject }} body: ${{ inputs.body }} diff --git a/.github/workflows/eslint-core.yml b/.github/workflows/eslint-core.yml index 06372ca61b9..19e26516290 100644 --- a/.github/workflows/eslint-core.yml +++ b/.github/workflows/eslint-core.yml @@ -13,16 +13,21 @@ jobs: eslint: name: eslint runs-on: ubuntu-latest + permissions: + contents: read + checks: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 - - uses: actions/setup-node@v6 + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: '22.15' + package-manager-cache: false cache: npm cache-dependency-path: frontend/package-lock.json - - uses: reviewdog/action-eslint@v1 + - uses: reviewdog/action-eslint@556a3fdaf8b4201d4d74d406013386aa4f7dab96 # v1 with: reporter: github-pr-check workdir: 'frontend/' diff --git a/.github/workflows/hocuspocus-docker.yml b/.github/workflows/hocuspocus-docker.yml index c260eb6215a..5fb928107f4 100644 --- a/.github/workflows/hocuspocus-docker.yml +++ b/.github/workflows/hocuspocus-docker.yml @@ -52,12 +52,16 @@ on: jobs: build: + if: github.repository == 'opf/openproject' runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ inputs.branch }} # this is empty on push, so it just checks out the event SHA in that case + persist-credentials: false - name: Include Git SHA in package.json version id: short-sha run: | @@ -85,31 +89,37 @@ jobs: BRANCH=$(echo "${INPUT_BRANCH:-$FALLBACK_BRANCH}" | tr / -) SPECIFIC_TAG=$BRANCH-$SHORT_SHA LATEST_TAG=$BRANCH-latest + MINOR_TAG= + MAJOR_TAG= if [ -n "$INPUT_TAG" ]; then SPECIFIC_TAG=$(echo "$INPUT_TAG" | tr -d v) LATEST_TAG=latest + MINOR_TAG=$(echo $SPECIFIC_TAG | cut -d. -f1-2) + MAJOR_TAG=$(echo $SPECIFIC_TAG | cut -d. -f1) fi echo 'tags<> $GITHUB_OUTPUT echo $REGISTRY:$SPECIFIC_TAG >> $GITHUB_OUTPUT echo $REGISTRY:$LATEST_TAG >> $GITHUB_OUTPUT + [ -n "$MINOR_TAG" ] && echo $REGISTRY:$MINOR_TAG >> $GITHUB_OUTPUT + [ -n "$MAJOR_TAG" ] && echo $REGISTRY:$MAJOR_TAG >> $GITHUB_OUTPUT echo 'EOF' >> $GITHUB_OUTPUT - name: Login to Docker Hub - uses: docker/login-action@v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Set up QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 - name: Build and push id: build - uses: docker/build-push-action@v7 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 with: context: extensions/op-blocknote-hocuspocus push: true @@ -121,11 +131,13 @@ jobs: - name: Summarize build run: | echo "Built and pushed the following tags:" >> $GITHUB_STEP_SUMMARY - echo "${{ steps.tags.outputs.tags }}" >> $GITHUB_STEP_SUMMARY + echo "${STEPS_TAGS_OUTPUTS_TAGS}" >> $GITHUB_STEP_SUMMARY + env: + STEPS_TAGS_OUTPUTS_TAGS: ${{ steps.tags.outputs.tags }} - name: Generate GHA token id: generate-gha-token - uses: actions/create-github-app-token@v3 + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3 with: app-id: ${{ vars.DEPLOY_APP_ID }} private-key: ${{ secrets.DEPLOY_APP_PRIVATE_KEY }} diff --git a/.github/workflows/hocuspocus-test.yml b/.github/workflows/hocuspocus-test.yml index 17af080cb10..053fae2239e 100644 --- a/.github/workflows/hocuspocus-test.yml +++ b/.github/workflows/hocuspocus-test.yml @@ -18,19 +18,21 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 + persist-credentials: false - - uses: actions/setup-node@v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: '22.19' + package-manager-cache: false cache: npm cache-dependency-path: extensions/op-blocknote-hocuspocus/package-lock.json - name: Install Dependencies - id: npm-i - run: npm i + id: npm-ci + run: npm ci - name: run tests id: npm-test diff --git a/.github/workflows/i18n-tasks.yml b/.github/workflows/i18n-tasks.yml index 07dcacf8cb2..e618e573fa0 100644 --- a/.github/workflows/i18n-tasks.yml +++ b/.github/workflows/i18n-tasks.yml @@ -28,10 +28,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Setup Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1 - name: Setup i18n-tasks run: | diff --git a/.github/workflows/openapi.yaml b/.github/workflows/openapi.yaml index bd2218b39d8..57d427055d7 100644 --- a/.github/workflows/openapi.yaml +++ b/.github/workflows/openapi.yaml @@ -17,12 +17,17 @@ jobs: name: APIv3 specification (OpenAPI 3.0) if: github.repository_owner == 'opf' runs-on: [ubuntu-latest] + permissions: + contents: read steps: - - uses: actions/checkout@v6 - - uses: ruby/setup-ruby@v1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - bundler-cache: true - - uses: actions/setup-node@v6 + persist-credentials: false + - uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1 + with: + bundler-cache: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: '20' + package-manager-cache: false - run: ./script/api/validate_spec diff --git a/.github/workflows/packager.yml b/.github/workflows/packager.yml index 095491ece13..96c2e9ae8b8 100644 --- a/.github/workflows/packager.yml +++ b/.github/workflows/packager.yml @@ -15,6 +15,8 @@ jobs: if: github.repository == 'opf/openproject' name: ${{ matrix.target }} runs-on: ubuntu-latest + permissions: + contents: read services: postgres: image: postgres:16 @@ -35,22 +37,23 @@ jobs: - el:9 - sles:15 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 + persist-credentials: false - name: Setup id: setup run: | VERSION=$(ruby -r ./lib/open_project/version.rb -e "puts OpenProject::VERSION") echo "version=$VERSION" >> $GITHUB_OUTPUT - if [[ "${{ github.ref_type }}" == "tag" ]]; then + if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then MAJOR=$(ruby -r ./lib/open_project/version.rb -e "puts OpenProject::VERSION::MAJOR") echo "channel=stable/${MAJOR}" >> $GITHUB_OUTPUT else - echo "channel=${{ github.ref_name }}" >> $GITHUB_OUTPUT + echo "channel=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT fi - name: Package - uses: pkgr/action/package@main + uses: pkgr/action/package@c5666febcd31750da6428042193fc5b2fb765435 # main id: package with: target: ${{ matrix.target }} @@ -60,7 +63,7 @@ jobs: env: | DATABASE_URL=postgres://app:p4ssw0rd@127.0.0.1:5432/app - name: Publish - uses: pkgr/action/publish@main + uses: pkgr/action/publish@c5666febcd31750da6428042193fc5b2fb765435 # main with: target: ${{ matrix.target }} token: ${{ secrets.PACKAGER_PUBLISH_TOKEN }} diff --git a/.github/workflows/pullpreview.yml b/.github/workflows/pullpreview.yml index bc62f22c36f..03e5606bdcd 100644 --- a/.github/workflows/pullpreview.yml +++ b/.github/workflows/pullpreview.yml @@ -19,7 +19,9 @@ jobs: runs-on: ubuntu-slim timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Generate .env.pullpreview file run: | echo "OPENPROJECT_SEED_ADMIN_USER_PASSWORD_RESET=false" >> .env.pullpreview @@ -41,7 +43,7 @@ jobs: cp ./docker/pullpreview/docker-compose.yml ./docker-compose.pullpreview.yml cp ./docker/pullpreview/Caddyfile ./Caddyfile cp ./docker/prod/Dockerfile ./Dockerfile - - uses: pullpreview/action@v6 + - uses: pullpreview/action@0dc5a741255c0afdd5a9e23e9264d6376652ec8e # v6 with: # allows to ssh to the instance using our GitHub ssh key admins: "crohr,HDinger,machisuji,oliverguenther,ulferts,wielinde,cbliard,@collaborators/push" diff --git a/.github/workflows/rubocop-core.yml b/.github/workflows/rubocop-core.yml index 8a2b2b97295..3071a5aeb7e 100644 --- a/.github/workflows/rubocop-core.yml +++ b/.github/workflows/rubocop-core.yml @@ -9,13 +9,18 @@ jobs: rubocop: name: rubocop runs-on: ubuntu-latest + permissions: + contents: read + checks: write steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Set up Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1 - name: Run Rubocop - uses: reviewdog/action-rubocop@v2 + uses: reviewdog/action-rubocop@b6d5e953a5fc0bf3ab65254e77730ea2174d6d6d # v2 with: github_token: ${{ secrets.github_token }} rubocop_version: gemfile @@ -32,7 +37,7 @@ jobs: - name: Install erb_lint run: gem install -N erb_lint erblint-github - name: Run erb-lint - uses: tk0miya/action-erblint@v1 + uses: tk0miya/action-erblint@44c5fe3552356fe8bff23f30d534aa4258aa3f7b # v1 with: github_token: ${{ secrets.github_token }} reporter: github-pr-check diff --git a/.github/workflows/seed-all-locales.yml b/.github/workflows/seed-all-locales.yml index 8d90c03eb34..a6fea961d3d 100644 --- a/.github/workflows/seed-all-locales.yml +++ b/.github/workflows/seed-all-locales.yml @@ -41,15 +41,18 @@ jobs: fi - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ steps.use_input_or_find_latest_release.outputs.ref }} + persist-credentials: false - name: Print ref to summary run: | SHA=$(git rev-parse HEAD) SHORT_SHA=$(git rev-parse --short HEAD) - echo "Testing seeding on **${{ steps.use_input_or_find_latest_release.outputs.ref }}** ([$SHORT_SHA](https://github.com/opf/openproject/commit/$SHA))" >> "$GITHUB_STEP_SUMMARY" + echo "Testing seeding on **${STEPS_USE_INPUT_OR_FIND_LATEST_RELEASE_OUTPUTS_REF}** ([$SHORT_SHA](https://github.com/opf/openproject/commit/$SHA))" >> "$GITHUB_STEP_SUMMARY" + env: + STEPS_USE_INPUT_OR_FIND_LATEST_RELEASE_OUTPUTS_REF: ${{ steps.use_input_or_find_latest_release.outputs.ref }} - name: List available locales id: list @@ -86,15 +89,16 @@ jobs: RAILS_ENV: development steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ needs.prepare.outputs.ref }} + persist-credentials: false - name: Install system dependencies run: sudo apt-get update && sudo apt-get install -y libpq-dev - name: Setup Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1 with: bundler-cache: true @@ -109,7 +113,8 @@ jobs: env: SILENCE_SQL_LOGS: 1 OPENPROJECT_EDITION: ${{ matrix.edition }} - run: ruby script/i18n/test_seed_all_locales ${{ matrix.locale }} + MATRIX_LOCALE: ${{ matrix.locale }} + run: ruby script/i18n/test_seed_all_locales ${MATRIX_LOCALE} notify-failure: needs: [prepare, seed] diff --git a/.github/workflows/sync-internal-fork.yml b/.github/workflows/sync-internal-fork.yml index 454ce6f4ddc..fe526b8979c 100644 --- a/.github/workflows/sync-internal-fork.yml +++ b/.github/workflows/sync-internal-fork.yml @@ -10,10 +10,11 @@ jobs: # Only run in the private fork, never in the public repo if: github.repository == 'opf/openproject-internal-fork' runs-on: ubuntu-latest - + permissions: + contents: write steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 token: ${{ secrets.OPENPROJECTCI_GH_FORK_TOKEN }} diff --git a/.github/workflows/test-core.yml b/.github/workflows/test-core.yml index bff718303ff..c13182f3952 100644 --- a/.github/workflows/test-core.yml +++ b/.github/workflows/test-core.yml @@ -26,19 +26,19 @@ jobs: name: Units + Features if: github.repository_owner == 'opf' runs-on: - labels: "runs-on=${{ github.run_id }}/image=ubuntu24-full-x64/family=m7+c7+r7+i7+r8/ram=128+256/cpu=32" + labels: "runs-on=${{ github.run_id }}/image=ubuntu24-full-x64/family=m8azn+m8+m7+i7+r8/ram=128+256/cpu=24+32/volume=50gb" timeout-minutes: 40 env: DOCKER_BUILDKIT: 1 CI_RETRY_COUNT: 3 steps: - - uses: runs-on/action@v2 + - uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - metrics: cpu,memory,disk,io,network - - uses: actions/checkout@v6 + persist-credentials: false - name: Cache DOCKER id: cache_docker - uses: runs-on/cache@v5 + uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5 with: path: cache/docker # Note: no restore keys since whenever the files below change, we want to rebuild the full image from scratch @@ -47,28 +47,28 @@ jobs: if: steps.cache_docker.outputs.cache-hit == 'true' run: docker load -i cache/docker/image.tar - name: Cache GEM - uses: runs-on/cache@v5 + uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5 with: path: cache/bundle key: gem-trixie-${{ hashFiles('.ruby-version') }}-${{ hashFiles('Gemfile.lock') }} restore-keys: | gem-trixie-${{ hashFiles('.ruby-version') }}- - name: Cache NPM - uses: runs-on/cache@v5 + uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5 with: path: cache/node key: node-${{ hashFiles('package.json', 'frontend/package-lock.json') }} restore-keys: | node- - name: Cache ANGULAR - uses: runs-on/cache@v5 + uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5 with: path: cache/angular key: angular-${{ hashFiles('package.json', 'frontend/package-lock.json') }} restore-keys: | angular- - name: Cache TEST RUNTIME - uses: runs-on/cache@v5 + uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5 with: path: cache/runtime-logs key: runtime-logs-${{ github.head_ref || github.ref }}-${{ github.sha }} diff --git a/.github/workflows/test-frontend-unit.yml b/.github/workflows/test-frontend-unit.yml index dc70bbc92e4..0b072098376 100644 --- a/.github/workflows/test-frontend-unit.yml +++ b/.github/workflows/test-frontend-unit.yml @@ -26,29 +26,37 @@ defaults: working-directory: ./frontend jobs: - units: - name: Units + unit: + name: Units (${{ matrix.browser }}) runs-on: ubuntu-latest - + strategy: + fail-fast: false + matrix: + browser: [chromium, firefox, webkit] + steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 - - - uses: actions/setup-node@v6 + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: '22.15' - cache: npm - cache-dependency-path: frontend/package-lock.json + package-manager-cache: false - name: Install Dependencies - id: npm-i - run: npm i + run: npm ci - name: Register plugins - id: npm-run-ci-plugins-register-frontend run: npm run ci:plugins:register_frontend - - name: Test (Angular) - id: npm-test - run: npm test + - name: Install Playwright dependencies + timeout-minutes: 5 + run: npx playwright install-deps ${{ matrix.browser }} + + - name: Install Playwright Browser + timeout-minutes: 5 + run: npx playwright install --only-shell ${{ matrix.browser }} + + - name: Test + run: npm test -- --browsers ${{ matrix.browser }} --reporters dot --reporters github-actions diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index 3798a0269fc..05b831e19f6 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -19,10 +19,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Set up Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1 - name: Verify linked version matches core version id: version-check @@ -32,7 +34,7 @@ jobs: - name: Add comment if versions differ if: steps.version-check.outputs.version_mismatch == 'true' - uses: marocchino/sticky-pull-request-comment@v3 + uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3 with: header: version-mismatch-comment message: | @@ -50,7 +52,7 @@ jobs: - The work package version OR your pull request target branch is correct - name: Version check passed if: steps.version-check.outputs.version_mismatch != 'true' - uses: marocchino/sticky-pull-request-comment@v3 + uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3 with: header: version-mismatch-comment delete: true diff --git a/.github/workflows/zizmor-scan.yml b/.github/workflows/zizmor-scan.yml new file mode 100644 index 00000000000..fc5f85283d3 --- /dev/null +++ b/.github/workflows/zizmor-scan.yml @@ -0,0 +1,30 @@ +name: GitHub Actions Security Analysis with zizmor 🌈 + +on: + pull_request: + paths: + - ".github/workflows/**" + - ".github/actions/**" + schedule: + - cron: "0 2 * * *" + workflow_dispatch: + +permissions: {} + +jobs: + zizmor: + name: Run zizmor 🌈 + runs-on: ubuntu-latest + if: github.repository_owner == 'opf' + permissions: + security-events: write # Required for upload-sarif + contents: read + actions: read + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Run zizmor 🌈 + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 00000000000..d4ca85896d8 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,16 @@ +rules: + artipacked: + ignore: + # These workflows intentionally persist credentials because they push commits/branches + - create-merge-from-previous-release-branch-pr.yml + - create-merge-release-into-dev-pr.yml + - crowdin.yml + - sync-internal-fork.yml + dangerous-triggers: + ignore: + # CLA workflow intentionally uses pull_request_target to handle outside contributors + - cla.yml + cache-poisoning: + ignore: + # Frontend unit tests cache npm packages; only triggered on push/PR from trusted branches + - test-frontend-unit.yml diff --git a/.pkgr.yml b/.pkgr.yml index 014e23e2bb1..7f7947a1976 100644 --- a/.pkgr.yml +++ b/.pkgr.yml @@ -27,6 +27,7 @@ targets: - NODE_ENV=production - NPM_CONFIG_PRODUCTION=false - SECRET_KEY_BASE=1 + - OPENPROJECT_DISABLE__SECRET_KEY_BASE__CHECK=true dependencies: - epel-release - ImageMagick diff --git a/.rubocop.yml b/.rubocop.yml index 415242606cd..6b64ca674d8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -142,6 +142,11 @@ Naming/ClassAndModuleCamelCase: Naming/FileName: Enabled: false +Naming/MethodParameterName: + AllowedNames: + - id + - cf # custom fields are commonly abbreviated like that + Naming/PredicatePrefix: ForbiddenPrefixes: - is_ diff --git a/Gemfile b/Gemfile index cfee700ee02..0b047927919 100644 --- a/Gemfile +++ b/Gemfile @@ -62,14 +62,14 @@ gem "warden-basic_auth", "~> 0.2.1" gem "pagy" gem "will_paginate", "~> 4.0.0" -gem "friendly_id", "~> 5.6.0" +gem "friendly_id", "~> 5.7.0" gem "scimitar", "~> 2.13" gem "acts_as_list", "~> 1.2.6" gem "acts_as_tree", "~> 2.9.0" gem "awesome_nested_set", "~> 3.9.0" -gem "closure_tree", "~> 9.6.1" +gem "closure_tree", "~> 9.7.0" gem "rubytree", "~> 2.2.0" gem "addressable", "~> 2.9.0" @@ -87,7 +87,7 @@ gem "htmldiff" gem "stringex", "~> 2.8.5" # CommonMark markdown parser with GFM extension -gem "commonmarker", "~> 2.8.0" +gem "commonmarker", "~> 2.8.2" # HTML pipeline for transformations on text formatter output # such as sanitization or additional features @@ -201,8 +201,8 @@ gem "nokogiri", "~> 1.19.2" gem "carrierwave", "~> 2.2.6" gem "carrierwave_direct", "~> 3.0.0" -gem "ssrf_filter", "~> 1.3" gem "fog-aws" +gem "ssrf_filter", "~> 1.3" gem "aws-sdk-core", "~> 3.244" # File upload via fog + screenshots on travis @@ -219,7 +219,7 @@ gem "mini_magick", "~> 5.3.0", require: false gem "validate_url" # Storages support code -gem "dry-container" +gem "dry-core" gem "dry-monads" gem "dry-validation" @@ -236,18 +236,18 @@ gem "yabeda-puma-plugin" gem "yabeda-rails" # opentelemetry -gem "opentelemetry-exporter-otlp", "~> 0.33.0", require: false +gem "opentelemetry-exporter-otlp", "~> 0.34.0", require: false gem "opentelemetry-instrumentation-all", "~> 0.93.0", require: false gem "opentelemetry-sdk", "~> 1.10", require: false -gem "view_component", "~> 4.9.0" +gem "view_component", "~> 4.10.0" # Lookbook gem "lookbook", "2.3.14" gem "inline_svg", "~> 1.10.0" # Require factory_bot for usage with openproject plugins testing -gem "factory_bot", "~> 6.5.6", require: false +gem "factory_bot", "~> 6.6.0", require: false # require factory_bot_rails for convenience in core development gem "factory_bot_rails", "~> 6.5.0", require: false @@ -365,7 +365,7 @@ group :development, :test do gem "rubocop-factory_bot", require: false gem "rubocop-openproject", require: false gem "rubocop-performance", require: false - gem "rubocop-rails", "~> 2.34.2" + gem "rubocop-rails", "~> 2.35.1" gem "rubocop-rspec", require: false gem "rubocop-rspec_rails", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 6a222201d7f..faa7638310f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -242,7 +242,7 @@ GEM remote: https://rubygems.org/ specs: Ascii85 (2.0.1) - action_text-trix (2.1.18) + action_text-trix (2.1.19) railties actioncable (8.1.3) actionpack (= 8.1.3) @@ -361,8 +361,8 @@ GEM awesome_nested_set (3.9.0) activerecord (>= 4.0.0, < 8.2) aws-eventstream (1.4.0) - aws-partitions (1.1242.0) - aws-sdk-core (3.246.0) + aws-partitions (1.1249.0) + aws-sdk-core (3.247.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) @@ -370,11 +370,11 @@ GEM bigdecimal jmespath (~> 1, >= 1.6.1) logger - aws-sdk-kms (1.124.0) - aws-sdk-core (~> 3, >= 3.244.0) + aws-sdk-kms (1.125.0) + aws-sdk-core (~> 3, >= 3.247.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.220.0) - aws-sdk-core (~> 3, >= 3.244.0) + aws-sdk-s3 (1.222.0) + aws-sdk-core (~> 3, >= 3.247.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) aws-sdk-sns (1.113.0) @@ -441,7 +441,7 @@ GEM childprocess (5.1.0) logger (~> 1.5) climate_control (1.2.0) - closure_tree (9.6.1) + closure_tree (9.7.0) activerecord (>= 7.2.0) with_advisory_lock (>= 7.5.0) zeitwerk (~> 2.7) @@ -450,13 +450,13 @@ GEM descendants_tracker (~> 0.0.1) color_conversion (0.1.2) colored2 (4.0.3) - commonmarker (2.8.1-aarch64-linux) - commonmarker (2.8.1-aarch64-linux-musl) - commonmarker (2.8.1-arm-linux) - commonmarker (2.8.1-arm64-darwin) - commonmarker (2.8.1-x86_64-darwin) - commonmarker (2.8.1-x86_64-linux) - commonmarker (2.8.1-x86_64-linux-musl) + commonmarker (2.8.2-aarch64-linux) + commonmarker (2.8.2-aarch64-linux-musl) + commonmarker (2.8.2-arm-linux) + commonmarker (2.8.2-arm64-darwin) + commonmarker (2.8.2-x86_64-darwin) + commonmarker (2.8.2-x86_64-linux) + commonmarker (2.8.2-x86_64-linux-musl) compare-xml (0.66) nokogiri (~> 1.8) concurrent-ruby (1.3.6) @@ -472,14 +472,14 @@ GEM bigdecimal rexml crass (1.0.6) - css_parser (2.1.0) + css_parser (2.2.0) addressable csv (3.3.5) cuprite (0.17) capybara (~> 3.0) ferrum (~> 0.17.0) daemons (1.4.1) - dalli (5.0.2) + dalli (5.0.4) logger date (3.5.1) date_validator (0.12.0) @@ -504,11 +504,9 @@ GEM dotenv (= 3.2.0) railties (>= 6.1) drb (2.2.3) - dry-configurable (1.3.0) - dry-core (~> 1.1) + dry-configurable (1.4.0) + dry-core (~> 1.0) zeitwerk (~> 2.6) - dry-container (0.11.0) - concurrent-ruby (~> 1.0) dry-core (1.2.0) concurrent-ruby (~> 1.0) logger @@ -578,12 +576,12 @@ GEM eventmachine_httpserver (0.2.1) excon (1.4.2) logger - factory_bot (6.5.6) + factory_bot (6.6.0) activesupport (>= 6.1.0) factory_bot_rails (6.5.1) factory_bot (~> 6.5) railties (>= 6.1.0) - faraday (2.14.1) + faraday (2.14.2) faraday-net_http (>= 2.0, < 3.5) json logger @@ -624,7 +622,7 @@ GEM nokogiri (>= 1.5.11, < 2.0.0) formatador (1.2.3) reline - friendly_id (5.6.0) + friendly_id (5.7.0) activerecord (>= 4.0.0) front_matter_parser (1.0.1) fugit (1.12.1) @@ -651,7 +649,7 @@ GEM mini_mime (~> 1.1) representable (~> 3.0) retriable (~> 3.1) - google-apis-gmail_v1 (0.48.0) + google-apis-gmail_v1 (0.49.0) google-apis-core (>= 0.15.0, < 2.a) google-cloud-env (2.3.1) base64 (~> 0.2) @@ -714,7 +712,7 @@ GEM htmlentities (4.3.4) http-2 (1.1.3) http_parser.rb (0.8.1) - httpx (1.7.6) + httpx (1.7.8) http-2 (>= 1.1.3) i18n (1.14.8) concurrent-ruby (~> 1.0) @@ -733,7 +731,7 @@ GEM rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.8, >= 1.8.1) terminal-table (>= 1.5.1) - icalendar (2.12.2) + icalendar (2.12.3) base64 ice_cube (~> 0.16) logger @@ -755,8 +753,8 @@ GEM reline (>= 0.4.2) iso8601 (0.13.0) jmespath (1.6.2) - job-iteration (1.13.1) - activejob (>= 7.0) + job-iteration (1.14.0) + activejob (>= 7.1) json (2.19.5) json-jwt (1.17.0) activesupport (>= 4.2) @@ -776,7 +774,7 @@ GEM json_spec (1.1.5) multi_json (~> 1.0) rspec (>= 2.0, < 4.0) - jwt (3.1.2) + jwt (3.2.0) base64 ladle (1.0.1) open4 (~> 1.0) @@ -884,7 +882,7 @@ GEM racc (~> 1.4) nokogiri (1.19.3-x86_64-linux-musl) racc (~> 1.4) - oj (3.17.0) + oj (3.17.1) bigdecimal (>= 3.0) ostruct (>= 0.2) okcomputer (1.19.1) @@ -918,14 +916,14 @@ GEM view_component (>= 3.1, < 5.0) openproject-token (8.8.2) activemodel - openssl (4.0.1) + openssl (4.0.2) openssl-signature_algorithm (1.3.0) openssl (> 2.0) - opentelemetry-api (1.9.0) + opentelemetry-api (1.10.0) logger - opentelemetry-common (0.24.0) + opentelemetry-common (0.25.0) opentelemetry-api (~> 1.0) - opentelemetry-exporter-otlp (0.33.0) + opentelemetry-exporter-otlp (0.34.0) google-protobuf (>= 3.18) googleapis-common-protos-types (~> 1.3) opentelemetry-api (~> 1.1) @@ -1086,22 +1084,22 @@ GEM opentelemetry-helpers-sql-processor opentelemetry-instrumentation-base (~> 0.25) opentelemetry-semantic_conventions (>= 1.8.0) - opentelemetry-registry (0.5.0) + opentelemetry-registry (0.6.0) opentelemetry-api (~> 1.1) - opentelemetry-sdk (1.11.0) + opentelemetry-sdk (1.12.0) logger opentelemetry-api (~> 1.1) opentelemetry-common (~> 0.20) opentelemetry-registry (~> 0.2) opentelemetry-semantic_conventions - opentelemetry-semantic_conventions (1.37.0) + opentelemetry-semantic_conventions (1.37.1) opentelemetry-api (~> 1.0) optimist (3.2.1) os (1.1.4) ostruct (0.6.3) - ox (2.14.25) + ox (2.14.26) bigdecimal (>= 3.0) - pagy (43.5.3) + pagy (43.5.4) json uri yaml @@ -1199,7 +1197,7 @@ GEM eventmachine_httpserver http_parser.rb (~> 0.8.0) multi_json - puma (8.0.0) + puma (8.0.1) nio4r (~> 2.0) puma-plugin-statsd (2.8.0) puma (>= 5.0, < 9) @@ -1276,7 +1274,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rb_sys (0.9.127) + rb_sys (0.9.128) rake-compiler-dock (= 1.12.0) rbtrace (0.5.3) ffi (>= 1.0.6) @@ -1291,7 +1289,7 @@ GEM redcarpet (3.6.1) redis (5.4.1) redis-client (>= 0.22.0) - redis-client (0.28.0) + redis-client (0.29.0) connection_pool regexp_parser (2.12.0) reline (0.6.3) @@ -1337,7 +1335,7 @@ GEM rspec-support (3.13.7) rspec-wait (1.0.2) rspec (>= 3.4) - rubocop (1.86.1) + rubocop (1.86.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -1363,7 +1361,7 @@ GEM lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rails (2.34.3) + rubocop-rails (2.35.1) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) @@ -1404,9 +1402,9 @@ GEM scimitar (2.15.0) rails (>= 7.0) securerandom (0.4.1) - selenium-devtools (0.147.0) + selenium-devtools (0.148.0) selenium-webdriver (~> 4.2) - selenium-webdriver (4.43.0) + selenium-webdriver (4.44.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -1427,7 +1425,7 @@ GEM bigdecimal logger ruby-ole - spring (4.4.2) + spring (4.5.0) spring-commands-rspec (1.0.4) spring (>= 0.9.1) spring-commands-rubocop (0.4.0) @@ -1494,8 +1492,8 @@ GEM activemodel (>= 3.0.0) public_suffix vcr (6.4.0) - vernier (1.10.0) - view_component (4.9.0) + vernier (1.10.1) + view_component (4.10.0) actionview (>= 7.1.0) activesupport (>= 7.1.0) concurrent-ruby (~> 1) @@ -1599,9 +1597,9 @@ DEPENDENCIES carrierwave (~> 2.2.6) carrierwave_direct (~> 3.0.0) climate_control - closure_tree (~> 9.6.1) + closure_tree (~> 9.7.0) colored2 - commonmarker (~> 2.8.0) + commonmarker (~> 2.8.2) compare-xml (~> 0.66) connection_pool (~> 3.0.2) costs! @@ -1616,7 +1614,7 @@ DEPENDENCIES disposable (~> 0.6.2) doorkeeper (~> 5.9.0) dotenv-rails - dry-container + dry-core dry-monads dry-validation email_validator (~> 2.2.3) @@ -1624,12 +1622,12 @@ DEPENDENCIES erb_lint erblint-github escape_utils (~> 1.3) - factory_bot (~> 6.5.6) + factory_bot (~> 6.6.0) factory_bot_rails (~> 6.5.0) ffi (~> 1.17) flamegraph fog-aws - friendly_id (~> 5.6.0) + friendly_id (~> 5.7.0) fuubar (~> 2.5.0) globalid (~> 1.3) good_job (~> 4.18.2) @@ -1700,7 +1698,7 @@ DEPENDENCIES openproject-webhooks! openproject-wikis! openproject-xls_export! - opentelemetry-exporter-otlp (~> 0.33.0) + opentelemetry-exporter-otlp (~> 0.34.0) opentelemetry-instrumentation-all (~> 0.93.0) opentelemetry-sdk (~> 1.10) overviews! @@ -1747,7 +1745,7 @@ DEPENDENCIES rubocop-factory_bot rubocop-openproject rubocop-performance - rubocop-rails (~> 2.34.2) + rubocop-rails (~> 2.35.1) rubocop-rspec rubocop-rspec_rails ruby-duration (~> 3.2.0) @@ -1785,7 +1783,7 @@ DEPENDENCIES validate_url vcr vernier - view_component (~> 4.9.0) + view_component (~> 4.10.0) warden (~> 1.2) warden-basic_auth (~> 0.2.1) webmock (~> 3.26) @@ -1798,7 +1796,7 @@ DEPENDENCIES CHECKSUMS Ascii85 (2.0.1) sha256=15cb5d941808543cbb9e7e6aea3c8ec3877f154c3461e8b3673e97f7ecedbe5a - action_text-trix (2.1.18) sha256=3fdb83f8bff4145d098be283cdd47ac41caf5110bfa6df4695ed7127d7fb3642 + action_text-trix (2.1.19) sha256=7012f59421009cf284aa651294896414d653a61a2417c9b8714c8476d2f74009 actioncable (8.1.3) sha256=e5bc7f75e44e6a22de29c4f43176927c3a9ce4824464b74ed18d8226e75a80f0 actionmailbox (8.1.3) sha256=df7da474eaa0e70df4ed5a6fef66eb3b3b0f2dbf7f14518deee8d77f1b4aae59 actionmailer (8.1.3) sha256=831f724891bb70d0aaa4d76581a6321124b6a752cb655c9346aae5479318448d @@ -1831,10 +1829,10 @@ CHECKSUMS auto_strip_attributes (2.6.0) sha256=a7e2e0cf744de2bcd947fd68014220702bcc88c81274c1cd9ce6f7316aae39b0 awesome_nested_set (3.9.0) sha256=3ce99e816550f97f4de118e621630070aacf24928b920fe4a68846578a8daaed aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b - aws-partitions (1.1242.0) sha256=58886ab5484ccf9287a8d55e603c3c0fb004241dfb4c0c7690b67d18e4c39352 - aws-sdk-core (3.246.0) sha256=393864ec8948560e69fcccc2e4d256b40c7028eb98930608dd295279e3c4ddcc - aws-sdk-kms (1.124.0) sha256=40d00ab706d7e49fd620270bd0dcb546f266295abdd49b54fec2611e2a41f37c - aws-sdk-s3 (1.220.0) sha256=237fda5e6ac7ecdd9c848e27187bfdc370edad5c5a141aeec389fb450fa28c7c + aws-partitions (1.1249.0) sha256=f0bed070aba353e6ffe439953d6c354b3f51d16770386d2b198e71d34612f2e9 + aws-sdk-core (3.247.0) sha256=789864594ce8cef05ee3d81fa8ed506099280bda6ea12a7612b8b7c5e5e62851 + aws-sdk-kms (1.125.0) sha256=23f81bc0838ae6ec2e8de3eae88af521d0e29d3a59b6c9dbb4b21343ba476bc8 + aws-sdk-s3 (1.222.0) sha256=bae4b06fccf0b81b8d77e7abfc56e5e2146590d43c4ab58db0cee3ff5bbfb7f1 aws-sdk-sns (1.113.0) sha256=15fe37d010e86f4c28b4c2f2133c463ce5c14189ec3673a1f43c30dfee511b0f aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 axe-core-api (4.11.3) sha256=f5f6e802743644a50e2d8ef24c22aefbb6df49dd169024ff0144b47f37e652ba @@ -1860,18 +1858,18 @@ CHECKSUMS cgi (0.5.1) sha256=e93fcafc69b8a934fe1e6146121fa35430efa8b4a4047c4893764067036f18e9 childprocess (5.1.0) sha256=9a8d484be2fd4096a0e90a0cd3e449a05bc3aa33f8ac9e4d6dcef6ac1455b6ec climate_control (1.2.0) sha256=36b21896193fa8c8536fa1cd843a07cf8ddbd03aaba43665e26c53ec1bd70aa5 - closure_tree (9.6.1) sha256=f6af11243dea13d888788ffb0fd28014bd1077abe3a4233ea1e7044e52fc6377 + closure_tree (9.7.0) sha256=54403254741e70b5da35eefffc6b8172505da71c004ff75635f4e1299e6ba44c coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b coercible (1.0.0) sha256=5081ad24352cc8435ce5472bc2faa30260c7ea7f2102cc6a9f167c4d9bffaadc color_conversion (0.1.2) sha256=99bea5fa412e1527a11389975aa6ad445ff8528ebae202c11d08c45ea2b94c96 colored2 (4.0.3) sha256=63e1038183976287efc43034f5cca17fb180b4deef207da8ba78d051cbce2b37 - commonmarker (2.8.1-aarch64-linux) sha256=f855599cc6855f4137d72dcacae9571451075afe6e6c8522eba353df9b81d0bf - commonmarker (2.8.1-aarch64-linux-musl) sha256=bbc2b3d361403431a5aac737dc86997d3b2843276ef395e7499cf17ad48e1b2c - commonmarker (2.8.1-arm-linux) sha256=ede48564a9c2e29e003361fd7b0b158b3b85bcbd5a15b963fa2b3c78eef3993f - commonmarker (2.8.1-arm64-darwin) sha256=cf75760122d6e1c3f8626b5a6911636d2e0aaa26fa8dd377fea440a49c854b3e - commonmarker (2.8.1-x86_64-darwin) sha256=75afa559c1f1cc201afbb81291a2f2fc77b2941347deaa8d564d008ba04ecb18 - commonmarker (2.8.1-x86_64-linux) sha256=c8f2e903c6ee4e2c7e280aa8b49ef37c53202d388e3a2ee06dfdccc8c6fb634f - commonmarker (2.8.1-x86_64-linux-musl) sha256=9b6305ce47e0d444b77dabf1762bf76085c2f7963b63c25c02ec36edcf06c785 + commonmarker (2.8.2-aarch64-linux) sha256=715a5df28e5c1ec7b520e6441466062e9717b82193d2a5bd11a9c94f2f1e15f0 + commonmarker (2.8.2-aarch64-linux-musl) sha256=c7d587bd7978416978e84d0de7488906b8f29b3f67ce8424942f0ca9aa6899db + commonmarker (2.8.2-arm-linux) sha256=da9da5c08d5133ad429ece13f2e93d3a30e1b23621ffac9040e185d7ea29bde6 + commonmarker (2.8.2-arm64-darwin) sha256=cd7364f1fb9735d60218e4af0a4afbbeedbc465eb81cccca56f29a4efe2b5013 + commonmarker (2.8.2-x86_64-darwin) sha256=fb946e0bb01f0e5742aef75cc972b51ffe099e5b983ce9cd792b4a78c5d2c297 + commonmarker (2.8.2-x86_64-linux) sha256=42cfb6532b15dd5821ce99e47397923255f32dcfc81024ef4c17e01e66ac7d75 + commonmarker (2.8.2-x86_64-linux-musl) sha256=66568dce1c6f265e85d57ea7f749a148446527c7dd599c6ded4cdc819997c43e compare-xml (0.66) sha256=e21aa5c0f69ef1177eced997c688fd4df989084e74a1b612257af32e1dd05319 concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a @@ -1881,11 +1879,11 @@ CHECKSUMS counter_culture (3.13.1) sha256=c297961933d9a9b96683fc298d68fde44039eca7c5876a2b05c3b180fe1c6328 crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d - css_parser (2.1.0) sha256=bfb7c9cf3896426b53337e34b4ad391c3cfe8c2f2c839e72f2cdccf615fb5247 + css_parser (2.2.0) sha256=23d1b247d7bc78cb2f2fe54629fb755e68d3004f1d1bd5c66d5096d42bff6325 csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f cuprite (0.17) sha256=b140d5dc70d08b97ad54bcf45cd95d0bd430e291e9dffe76fff851fddd57c12b daemons (1.4.1) sha256=8fc76d76faec669feb5e455d72f35bd4c46dc6735e28c420afb822fac1fa9a1d - dalli (5.0.2) sha256=818469227b9acdd9da3fc65ec5ae75d4020115545879e5e8f95634085e9a8749 + dalli (5.0.4) sha256=3b258f100b8e3acd128a095fbd090c5a1324a6f5238fd1f86a2547473e6d49fc date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 date_validator (0.12.0) sha256=68c9834da240347b9c17441c553a183572508617ebfbe8c020020f3192ce3058 deckar01-task_list (2.3.4) sha256=66abdc7e009ea759732bb53867e1ea42de550e2aa03ac30a015cbf42a04c1667 @@ -1898,8 +1896,7 @@ CHECKSUMS dotenv (3.2.0) sha256=e375b83121ea7ca4ce20f214740076129ab8514cd81378161f11c03853fe619d dotenv-rails (3.2.0) sha256=657e25554ba622ffc95d8c4f1670286510f47f2edda9f68293c3f661b303beab drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373 - dry-configurable (1.3.0) sha256=882d862858567fc1210d2549d4c090f34370fc1bb7c5c1933de3fe792e18afa8 - dry-container (0.11.0) sha256=23be9381644d47343f3bf13b082b4255994ada0bfd88e0737eaaadc99d035229 + dry-configurable (1.4.0) sha256=e35d1b5f3c081753ef361f564919db79000f32cfa6f20ee3a3ba5921b41b73ce dry-core (1.2.0) sha256=0cc5a7da88df397f153947eeeae42e876e999c1e30900f3c536fb173854e96a1 dry-inflector (1.3.1) sha256=7fb0c2bb04f67638f25c52e7ba39ab435d922a3a5c3cd196120f63accb682dcc dry-initializer (3.2.0) sha256=37d59798f912dc0a1efe14a4db4a9306989007b302dcd5f25d0a2a20c166c4e3 @@ -1923,9 +1920,9 @@ CHECKSUMS eventmachine (1.2.7) sha256=994016e42aa041477ba9cff45cbe50de2047f25dd418eba003e84f0d16560972 eventmachine_httpserver (0.2.1) sha256=5db5e8a23754204d43592e5fcc2160457c57c870babe6307c4e61fc95019b809 excon (1.4.2) sha256=32d8d8eda619717d9b8043b4675e096fb5c2139b080e2ad3b267f88c545aaa35 - factory_bot (6.5.6) sha256=12beb373214dccc086a7a63763d6718c49769d5606f0501e0a4442676917e077 + factory_bot (6.6.0) sha256=1fc1b3b5620ec980a6a27aec1b6ec8c250ca82962e970e8a40f93e8d388d4b89 factory_bot_rails (6.5.1) sha256=d3cc4851eae4dea8a665ec4a4516895045e710554d2b5ac9e68b94d351bc6d68 - faraday (2.14.1) sha256=a43cceedc1e39d188f4d2cdd360a8aaa6a11da0c407052e426ba8d3fb42ef61c + faraday (2.14.2) sha256=73ccb9994a9e8648f010e32eca2ae82e41c57860aa10932cda29418b9e0223ad faraday-follow_redirects (0.5.0) sha256=5cde93c894b30943a5d2b93c2fe9284216a6b756f7af406a1e55f211d97d10ad faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c ferrum (0.17.2) sha256=2c2540a850b211a46f4d81de21bfd62048f507e4c327d1807225c3823c17e6ee @@ -1943,7 +1940,7 @@ CHECKSUMS fog-json (1.3.0) sha256=8c2e4feb221c14f92ceeffb0aa5c8b6e8dd7c614a9141dfe7905f2dffebea217 fog-xml (0.1.5) sha256=52b9fea10701461dd3eaf9d9839702169b418dbbf50426786b9b74fade373bd6 formatador (1.2.3) sha256=19fa898133c2c26cdbb5d09f6998c1e137ad9427a046663e55adfe18b950d894 - friendly_id (5.6.0) sha256=28e221cd53fbd21586321164c1c6fd0c9ba8dde13969cb2363679f44726bb0c3 + friendly_id (5.7.0) sha256=b8994416ebaceebc7c60bc556e84ef32a3a663e2e582eb04c429216cc98de4c4 front_matter_parser (1.0.1) sha256=bae298bda01db95788a4d6452f1670a3d198c6716c8d3727db9a95533deb7b7b fugit (1.12.1) sha256=5898f478ede9b415f0804e42b8f3fd53f814bd85eebffceebdbc34e1107aaf68 fuubar (2.5.1) sha256=b272a7804b282661c7fab583a3764f92543cb482c365ae39c685cd218fdd4880 @@ -1951,7 +1948,7 @@ CHECKSUMS globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11 good_job (4.18.2) sha256=7e557a15865fc7b7ad4ab71644cf1d4189a2a1869d3b381e5e88741c540beca6 google-apis-core (1.0.2) sha256=ba4579aaadc902d6cc7bc8db88f566ab00f5e31ea87ab41e9f9a032c470f2629 - google-apis-gmail_v1 (0.48.0) sha256=561534bb3d93610032720d0459153c432dc8e47e7096a1250fbe0ee8dcc6540c + google-apis-gmail_v1 (0.49.0) sha256=f5b5e597c8018d9e4b8df37fb06a8df429bd2de5f0fc94fb21a4d32b139f6ff1 google-cloud-env (2.3.1) sha256=0faac01eb27be78c2591d64433663b1a114f8f7af55a4f819755426cac9178e7 google-logging-utils (0.2.0) sha256=675462b4ea5affa825a3442694ca2d75d0069455a1d0956127207498fca3df7b google-protobuf (4.34.1) sha256=347181542b8d659c60f028fa3791c9cccce651a91ad27782dbc5c5e374796cdc @@ -1978,11 +1975,11 @@ CHECKSUMS htmlentities (4.3.4) sha256=125a73c6c9f2d1b62100b7c3c401e3624441b663762afa7fe428476435a673da http-2 (1.1.3) sha256=1b2f379d35a11dbae94f8a1a52c053d8c161eb4a0c98b5d1605ff1b2bf171c9c http_parser.rb (0.8.1) sha256=9ae8df145b39aa5398b2f90090d651c67bd8e2ebfe4507c966579f641e11097a - httpx (1.7.6) sha256=82d825abc9876a132adc3492c56a0c528478ac238dd6f74d3422ab0036c6b5c8 + httpx (1.7.8) sha256=6d769465ed608287a272ba0e4700fc22cee6f0335d80bd5c2effaf7fb7bd2a3a i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5 i18n-js (4.2.4) sha256=61390d372f8fa68c495c5907d577657e8cc3a7031f4945db1e91f935e1391355 i18n-tasks (1.1.2) sha256=4dcfba49e52a623f30661cb316cb80d84fbba5cb8c6d88ef5e02545fffa3637a - icalendar (2.12.2) sha256=b63dfb784b4d1214bceaec40097e61eaf78c8691929d7f217779956d9019d9f5 + icalendar (2.12.3) sha256=37dea76c36f5b21dd745bcd3cb53e7bacfbb4ad0a8d9c2a1c0aa9d39af83c850 ice_cube (0.17.0) sha256=32deb45dda4b4acc53505c2f581f6d32b5afc04d29b9004769944a0df5a5fcbe ice_nine (0.11.2) sha256=5d506a7d2723d5592dc121b9928e4931742730131f22a1a37649df1c1e2e63db image_processing (1.14.0) sha256=754cc169c9c262980889bec6bfd325ed1dafad34f85242b5a07b60af004742fb @@ -1992,13 +1989,13 @@ CHECKSUMS irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3 iso8601 (0.13.0) sha256=298c2b15b7be5fa95a1372813d36a2257656cd8e906dfbc1f5cb409851425aa2 jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 - job-iteration (1.13.1) sha256=af4d5ac624c35ed2f32ed78de92d4673f0a93212105b96d46877b8422e3ff5a3 + job-iteration (1.14.0) sha256=f154f978109acc838c0359ecde2fdd4dccc3382f95a22e03a58ac561a3615224 json (2.19.5) sha256=218a18553e4801d579ca7e0f5bc72bafd776d7397238a1fb4e74db5b0a812c59 json-jwt (1.17.0) sha256=6ff99026b4c54281a9431179f76ceb81faa14772d710ef6169785199caadc4cc json-schema (6.2.0) sha256=e8bff46ed845a22c1ab2bd0d7eccf831c01fe23bb3920caa4c74db4306813666 json_schemer (2.5.0) sha256=2f01fb4cce721a4e08dd068fc2030cffd0702a7f333f1ea2be6e8991f00ae396 json_spec (1.1.5) sha256=7a77b97a92c787e2aa3fbc4a1239afc3342c781151dc98cfb81461b3b7cad10f - jwt (3.1.2) sha256=af6991f19a6bb4060d618d9add7a66f0eeb005ac0bc017cd01f63b42e122d535 + jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7 ladle (1.0.1) sha256=e8586964108c798d48bf57d2a65bd5602e8e5223a176b6602a0fb36c0bda90dc language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc launchy (3.1.1) sha256=72b847b5cc961589dde2c395af0108c86ff0119f42d4648d25b5440ebb10059e @@ -2045,7 +2042,7 @@ CHECKSUMS nokogiri (1.19.3-x86_64-darwin) sha256=77f3fba57d46c53ab31e62fc6c28f705109d1bf6264356c76f132b2be5728d4d nokogiri (1.19.3-x86_64-linux-gnu) sha256=2f5078620fe12e83669b5b17311b32532a8153d02eee7ad06948b926d6080976 nokogiri (1.19.3-x86_64-linux-musl) sha256=248c906d2166eca5efb56d52fdee5f9a1f51d69a72e2b64fdac647b4ce39ea3f - oj (3.17.0) sha256=5684b2127fb70e650fae90df521b91336ff8e55e2e1011ed80eb0283beac5360 + oj (3.17.1) sha256=b00687f10bf68a32bfb633b87624174faf0989a5c96aff2f3f96f992717ce782 okcomputer (1.19.1) sha256=7df770e768434816d228407f0786563827cbf34cb379933578829720cb4f1e77 omniauth (1.9.2) omniauth-openid-connect (0.5.0) @@ -2082,11 +2079,11 @@ CHECKSUMS openproject-webhooks (1.0.0) openproject-wikis (1.0.0) openproject-xls_export (1.0.0) - openssl (4.0.1) sha256=e27974136b7b02894a1bce46c5397ee889afafe704a839446b54dc81cb9c5f7d + openssl (4.0.2) sha256=1037ad2868ae58df9ad917891c0c0f9815a1172f6846d4bcdd508e4c2ee747c2 openssl-signature_algorithm (1.3.0) sha256=a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80 - opentelemetry-api (1.9.0) sha256=d24065dd26583babd8d498d38ea35f74dfa193fb7102512e6e161649440079fb - opentelemetry-common (0.24.0) sha256=f1647b233b8ac667feeb74d66a65b702008d9ab55aae825c220b4fe2c14fa773 - opentelemetry-exporter-otlp (0.33.0) sha256=6e9ce38e393c7eb9aea3fb57b128174a0066767bf495f4fd9e63d7607e0b2ad3 + opentelemetry-api (1.10.0) sha256=99ee7c829b18381c31a817ee9bf6a160d737542d99cb8da55d443336d266bfa9 + opentelemetry-common (0.25.0) sha256=73915362e58d337fc92acbe1abfdaee1f725442527125fdb2af1420417f1149d + opentelemetry-exporter-otlp (0.34.0) sha256=3b3cdf4329ba30f4389d849c7f13b8f9f983ecb4a030031c03997dffae1e2a60 opentelemetry-helpers-mysql (0.6.0) sha256=7eeb5e6950c434775a8cf28b5fde4defc12e8b865c86479ce3119fcf593d9337 opentelemetry-helpers-sql (0.4.0) sha256=b10e8c3a2cca28a98af951bbb3e4efdc59e68b25ba0825e055574af543420afb opentelemetry-helpers-sql-processor (0.5.0) sha256=b199241bc9451fcbd9f00b2f454830af19d4ca27c2219ea379c9b0d53cd0e0f1 @@ -2136,15 +2133,15 @@ CHECKSUMS opentelemetry-instrumentation-sidekiq (0.29.0) sha256=b1d2a0cb9041a5e14239fe7c94d99e3dd07f870e2759460ab63592d7cdd8aadc opentelemetry-instrumentation-sinatra (0.30.0) sha256=b67301153420f43264a0c68cdb3ca5bd77467cf5054e57b83a2bf891aaaa0361 opentelemetry-instrumentation-trilogy (0.68.0) sha256=24b31efdf21a08644ad26038b574f4b0876195b1502f3f64b1065eff3fe0f588 - opentelemetry-registry (0.5.0) sha256=726ca58ada93a23efaa5f7bb81b8ab7a8a1e14602935c9c65dfa2e597a19fb4f - opentelemetry-sdk (1.11.0) sha256=427c6708f4732105ffa46c11afecb91807085c59e92538eaa6cf46b97b1850c6 - opentelemetry-semantic_conventions (1.37.0) sha256=1e2dc5ad649e19ba2fb0fa7c6f9303e5cdd8d3952511415cb07efe28a0f8f4c3 + opentelemetry-registry (0.6.0) sha256=5d3ed32ab9eee0fbdb30d4f0d0bb61ad11a4040b267b475ae815b80a8498a728 + opentelemetry-sdk (1.12.0) sha256=a224abe0c59023d41cb7ac1c634d9d28843907efcd045ed1ae320796c48b864b + opentelemetry-semantic_conventions (1.37.1) sha256=74e87b31ab88c38410c5447d3bc5e692dcc0a73ee2114910c0142fc4546531c1 optimist (3.2.1) sha256=8cf8a0fd69f3aa24ab48885d3a666717c27bc3d9edd6e976e18b9d771e72e34e os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 overviews (1.0.0) - ox (2.14.25) sha256=c938bcfce4d8ff2bd2bdbffe1277222a76c0a6e62078d6854bd4d40f34f2f7db - pagy (43.5.3) sha256=f9d73e690648d484706661dcb815647775cf8330fcc5c6e62ec87b9df431290b + ox (2.14.26) sha256=d4a9cb49fb55751631d469623cfb2e7dad7cea89119c93749c1e31a3fb2f4896 + pagy (43.5.4) sha256=2bdf3fa6b1e0cac5bbafe5d077fb24eb971f72f3194f8c6863a0f3867261ce59 paper_trail (17.0.0) sha256=1c2842061d3874ca7015908e821e2aa14f9b982af2acb2a7974713bf79021c85 parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356 parallel_tests (5.7.0) sha256=3f1762c46ca2c223b8af8ef877217f9d76974e191bfa934f2580b58bcf1d005c @@ -2179,7 +2176,7 @@ CHECKSUMS psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974 public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623 puffing-billy (4.0.4) sha256=87015b0c41e0722b2171a0c5aa8130fd3f58aa1c016a1dc6dc569b2028aa846f - puma (8.0.0) sha256=1681050b8b60fab1d3033255ab58b6aec64cd063e43fc6f8204bcb8bf9364b88 + puma (8.0.1) sha256=7b94e50c07655718c1fb8ae41a11fc06c7d61293208b3aa608ff71a46d3ad37c puma-plugin-statsd (2.8.0) sha256=e515445f93232b6b3571a23b832f93a776d4ce0fc8a5edee798013b82f3488f3 raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882 racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f @@ -2205,14 +2202,14 @@ CHECKSUMS rake-compiler-dock (1.12.0) sha256=f13205c2738f3d2053afcd03491a9e4541b22a59a0bfc53fc8bc883bd8188023 rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe rb-inotify (0.11.1) sha256=a0a700441239b0ff18eb65e3866236cd78613d6b9f78fea1f9ac47a85e47be6e - rb_sys (0.9.127) sha256=e9f90df3bb0577472d26d96127d5b5774b98f44de881e7d36aeefd28d6337847 + rb_sys (0.9.128) sha256=9ab81f4d6d4e1895de18762232362d1264475aa7035756b50441e442130538fd rbtrace (0.5.3) sha256=c432292f305d9ab12fd47d9722e0d5210d983758a951fe6107c36cc955cb923f rbtree3 (0.7.1) sha256=ab60ead728a5491b70df4f4065e180b18dbab5319f817ce1dbf5dd906f26d8ba rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192 recaptcha (5.21.2) sha256=8f7bb6cc9f04c523fff3f38126b7c5f1314c35fcb3691fc8e70d2b464533c12a redcarpet (3.6.1) sha256=d444910e6aa55480c6bcdc0cdb057626e8a32c054c29e793fa642ba2f155f445 redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae - redis-client (0.28.0) sha256=888892f9cd8787a41c0ece00bdf5f556dfff7770326ce40bb2bc11f1bfec824b + redis-client (0.29.0) sha256=0c65bf1f8f6dca22063ddb085c0bb2054feef6f03a84869f4161b18a9a15bea3 regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace @@ -2232,13 +2229,13 @@ CHECKSUMS rspec-retry (0.6.2) sha256=6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858 rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c rspec-wait (1.0.2) sha256=865f921239325d3d26fc10ded4bdd485d8b58bcaaad1a28dd85ed15266b5a912 - rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531 + rubocop (1.86.2) sha256=bb2e97f635eda42c448f2588f4a6ff78f221b8bdfdf65b1e9b07fbd57521b45d rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035 rubocop-capybara (2.23.0) sha256=f9ea1ba3a7561ee8e88cf76fc378ce517ce5327155f305ee7b5c2500e5aee357 rubocop-factory_bot (2.28.0) sha256=4b17fc02124444173317e131759d195b0d762844a71a29fe8139c1105d92f0cb rubocop-openproject (0.4.0) sha256=ce56d9e591f9be5a4d98125b10a73564b0557a5e408f97918f9630fb15ae66ae rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834 - rubocop-rails (2.34.3) sha256=10d37989024865ecda8199f311f3faca990143fbac967de943f88aca11eb9ad2 + rubocop-rails (2.35.1) sha256=4ad270f5fb968ce86ac19f53c4b97354e4f91bc5334177478238674f51568377 rubocop-rspec (3.9.0) sha256=8fa70a3619408237d789aeecfb9beef40576acc855173e60939d63332fdb55e2 rubocop-rspec_rails (2.32.0) sha256=4a0d641c72f6ebb957534f539d9d0a62c47abd8ce0d0aeee1ef4701e892a9100 ruby-duration (3.2.3) sha256=eb3d13b1df85067a015a8fb2ed8f1eec842a3b721e47c9b6fd74d2f356069784 @@ -2255,8 +2252,8 @@ CHECKSUMS sanitize (7.0.0) sha256=269d1b9d7326e69307723af5643ec032ff86ad616e72a3b36d301ac75a273984 scimitar (2.15.0) sha256=700901afab9303b705a8f37644cd733ee3c3819b168d24a14a48dec060b16d63 securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 - selenium-devtools (0.147.0) sha256=8e9262c7c3bfafc7e16b773047912fb952e7bb59c2e6b592ea74a781700d6873 - selenium-webdriver (4.43.0) sha256=a634377b964b701c6ac0a009ce3a08fa34ec1e1e7fe9a6d57e3088d14529a65c + selenium-devtools (0.148.0) sha256=3758305cf150b2abbb074085712808395e4369171f775f5c87fc0b9b1d03cf3f + selenium-webdriver (4.44.0) sha256=6f1df072529af369589c46f0e01132952aabb250cfd683c274d74dc1eb5d8477 semantic (1.6.1) sha256=3cdbb48f59198ebb782a3fdfb87b559e0822a311610db153bae22777a7d0c163 shoulda-context (2.0.0) sha256=7adf45342cd800f507d2a053658cb1cce2884b616b26004d39684b912ea32c34 shoulda-matchers (7.0.1) sha256=b4bfd8744c10e0a36c8ac1a687f921ee7e25ed529e50488d61b79a8688749c77 @@ -2264,7 +2261,7 @@ CHECKSUMS simpleidn (0.2.3) sha256=08ce96f03fa1605286be22651ba0fc9c0b2d6272c9b27a260bc88be05b0d2c29 smart_properties (1.17.0) sha256=f9323f8122e932341756ddec8e0ac9ec6e238408a7661508be99439ca6d6384b spreadsheet (1.3.5) sha256=cd83ea66803d9cae4ac258dfe16cd8c2b85da33eec18a6d7b48fd4a45840ab7d - spring (4.4.2) sha256=22f61bacd8dc8595cedcdc738de46d7fc18be4d7a770986760344c924f485ce7 + spring (4.5.0) sha256=9124b954f6d079cb9aa610d7069cbada7c146c9368599557806a9ff7067b2e1b spring-commands-rspec (1.0.4) sha256=6202e54fa4767452e3641461a83347645af478bf45dddcca9737b43af0dd1a2c spring-commands-rubocop (0.4.0) sha256=3e677a2c8a27ae8a986f04bfb69e66d5d55b017541e8be93bf0dc48a7f5690c1 sprockets (3.7.5) sha256=72c20f256548f8a37fe7db41d96be86c3262fddaf4ebe9d69ec8317394fed383 @@ -2304,8 +2301,8 @@ CHECKSUMS validate_email (0.1.6) sha256=9dfe9016d527b17a8d3a6e95e4dc50a125400eef899d13d4cc2a254393f82ee4 validate_url (1.0.15) sha256=72fe164c0713d63a9970bd6700bea948babbfbdcec392f2342b6704042f57451 vcr (6.4.0) sha256=077ac92cc16efc5904eb90492a18153b5e6ca5398046d8a249a7c96a9ea24ae6 - vernier (1.10.0) sha256=5b1dc57012e08ed23e14f4d2943540140d454aa8434c7c35e7eb97befd4969bf - view_component (4.9.0) sha256=f599f0831ed0148bb19625f914c43cc982746a2252b6ebe8492bd4eea0d7dbaf + vernier (1.10.1) sha256=f2ab2e163b75ac3ef4e3173262d27d27da6219cf04b1b4a7171e4798114275ce + view_component (4.10.0) sha256=9e86960ee7ea4da3d1d07f65b1a66e4a9fb656be5434f1a649eb6fce6ac9baf9 virtus (2.0.0) sha256=8841dae4eb7fcc097320ba5ea516bf1839e5d056c61ee27138aa4bddd6e3d1c2 warden (1.2.9) sha256=46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0 warden-basic_auth (0.2.1) sha256=bfc752e0109c0182c3e69e930284c5e1e81e7b4a354aeb2b5914ead1391f3c6e diff --git a/app/components/_index.sass b/app/components/_index.sass index cd21a4285f8..db76bc961b5 100644 --- a/app/components/_index.sass +++ b/app/components/_index.sass @@ -1,5 +1,6 @@ @import "enterprise_edition/banner_component" @import "filter/filters_component" +@import "open_project/common/border_box_list_component" @import "op_primer/border_box_table_component" @import "op_primer/full_page_prompt_component" @import "op_primer/form_helpers" @@ -11,8 +12,6 @@ @import "open_project/common/inplace_edit_fields/index" @import "open_project/common/submenu_component" @import "open_project/common/main_menu_toggle_component" -@import "open_project/common/work_package_card_list_component" -@import "open_project/common/work_package_card_list_component/header" @import "open_project/common/work_package_card_component" @import "portfolios/details_component" @import "projects/row_component" diff --git a/app/components/open_project/common/work_package_card_list_component.html.erb b/app/components/open_project/common/border_box_list_component.html.erb similarity index 89% rename from app/components/open_project/common/work_package_card_list_component.html.erb rename to app/components/open_project/common/border_box_list_component.html.erb index 7aa5bdb62fe..416d6220be8 100644 --- a/app/components/open_project/common/work_package_card_list_component.html.erb +++ b/app/components/open_project/common/border_box_list_component.html.erb @@ -29,25 +29,25 @@ See COPYRIGHT and LICENSE files for more details. <%= render(Primer::Beta::BorderBox.new(**@system_arguments)) do |border_box| %> <% if header? %> - <% border_box.with_header(id: header_id) do %> + <% border_box.with_header(**header.row_args) do %> <%= header %> <% end %> <% end %> - <% if items.empty? %> - <% border_box.with_row(data: { empty_list_item: true }) do %> - <%= empty_state %> - <% end %> - <% else %> + <% if items.any? %> <% items.each do |item| %> <% border_box.with_row(**item.row_args) do %> - <%= render(item.card) %> + <%= item %> <% end %> <% end %> + <% elsif empty_state? %> + <% border_box.with_row(data: { empty_list_item: true }) do %> + <%= empty_state %> + <% end %> <% end %> <% if footer? %> - <% border_box.with_row(scheme: :neutral) do %> + <% border_box.with_footer(**footer.footer_args) do %> <%= footer %> <% end %> <% end %> diff --git a/app/components/open_project/common/border_box_list_component.rb b/app/components/open_project/common/border_box_list_component.rb new file mode 100644 index 00000000000..777263a6da1 --- /dev/null +++ b/app/components/open_project/common/border_box_list_component.rb @@ -0,0 +1,225 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module Common + # A Primer BorderBox-backed list composition with optional header, items, + # empty state, and footer. + # + # Use this component for compact lists that need consistent OpenProject + # header actions, collapsible behavior, and row rendering. + class BorderBoxListComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + + attr_reader :container, :collapsible, :current_user, :header_id, :footer_id + + alias_method :collapsible?, :collapsible + + # Optional header row. + # + # @!parse + # # Adds the optional header row. + # # + # # @param system_arguments [Hash] forwarded to {Header}. List wiring + # # arguments are supplied internally. + # # @return [ViewComponent::Slot] + # def with_header(**system_arguments, &block) + # end + renders_one :header, ->(**system_arguments) { + system_arguments = system_arguments.except(:id, :list_id) + system_arguments[:id] = header_id + system_arguments[:list_id] = list_id + system_arguments[:interactive] = interactive? + system_arguments[:collapsible] = collapsible? + + Header.new(**system_arguments) + } + + # List row content. + # + # Use: + # + # - `item` for generic row content. + # - `work_package_item` for rows backed by a work package card. + # + # @!parse + # # Adds a generic list row. + # # + # # @param system_arguments [Hash] forwarded to Primer's BorderBox row. + # # @return [ViewComponent::Slot] + # def with_item(**system_arguments, &block) + # end + # + # # Adds a work-package list row. + # # + # # @param work_package [WorkPackage] work package rendered by the row. + # # @param project [Project] project context for the work package. + # # @param params [Hash] request params used by specialized item classes. + # # @param component_klass [Class] item component class to instantiate. + # # Custom classes must accept `work_package:`, `project:`, `params:`, + # # `container:`, `current_user:`, and any forwarded item arguments. + # # @param item_arguments [Hash] forwarded to the item component. + # # @return [ViewComponent::Slot] + # def with_work_package_item( + # work_package:, + # project: work_package.project, + # params: {}, + # component_klass: WorkPackageItem, + # **item_arguments, + # &block + # ) + # end + renders_many :items, types: { + item: { + renders: ->(**system_arguments) { + Item.new(**system_arguments) + }, + as: :item + }, + work_package_item: { + renders: ->( + work_package:, + project: work_package.project, + params: {}, + component_klass: WorkPackageItem, + **item_arguments + ) { + component_klass.new( + work_package:, + project:, + params:, + container:, + current_user:, + **item_arguments + ) + }, + as: :work_package_item + } + } + + # Optional empty-state content rendered when no items are present. + # + # @!parse + # # Adds empty-state content. + # # + # # Interactive lists announce this empty state only when the slot is + # # configured explicitly. + # # + # # @param title [String] empty-state title. + # # @param description [String, nil] optional supporting text. + # # @param icon [Symbol, nil] optional Primer icon. + # # @param system_arguments [Hash] forwarded to `Primer::Beta::Blankslate`. + # # @return [ViewComponent::Slot] + # def with_empty_state(title:, description: nil, icon: nil, **system_arguments) + # end + renders_one :empty_state, ->(title:, description: nil, icon: nil, **system_arguments) { + EmptyState.new(title:, description:, icon:, interactive: interactive?, **system_arguments) + } + + # Optional footer row. + # + # @!parse + # # Adds an optional footer row. + # # + # # @param system_arguments [Hash] forwarded to Primer's BorderBox + # # footer. The `id` is generated internally for collapsible header + # # wiring. + # # @return [ViewComponent::Slot] + # def with_footer(**system_arguments, &block) + # end + renders_one :footer, ->(**system_arguments) { + system_arguments = system_arguments.except(:id) + system_arguments[:id] = footer_id + + Footer.new(**system_arguments) + } + + # @param container [String, Symbol, Class, Object] value passed to + # `dom_target` to derive DOM ids for the list and related controls. + # @param interactive [Boolean] whether dynamic list updates should be + # announced politely to assistive technology. This affects the counter + # and an explicitly configured empty state; it does not create default + # empty-state content for manually composed lists. + # @param collapsible [Boolean] whether the header renders a collapsible + # toggle. Defaults to `false`. + # @param current_user [User] user context passed to work-package items. + # @param system_arguments [Hash] forwarded to `Primer::Beta::BorderBox`. + # Pass `id:` to set the box id; related ids are derived from it. + def initialize( + container:, + interactive: false, + collapsible: false, + current_user: User.current, + **system_arguments + ) + super() + + @container = container + @interactive = interactive + @collapsible = collapsible + @current_user = current_user + @system_arguments = system_arguments.except(:list_id) + + @system_arguments[:id] ||= dom_target(container) + @system_arguments[:list_id] = dom_target(@system_arguments[:id], :list) + @header_id = dom_target(@system_arguments[:id], :header) + @footer_id = dom_target(@system_arguments[:id], :footer) + end + + def before_render + content + configure_header! + end + + def render? + header? || items.any? || empty_state? || footer? + end + + private + + def interactive? + @interactive == true + end + + def configure_header! + return unless header? + + header.resolve_count!(items.size) + return unless collapsible? && footer? + + header.collapsible_id = [list_id, footer_id].compact.join(" ") + end + + def list_id + @system_arguments[:list_id] + end + end + end +end diff --git a/app/components/open_project/common/border_box_list_component.sass b/app/components/open_project/common/border_box_list_component.sass new file mode 100644 index 00000000000..11a8520a032 --- /dev/null +++ b/app/components/open_project/common/border_box_list_component.sass @@ -0,0 +1,22 @@ +//-- copyright +// OpenProject is an open source project management software. +// Copyright (C) the OpenProject GmbH +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License version 3. +// +// See COPYRIGHT and LICENSE files for more details. +//++ + +.op-border-box-list-header + display: grid + grid-template-columns: 1fr minmax(5rem, max-content) auto + grid-template-areas: "collapsible actions menu" + align-items: center + + &--actions, + &--menu + margin-left: var(--stack-gap-normal) + align-self: flex-start + // Unfortunately, the invisible button style bites us here again. + margin-top: -6px diff --git a/app/components/open_project/common/border_box_list_component/empty_state.rb b/app/components/open_project/common/border_box_list_component/empty_state.rb new file mode 100644 index 00000000000..225c08b0d32 --- /dev/null +++ b/app/components/open_project/common/border_box_list_component/empty_state.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module Common + class BorderBoxListComponent + # Empty-state content rendered as a Primer Blankslate. + # + # This component is part of {BorderBoxListComponent} and should not be + # used as a standalone component. + # + class EmptyState < ApplicationComponent + include Primer::AttributesHelper + + # @param title [String] empty-state heading. + # @param description [String, nil] optional supporting text. + # @param icon [Symbol, nil] optional Primer icon. + # @param interactive [Boolean] whether empty-state updates should be + # announced politely to assistive technology. + # @param system_arguments [Hash] forwarded to `Primer::Beta::Blankslate`. + def initialize(title:, description: nil, icon: nil, interactive: false, **system_arguments) + super() + + @title = title + @description = description + @icon = icon + + @system_arguments = system_arguments + return unless interactive + + @system_arguments[:role] ||= "status" + @system_arguments[:aria] = merge_aria( + { aria: { live: "polite" } }, + @system_arguments + ) + end + + def call + blankslate = Primer::Beta::Blankslate.new(**@system_arguments) + blankslate.with_heading(tag: :h4).with_content(@title) + blankslate.with_description_content(@description) if @description + blankslate.with_visual_icon(icon: @icon) if @icon + + render(blankslate) + end + end + end + end +end diff --git a/app/components/open_project/common/border_box_list_component/footer.rb b/app/components/open_project/common/border_box_list_component/footer.rb new file mode 100644 index 00000000000..e4912c09551 --- /dev/null +++ b/app/components/open_project/common/border_box_list_component/footer.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module Common + class BorderBoxListComponent + # Footer row rendered below list items. + # + # This component is part of {BorderBoxListComponent} and should not be + # used as a standalone component. + class Footer < ApplicationComponent + attr_reader :id + + # @param system_arguments [Hash] forwarded to Primer's BorderBox footer. + def initialize(**system_arguments) + super() + + @id = system_arguments[:id] + @system_arguments = system_arguments + end + + # @return [Hash] arguments forwarded to Primer's BorderBox footer. + def footer_args + @system_arguments.deep_dup + end + + def call + content + end + end + end + end +end diff --git a/app/components/open_project/common/work_package_card_list_component/header.rb b/app/components/open_project/common/border_box_list_component/has_menu.rb similarity index 58% rename from app/components/open_project/common/work_package_card_list_component/header.rb rename to app/components/open_project/common/border_box_list_component/has_menu.rb index 3bb6f63f823..60beea91b76 100644 --- a/app/components/open_project/common/work_package_card_list_component/header.rb +++ b/app/components/open_project/common/border_box_list_component/has_menu.rb @@ -23,55 +23,60 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # See COPYRIGHT and LICENSE files for more details. #++ module OpenProject module Common - class WorkPackageCardListComponent - class Header < ApplicationComponent - include OpPrimer::ComponentHelpers + class BorderBoxListComponent + # Adds the standard list action menu slot used by list headers and items. + module HasMenu + extend ActiveSupport::Concern + include Primer::ClassNameHelper - renders_one :description - - renders_many :actions, types: { - button: ->(**system_arguments) do - Primer::Beta::Button.new(**system_arguments) + included do + # @!parse + # # Adds a trailing action menu. + # # + # # @param menu_id [String, nil] id prefix for the Primer action menu. + # # @param button_aria_label [String, nil] accessible label for the + # # menu button. + # # @param system_arguments [Hash] forwarded to + # # `Primer::Alpha::ActionMenu`. + # # @return [ViewComponent::Slot] + # def with_menu(menu_id: nil, button_aria_label: nil, **system_arguments, &block) + # end + renders_one :menu, ->(menu_id: nil, button_aria_label: nil, **system_arguments) do + build_menu(menu_id:, button_aria_label:, **system_arguments) end - } + end - renders_one :menu, ->(menu_id: nil, button_aria_label: nil, **system_arguments) do + private + + def build_menu(menu_id: nil, button_aria_label: nil, **system_arguments) system_arguments[:classes] = class_names( system_arguments[:classes], "hide-when-print" ) menu = Primer::Alpha::ActionMenu.new( - menu_id: menu_id || dom_target(container, :menu), + menu_id: menu_id || default_menu_id, anchor_align: :end, **system_arguments ) menu.with_show_button( scheme: :invisible, icon: :"kebab-horizontal", - "aria-label": button_aria_label || t(".label_actions"), + "aria-label": button_aria_label || I18n.t(:label_actions), tooltip_direction: :se ) menu end - attr_reader :title, :container, :list_id, :collapsed, :count - - def initialize(title:, container:, list_id:, collapsed: false, count: nil) - super() - - @title = title - @container = container - @list_id = list_id - @collapsed = collapsed - @count = count + def default_menu_id + self.class.generate_id end end end diff --git a/app/components/open_project/common/border_box_list_component/header.html.erb b/app/components/open_project/common/border_box_list_component/header.html.erb new file mode 100644 index 00000000000..3ef158da2d2 --- /dev/null +++ b/app/components/open_project/common/border_box_list_component/header.html.erb @@ -0,0 +1,82 @@ +<%# -- copyright +OpenProject is an open source project management software. +Copyright (C) the OpenProject GmbH + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 3. + +OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2010-2013 the ChiliProject Team + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +See COPYRIGHT and LICENSE files for more details. + +++# %> + +<%= grid_layout("op-border-box-list-header", tag: :div) do |grid| %> + <% grid.with_area(:collapsible) do %> + <% if collapsible? %> + <%= + render( + Primer::OpenProject::BorderBox::CollapsibleHeader.new( + collapsible_id:, + collapsed:, + multi_line: true + ) + ) do |collapsible| + %> + <% collapsible.with_title(tag: title_tag) { title } %> + <% if render_count? %> + <% collapsible.with_count(**counter_arguments) %> + <% end %> + <% if description? %> + <% collapsible.with_description do %> + <%= description %> + <% end %> + <% end %> + <% end %> + <% else %> + <%= render(Primer::BaseComponent.new(tag: :div, classes: "CollapsibleHeader CollapsibleHeader--multi-line")) do %> + <%= render(Primer::BaseComponent.new(tag: :div, classes: "CollapsibleHeader-title-line")) do %> + <%= render(Primer::Beta::Truncate.new(tag: title_tag, classes: "CollapsibleHeader-title Box-title")) { title } %> + <% if render_count? %> + <%= render(Primer::Beta::Counter.new(**counter_arguments, classes: class_names(counter_arguments[:classes], "CollapsibleHeader-count"))) %> + <% end %> + <% end %> + <% if description? %> + <%= render(Primer::Beta::Text.new(color: :subtle, trim: true, classes: "CollapsibleHeader-description")) do %> + <%= description %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %> + + <% if actions? %> + <% grid.with_area(:actions) do %> + <% actions.each do |action| %> + <%= action %> + <% end %> + <% end %> + <% end %> + + <% if menu? %> + <% grid.with_area(:menu) do %> + <%= menu %> + <% end %> + <% end %> +<% end %> diff --git a/app/components/open_project/common/border_box_list_component/header.rb b/app/components/open_project/common/border_box_list_component/header.rb new file mode 100644 index 00000000000..db07605194b --- /dev/null +++ b/app/components/open_project/common/border_box_list_component/header.rb @@ -0,0 +1,183 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpenProject + module Common + class BorderBoxListComponent + # Structured header for {BorderBoxListComponent}. + # + # This component is part of {BorderBoxListComponent} and should not be + # used as a standalone component. + # + # The header renders through `Primer::Beta::BorderBox#with_header` and + # wraps the supplied title, count, description, actions, and menu in an + # `Primer::OpenProject::BorderBox::CollapsibleHeader`. + class Header < ApplicationComponent + include OpPrimer::ComponentHelpers + include Primer::AttributesHelper + include HasMenu + + DEFAULT_ACTION_SCHEME = :default + + DEFAULT_COUNT_ARGUMENTS = { + scheme: :primary, + round: true, + limit: 1_000, + hide_if_zero: true + }.freeze + + # @!parse + # # Adds secondary content below the header title. + # # + # # @return [ViewComponent::Slot] + # def with_description(&block) + # end + renders_one :description + + # @!parse + # # Adds a button to the header actions area. + # # + # # @param system_arguments [Hash] forwarded to `Primer::Beta::Button`. + # # @return [ViewComponent::Slot] + # def with_action_button(**system_arguments, &block) + # end + renders_many :actions, types: { + button: ->(scheme: DEFAULT_ACTION_SCHEME, **system_arguments) do + Primer::Beta::Button.new(scheme:, **system_arguments) + end + } + + attr_reader :title, + :count, + :count_label, + :count_arguments, + :title_tag, + :list_id, + :interactive, + :collapsed, + :collapsible + + attr_writer :collapsible_id + + # @param title [String] header title. + # @param count [Integer, Boolean, nil] count badge behavior. Pass + # `nil` or `false` to hide it, `true` to infer the rendered item + # count, or an integer to render an explicit value. + # @param count_label [String, nil] accessible label for the counter + # badge. Defaults to `I18n.t(:label_x_items, count:)` when a count + # is rendered. Pass an explicit string to override. + # @param count_arguments [Hash] forwarded to `Primer::Beta::Counter`. + # Values are merged over the default counter arguments. + # @param title_tag [Symbol] tag used for the title heading. + # @param list_id [String, nil] id of the collapsible list body. + # @param interactive [Boolean] whether counter updates should be + # announced politely to assistive technology. + # @param collapsed [Boolean] whether the collapsible header starts closed. + # @param collapsible [Boolean] whether the header renders a collapsible + # toggle. Defaults to `false`. Pass `true` to render a header + # with a toggle button. + # @param system_arguments [Hash] forwarded to `Primer::Beta::BorderBox#with_header`. + def initialize( + title:, + count: nil, + count_label: nil, + count_arguments: {}, + title_tag: :h4, + list_id: nil, + interactive: false, + collapsed: false, + collapsible: false, + **system_arguments + ) + super() + + @title = title + @count = count + @count_label = count_label + @count_arguments = count_arguments + @title_tag = title_tag + @list_id = list_id + @interactive = interactive + @collapsible_id = list_id + @collapsed = collapsed + @collapsible = collapsible + @system_arguments = system_arguments + end + + # @return [Boolean] whether a collapsible toggle should be rendered. + def collapsible? + collapsible + end + + # Resolves inferred counts after the list slots have been captured. + # + # @param item_count [Integer] number of rendered item slots. + # @return [void] + def resolve_count!(item_count) + @count = item_count if count == true + @count_label ||= I18n.t(:label_x_items, count: @count) if render_count? + end + + # @return [Hash] arguments forwarded to `Primer::Beta::BorderBox#with_header`. + def row_args + @system_arguments.deep_dup + end + + # @return [Boolean] whether a counter should be rendered. + def render_count? + !count.nil? && count != false + end + + # @return [Hash] merged arguments forwarded to `Primer::Beta::Counter`. + def counter_arguments + merged = DEFAULT_COUNT_ARGUMENTS.merge(count_arguments).merge(count:) + default_aria = { label: count_label } + default_aria[:live] = "polite" if interactive + merged[:aria] = merge_aria( + { aria: default_aria }, + merged + ) + merged + end + + # @return [String, nil] ids controlled by the collapsible header. + def collapsible_id + @collapsible_id.presence + end + + private + + def default_menu_id + list_id ? "#{list_id}_menu" : super + end + end + end + end +end diff --git a/app/components/open_project/common/work_package_card_list_component/content_item.rb b/app/components/open_project/common/border_box_list_component/item.rb similarity index 82% rename from app/components/open_project/common/work_package_card_list_component/content_item.rb rename to app/components/open_project/common/border_box_list_component/item.rb index a6b27d116d8..dcd8079c7c9 100644 --- a/app/components/open_project/common/work_package_card_list_component/content_item.rb +++ b/app/components/open_project/common/border_box_list_component/item.rb @@ -23,32 +23,28 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # See COPYRIGHT and LICENSE files for more details. #++ module OpenProject module Common - class WorkPackageCardListComponent - # Item bridge for caller-provided content. - class ContentItem < ApplicationComponent + class BorderBoxListComponent + # Generic BorderBox list row that renders the slot content directly. + class Item < ApplicationComponent + # @param system_arguments [Hash] forwarded to Primer's BorderBox row. def initialize(**system_arguments) super() @system_arguments = system_arguments end + # @return [Hash] arguments forwarded to Primer's BorderBox row. def row_args @system_arguments.deep_dup end - def card - self - end - - def empty_item? = false - def call content end diff --git a/app/components/open_project/common/work_package_card_list_component/item.rb b/app/components/open_project/common/border_box_list_component/work_package_item.rb similarity index 61% rename from app/components/open_project/common/work_package_card_list_component/item.rb rename to app/components/open_project/common/border_box_list_component/work_package_item.rb index ca5c6a66131..32537fd9ca2 100644 --- a/app/components/open_project/common/work_package_card_list_component/item.rb +++ b/app/components/open_project/common/border_box_list_component/work_package_item.rb @@ -23,18 +23,19 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # See COPYRIGHT and LICENSE files for more details. #++ module OpenProject module Common - class WorkPackageCardListComponent - # Internal row bridge between the card list and the visual card. It owns - # the surrounding BorderBox row arguments while `WorkPackageCardComponent` - # renders the card body. - class Item < ApplicationComponent + class BorderBoxListComponent + # BorderBox list row that renders a work package card. + # + # Specialized rows can subclass this component and override `build_card` + # to provide a different card component while keeping row behavior. + class WorkPackageItem < ApplicationComponent include ActionView::RecordIdentifier include Primer::ClassNameHelper include Primer::AttributesHelper @@ -45,8 +46,16 @@ module OpenProject :params, :current_user - delegate :with_metric, to: :card + # Delegates card slots so callers can configure the rendered card from + # `with_work_package_item`. + delegate :with_metric, :with_menu, to: :card + # @param work_package [WorkPackage] work package rendered by the card. + # @param project [Project] project context for row behavior. + # @param container [String, Array] parent list container id seed. + # @param params [Hash] request params used by specialized item classes. + # @param current_user [User] user context for specialized item classes. + # @param system_arguments [Hash] forwarded to Primer's BorderBox row. def initialize( work_package:, project:, @@ -65,11 +74,16 @@ module OpenProject @system_arguments = system_arguments end + # @return [Hash] arguments forwarded to Primer's BorderBox row. def row_args row_arguments = @system_arguments.deep_dup row_arguments[:id] ||= dom_id(work_package) row_arguments[:tabindex] ||= 0 - row_arguments[:classes] = class_names(row_classes, row_arguments[:classes]) + row_arguments[:test_selector] ||= "work-package-#{work_package.id}" + row_arguments[:classes] = class_names( + row_classes, + row_arguments[:classes] + ) row_arguments[:data] = merge_data( { data: row_data }, row_arguments @@ -77,16 +91,28 @@ module OpenProject row_arguments end - def card - @card ||= WorkPackageCardComponent.new(work_package:) + def before_render + content end - def render? = false + def call + render(card) + end - def empty_item? = false + # @return [ApplicationComponent] card component rendered inside the row. + def card + @card ||= build_card + end private + # Override in subclasses to render a specialized work-package card. + # + # @return [ApplicationComponent] + def build_card + WorkPackageCardComponent.new(work_package:) + end + def row_classes class_names( "Box-row--hover-blue", @@ -97,11 +123,7 @@ module OpenProject end def row_data - data = { - test_selector: "work-package-#{work_package.id}" - } - - draggable? ? data.merge(draggable_data) : data + draggable? ? draggable_data : {} end def draggable? diff --git a/app/components/open_project/common/work_package_card_list_component.rb b/app/components/open_project/common/work_package_card_list_component.rb deleted file mode 100644 index 0f29b85c268..00000000000 --- a/app/components/open_project/common/work_package_card_list_component.rb +++ /dev/null @@ -1,309 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -module OpenProject - module Common - class WorkPackageCardListComponent < ApplicationComponent - include Primer::AttributesHelper - include OpPrimer::ComponentHelpers - - # Renders a `Header` above the card list with the title, count badge, and - # consumer-provided actions/menu/description. - # - # @param title [String] heading text rendered inside the collapsible header. - # @param count [Integer, NilClass] optional count badge displayed alongside - # the title; hidden when zero or nil. - renders_one :header, ->(title:, count: nil) { - Header.new(title:, count:, container:, list_id:, collapsed: folded?) - } - - # Renders a `Primer::Beta::Blankslate` when no items are produced — that - # is, when `items.empty?` after slot resolution and automatic item builds. - # The slot is required unless the caller provides manual items, and is - # silently ignored whenever `items` is non-empty. - # - # @param title [String] blankslate heading. - # @param description [String, NilClass] optional secondary text. - # @param icon [Symbol, NilClass] optional Octicon name. - # @param system_arguments [Hash] forwarded to `Primer::Beta::Blankslate`. - renders_one :empty_state, ->(title:, description: nil, icon: nil, **system_arguments) { - system_arguments[:role] = "status" - system_arguments[:aria] = merge_aria( - system_arguments, - aria: { live: "polite" } - ) - - blankslate = Primer::Beta::Blankslate.new(**system_arguments) - blankslate.with_heading(tag: :h4).with_content(title) - blankslate.with_description_content(description) if description - blankslate.with_visual_icon(icon:) if icon - blankslate - } - - # @!parse - # # Adds a work package item row to the list. When at least one item - # # is added manually, the list does not build rows from - # # `work_packages:`. - # # - # # @param work_package [WorkPackage] the work package rendered in the row. - # # @param component_klass [Class] row bridge class used instead of the - # # default item class. Defaults to the list's configured - # # `item_component_klass`. It must accept the arguments documented on - # # `#build_item`, expose `#row_args` with valid - # # `Primer::Beta::BorderBox#with_row` keyword arguments, and expose - # # `#card` returning a renderable object. - # # @param system_arguments [Hash] forwarded to the item class. - # def with_work_package_item( - # work_package:, - # component_klass: Item, - # **system_arguments, - # &block - # ) - # end - - # @!parse - # # Adds a custom empty item row to the list. This can be used instead of - # # the `empty_state` slot when the caller owns item iteration. It cannot - # # be combined with `work_packages:`, `with_work_package_item`, or - # # `with_item`. - # # - # # @param system_arguments [Hash] forwarded to - # # `Primer::Beta::BorderBox#with_row`. - # def with_empty_item(**system_arguments, &block) - # end - - # @!parse - # # Adds a generic item to the list. When at least one item is added - # # manually, the list does not build rows from `work_packages:`. - # # - # # @param system_arguments [Hash] forwarded to - # # `Primer::Beta::BorderBox#with_row`. - # def with_item(**system_arguments, &block) - # end - renders_many :items, types: { - work_package_item: { - renders: lambda { |work_package:, **system_arguments, &block| - build_item(work_package:, **system_arguments).tap do |item| - capture(item, &block) if block - end - }, - as: :work_package_item - }, - empty_item: { - renders: lambda { |**system_arguments, &block| - build_content_item(EmptyItem, **system_arguments, &block) - }, - as: :empty_item - }, - item: { - renders: lambda { |**system_arguments, &block| - build_content_item(ContentItem, **system_arguments, &block) - }, - as: :item - } - } - - # Renders a free-form footer row below the card list. - renders_one :footer - - attr_reader :work_packages, - :project, - :container, - :drag_and_drop, - :item_component_klass, - :params, - :current_user - - # @param project [Project] the project this card list is rendered in. May - # differ from individual `work_package.project` values when sprints or - # buckets are shared across projects. - # @param container [Symbol, String, Class, ApplicationRecord] drives the - # list DOM id and related ids via `dom_target`. - # @param work_packages [Enumerable] the work packages to render - # as cards. - # @param drag_and_drop [Hash, NilClass] optional generic drag-and-drop - # target data. Requires `:target_id` and `:allowed_drag_type` when set. - # @param item_component_klass [Class] item class used for automatically - # built work package items. - # @param params [Hash] optional URL params passed to work package items - # when deriving row arguments. - # @param current_user [User] passed through to each item for permission - # checks; defaults to `User.current`. - # @param system_arguments [Hash] forwarded to the underlying - # `Primer::Beta::BorderBox`. - def initialize( - project:, - container:, - work_packages: [], - drag_and_drop: nil, - item_component_klass: Item, - params: {}, - current_user: User.current, - **system_arguments - ) - super() - - @work_packages = work_packages - @project = project - @container = container - @drag_and_drop = drag_and_drop - @item_component_klass = item_component_klass - @params = params - @current_user = current_user - @automatic_items = false - - @system_arguments = system_arguments - @system_arguments[:id] = container_id - @system_arguments[:list_id] = list_id - @system_arguments[:padding] = :condensed - merge_drag_and_drop_data! if drag_and_drop - end - - def before_render - # Content must be loaded before mode validation and automatic item builds - # so slot calls have already populated `items`. - content - validate_item_mode! - build_automatic_items if build_automatic_items? - validate_empty_state! - end - - # Builds a new work package item without adding it to the list. Use this - # instead of the `#with_work_package_item` slot when rendering additional - # items outside this list, such as in a separately-loaded page. - # - # @param work_package [WorkPackage] the work package rendered in the row. - # @param component_klass [Class] item class used instead of the configured - # default item class. It must accept `work_package:`, `project:`, - # `container:`, `params:`, `current_user:`, and `**system_arguments`. - # @param system_arguments [Hash] forwarded to the item class. - def build_item( - work_package:, - component_klass: item_component_klass, - **system_arguments - ) - component_klass.new( - work_package:, - project:, - container:, - params:, - current_user:, - **system_arguments - ) - end - - private - - def folded? - current_user.pref[:backlogs_versions_default_fold_state] == "closed" - end - - def build_automatic_items? - non_empty_items.empty? && work_packages.any? - end - - def build_automatic_items - @automatic_items = true - - work_packages.each do |work_package| - with_work_package_item(work_package:) - end - end - - def build_content_item(item_class, **system_arguments, &block) - item_class.new(**system_arguments).tap do |item| - item.with_content(capture(&block)) if block - end - end - - def automatic_items? - @automatic_items - end - - def validate_item_mode! - return unless empty_items.any? - - if work_packages.any? - raise ArgumentError, "empty_item cannot be combined with work_packages" - end - - if non_empty_items.any? - raise ArgumentError, "empty_item cannot be combined with other items" - end - end - - def validate_empty_state! - return unless items.empty? && !empty_state? - - raise ArgumentError, "empty_state slot is required when no work package items are rendered" - end - - def container_id - dom_target(container) - end - - def list_id - dom_target(container, :list) - end - - def header_id - dom_target(container, :header) - end - - def empty_items - items.select { |item| item.respond_to?(:empty_item?) && item.empty_item? } - end - - def non_empty_items - items - empty_items - end - - def merge_drag_and_drop_data! - @system_arguments[:data] = merge_data( - { - data: drag_and_drop_data - }, - @system_arguments - ) - end - - def drag_and_drop_data - { - # Existing callers share one mirror container target on the page until - # parent-specific DnD handling is extracted in follow-up work. - generic_drag_and_drop_target: "container", - target_container_accessor: ":scope > ul", - target_id: drag_and_drop.fetch(:target_id), - target_allowed_drag_type: drag_and_drop.fetch(:allowed_drag_type) - } - end - end - end -end diff --git a/app/components/open_project/common/work_package_card_list_component/header.html.erb b/app/components/open_project/common/work_package_card_list_component/header.html.erb deleted file mode 100644 index d57862a113e..00000000000 --- a/app/components/open_project/common/work_package_card_list_component/header.html.erb +++ /dev/null @@ -1,74 +0,0 @@ -<%# -- copyright -OpenProject is an open source project management software. -Copyright (C) the OpenProject GmbH - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License version 3. - -OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -Copyright (C) 2006-2013 Jean-Philippe Lang -Copyright (C) 2010-2013 the ChiliProject Team - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -See COPYRIGHT and LICENSE files for more details. - -++# %> - -<%= grid_layout("op-work-package-card-list-header", tag: :div) do |grid| %> - <% grid.with_area(:collapsible) do %> - <%= - render( - Primer::OpenProject::BorderBox::CollapsibleHeader.new( - collapsible_id: list_id, - collapsed:, - multi_line: true - ) - ) do |collapsible| - %> - <% collapsible.with_title(tag: :h4) { title } %> - <% if count %> - <% collapsible.with_count( - scheme: :primary, - count: count, - round: true, - limit: 1_000, - hide_if_zero: true, - aria: { - label: t(".label_work_package_count", count: count), - live: "polite" - } - ) %> - <% end %> - <% if description? %> - <% collapsible.with_description do %> - <%= description %> - <% end %> - <% end %> - <% end %> - <% end %> - - <% if actions? %> - <% grid.with_area(:actions) do %> - <% actions.each do |action| %> - <%= action %> - <% end %> - <% end %> - <% end %> - - <% grid.with_area(:menu) do %> - <%= menu %> - <% end %> -<% end %> diff --git a/app/components/projects/row_component.rb b/app/components/projects/row_component.rb index e09c722ee59..8cd0697a812 100644 --- a/app/components/projects/row_component.rb +++ b/app/components/projects/row_component.rb @@ -80,26 +80,10 @@ module Projects send(column.attribute) end - def custom_field_column(column) # rubocop:disable Metrics/AbcSize + def custom_field_column(column) return nil unless user_can_view_project_attributes? - cf = column.custom_field - custom_value = project.formatted_custom_value_for(cf) - - if cf.field_format == "text" && custom_value.present? - render OpenProject::Common::AttributeComponent.new( - "dialog-#{project.id}-cf-#{cf.id}", - cf.name, - custom_value, - format: false # already formatted - ) - elsif custom_value.is_a?(Array) - safe_join(Array(custom_value).compact_blank, ", ") - elsif cf.calculated_value? - render_calculated_value(cf, custom_value) - else - custom_value - end + super end def render_calculated_value(custom_field, custom_value) @@ -451,6 +435,25 @@ module Projects User.current.allowed_in_project?(:view_project_phases, project) end + def custom_field_column_subject + project + end + + def format_custom_field_value(cf, custom_value) + if cf.field_format == "text" && custom_value.present? + render OpenProject::Common::AttributeComponent.new( + "dialog-#{project.id}-cf-#{cf.id}", + cf.name, + custom_value, + format: false + ) + elsif cf.calculated_value? + render_calculated_value(cf, custom_value) + else + super + end + end + def custom_field_column?(column) column.is_a?(::Queries::Projects::Selects::CustomField) end diff --git a/app/components/row_component.rb b/app/components/row_component.rb index 43f3fcd71e5..00ae80c0993 100644 --- a/app/components/row_component.rb +++ b/app/components/row_component.rb @@ -45,9 +45,22 @@ class RowComponent < ApplicationComponent end def column_value(column) + return custom_field_column(column) if custom_field_column?(column) + send(column) end + def custom_field_column?(column) + column.is_a?(Queries::Selects::Shared::CustomFieldSelect) + end + + def custom_field_column(column) + cf = column.custom_field + return "" unless cf + + format_custom_field_value(cf, custom_field_column_subject.formatted_custom_value_for(cf)) + end + def column_css_class(column) column_css_classes[column] end @@ -81,4 +94,18 @@ class RowComponent < ApplicationComponent helpers.op_icon "icon icon-checkmark" end end + + private + + def custom_field_column_subject + model + end + + def format_custom_field_value(_cf, custom_value) + if custom_value.is_a?(Array) + safe_join(Array(custom_value).compact_blank, ", ") + else + custom_value.to_s + end + end end diff --git a/app/components/table_component.html.erb b/app/components/table_component.html.erb index 9e1c00c35b1..17e2a3410b9 100644 --- a/app/components/table_component.html.erb +++ b/app/components/table_component.html.erb @@ -26,55 +26,56 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. See COPYRIGHT and LICENSE files for more details. ++#%> - -
-
- <%= render(Primer::BaseComponent.new(**@system_arguments)) do %> - - <% headers.each do |_name, _options| %> - - <% end %> - - - - - <% headers.each do |name, options| %> - <% if sortable_column?(name) %> - <%= helpers.sort_header_tag(name, **options) %> - <% else %> - -
-
- - <%= options[:caption] %> - -
-
- - <% end %> +<%= component_wrapper do %> +
+
+ <%= render(Primer::BaseComponent.new(**@system_arguments)) do %> + + <% headers.each do |_name, _options| %> + <% end %> - - <%# last column for buttons %> -
- - - - - <% if rows.empty? %> - - <%= empty_row_message %> - - <% end %> - <%= render_collection rows %> - - <% end %> - <% if inline_create_link %> -
- <%= inline_create_link %> -
- <% end %> + + + + + <% headers.each do |name, options| %> + <% if sortable_column?(name) %> + <%= helpers.sort_header_tag(name, **options) %> + <% else %> + +
+
+ + <%= options[:caption] %> + +
+
+ + <% end %> + <% end %> + + <%# last column for buttons %> +
+ + + + + <% if rows.empty? %> + + <%= empty_row_message %> + + <% end %> + <%= render_collection rows %> + + <% end %> + <% if inline_create_link %> +
+ <%= inline_create_link %> +
+ <% end %> +
-
-<% if paginated? %> - <%= helpers.pagination_links_full rows %> + <% if paginated? %> + <%= helpers.pagination_links_full rows %> + <% end %> <% end %> diff --git a/app/components/table_component.rb b/app/components/table_component.rb index efe7f01ebd8..470bafc59c6 100644 --- a/app/components/table_component.rb +++ b/app/components/table_component.rb @@ -32,6 +32,7 @@ # Abstract view component. Subclass this for a concrete table. class TableComponent < ApplicationComponent include Primer::AttributesHelper + include OpTurbo::Streamable def initialize(rows: [], table_arguments: {}, **) super(rows, **) diff --git a/app/components/users/configure_view_modal_component.html.erb b/app/components/users/configure_view_modal_component.html.erb new file mode 100644 index 00000000000..068e3cee7eb --- /dev/null +++ b/app/components/users/configure_view_modal_component.html.erb @@ -0,0 +1,27 @@ +<%= render(Primer::Alpha::Dialog.new(title: t(:"queries.configure_view.heading"), size: :large, id: MODAL_ID, style: "min-height: 360px")) do |d| %> + <% d.with_header(variant: :large) %> + <%= render(Primer::Alpha::Dialog::Body.new) do %> + <%= primer_form_with(url: users_path, id: QUERY_FORM_ID, data: { "turbo-stream" => false }, method: :get) do %> + <% { filters: params[:filters], sortBy: params[:sortBy] }.each do |name, value| %> + <%= hidden_field_tag name, value if value.present? %> + <% end %> + <%= helpers.angular_component_tag "opce-draggable-autocompleter", + inputs: { + options: selectable_columns, + selected: selected_columns, + name: COLUMN_HTML_NAME, + id: COLUMN_HTML_ID, + dragAreaName: "#{COLUMN_HTML_ID}_dragarea", + formControlId: "#{COLUMN_HTML_ID}_autocompleter", + inputLabel: t(:"queries.configure_view.columns.input_label"), + inputPlaceholder: t(:"queries.configure_view.columns.input_placeholder"), + dragAreaLabel: t(:"queries.configure_view.columns.drag_area_label"), + appendToComponent: true + } %> + <% end %> + <% end %> + <%= render(Primer::Alpha::Dialog::Footer.new) do %> + <%= render(Primer::Beta::Button.new(data: { "close-dialog-id": MODAL_ID })) { t(:button_cancel) } %> + <%= render(Primer::Beta::Button.new(scheme: :primary, type: :submit, data: { "test-selector": "#{MODAL_ID}-submit" }, form: QUERY_FORM_ID)) { t(:button_apply) } %> + <% end %> +<% end %> diff --git a/app/components/users/configure_view_modal_component.rb b/app/components/users/configure_view_modal_component.rb new file mode 100644 index 00000000000..7800523a75a --- /dev/null +++ b/app/components/users/configure_view_modal_component.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module Users + class ConfigureViewModalComponent < ApplicationComponent + include OpTurbo::Streamable + + MODAL_ID = "op-user-list-configure-dialog" + QUERY_FORM_ID = "op-user-list-configure-query-form" + COLUMN_HTML_NAME = "columns" + COLUMN_HTML_ID = "columns-select" + + options :query + + def selectable_columns + @selectable_columns ||= UserQuery.new + .available_selects + .reject { |c| text_format_custom_field?(c) } + .sort_by(&:caption) + .map { |c| { id: c.attribute, name: c.caption } } + end + + def selected_columns + @selected_columns ||= query.selects + .reject { |c| text_format_custom_field?(c) } + .map { |c| { id: c.attribute, name: c.caption } } + end + + private + + def text_format_custom_field?(select) + select.is_a?(Queries::Users::Selects::CustomField) && + select.custom_field&.field_format == "text" + end + end +end diff --git a/app/components/users/index_page_header_component.html.erb b/app/components/users/index_page_header_component.html.erb index 10c654cf975..bacbcba5285 100644 --- a/app/components/users/index_page_header_component.html.erb +++ b/app/components/users/index_page_header_component.html.erb @@ -1,17 +1,35 @@ -<%= render(Primer::OpenProject::PageHeader.new) do |header| %> - <% header.with_title { t(:label_user_plural) } %> - <% header.with_breadcrumbs(breadcrumb_items) %> - <% header.with_description do %> - <%= t(:label_enterprise_active_users, current: OpenProject::Enterprise.active_user_count, limit: user_limit) %> -   - <%= render( - Primer::Beta::Button.new( - scheme: :primary, - size: :small, - tag: :a, - href: OpenProject::Enterprise.upgrade_path, - title: t(:title_enterprise_upgrade) - ) - ) { t(:button_upgrade) } %> - <% end if user_limit %> +<%= component_wrapper do %> + <%= render(Primer::OpenProject::PageHeader.new) do |header| %> + <% header.with_title { t(:label_user_plural) } %> + <% header.with_breadcrumbs(breadcrumb_items) %> + <% header.with_description do %> + <%= t(:label_enterprise_active_users, current: OpenProject::Enterprise.active_user_count, limit: user_limit) %> +   + <%= render( + Primer::Beta::Button.new( + scheme: :primary, + size: :small, + tag: :a, + href: OpenProject::Enterprise.upgrade_path, + title: t(:title_enterprise_upgrade) + ) + ) { t(:button_upgrade) } %> + <% end if user_limit %> + <% header.with_action_menu( + menu_arguments: { anchor_align: :end }, + button_arguments: { + icon: "kebab-horizontal", + "aria-label": t(:label_more) + } + ) do |menu| %> + <% menu.with_item( + tag: :a, + label: t(:"queries.configure_view.heading"), + href: configure_view_modal_path, + content_arguments: { data: { controller: "async-dialog" }, rel: "nofollow" } + ) do |item| %> + <% item.with_leading_visual_icon(icon: :gear) %> + <% end %> + <% end %> + <% end %> <% end %> diff --git a/app/components/users/index_page_header_component.rb b/app/components/users/index_page_header_component.rb index 243af54b74f..49fd0435999 100644 --- a/app/components/users/index_page_header_component.rb +++ b/app/components/users/index_page_header_component.rb @@ -29,9 +29,12 @@ # ++ class Users::IndexPageHeaderComponent < ApplicationComponent + include OpTurbo::Streamable include OpPrimer::ComponentHelpers include ApplicationHelper + options :query + delegate :user_limit, to: :"OpenProject::Enterprise" def breadcrumb_items @@ -39,4 +42,23 @@ class Users::IndexPageHeaderComponent < ApplicationComponent { href: admin_settings_users_path, text: t(:label_user_and_permission) }, t(:label_user_plural)] end + + def configure_view_modal_path + helpers.configure_view_modal_users_path(query_params) + end + + private + + def query_params + { filters: helpers.params[:filters], + sortBy: helpers.params[:sortBy], + columns: current_columns }.compact_blank + end + + def current_columns + return if query.nil? + + cols = query.selects.map { |s| s.attribute.to_s }.join(" ") + cols.presence + end end diff --git a/app/components/users/index_sub_header_component.html.erb b/app/components/users/index_sub_header_component.html.erb index acad7b63cc4..87e32613d84 100644 --- a/app/components/users/index_sub_header_component.html.erb +++ b/app/components/users/index_sub_header_component.html.erb @@ -1,5 +1,5 @@ <%= - render(Primer::OpenProject::SubHeader.new) do |subheader| + render(Primer::OpenProject::SubHeader.new(data: sub_header_data_attributes)) do |subheader| subheader.with_action_button( scheme: :primary, leading_icon: :plus, @@ -10,8 +10,21 @@ t("activerecord.models.user") end + subheader.with_filter_input( + name: "any_name_attribute", + label: t(:label_search), + value: filter_input_value, + placeholder: t(:label_search), + clear_button_id: clear_button_id, + data: filter_input_data_attributes + ) + + subheader.with_filter_component do + render Users::UserFilterButtonComponent.new(query: @query) + end + subheader.with_bottom_pane_component do - render Users::UserFilterComponent.new(@params, groups: @groups, status: @status) + render Users::UserFiltersComponent.new(query: @query, initially_expanded: filters_expanded?) end end %> diff --git a/app/components/users/index_sub_header_component.rb b/app/components/users/index_sub_header_component.rb index 8f4ed16d22c..700a233ee95 100644 --- a/app/components/users/index_sub_header_component.rb +++ b/app/components/users/index_sub_header_component.rb @@ -32,11 +32,39 @@ module Users class IndexSubHeaderComponent < ApplicationComponent include ApplicationHelper - def initialize(groups:, status:, params:) + def initialize(query:) super - @groups = groups - @status = status - @params = params + @query = query + end + + def filter_input_value + @query.find_active_filter(:any_name_attribute)&.values&.first + end + + def sub_header_data_attributes + { + controller: "filter--filters-form", + "filter--filters-form-perform-turbo-requests-value": true, + "filter--filters-form-clear-button-id-value": clear_button_id, + "filter--filters-form-display-filters-value": filters_expanded? + } + end + + def filter_input_data_attributes + { + "filter-name": "any_name_attribute", + "filter-type": "string", + "filter-operator": "~", + "filter--filters-form-target": "simpleFilter filterValueContainer simpleValue" + } + end + + def clear_button_id + "user-filters-form-clear-button" + end + + def filters_expanded? + params[:filters].present? end end end diff --git a/app/components/users/row_component.rb b/app/components/users/row_component.rb index b25130224af..998c7c9774d 100644 --- a/app/components/users/row_component.rb +++ b/app/components/users/row_component.rb @@ -98,14 +98,25 @@ module Users user.admin? && !table.current_user.admin? end + def column_value(column) + return custom_field_column(column) if custom_field_column?(column) + + send(column.respond_to?(:attribute) ? column.attribute : column) + end + def column_css_class(column) - if column == :mail - "email" - elsif column == :login - "username" - else - super + attr = column.respond_to?(:attribute) ? column.attribute : column + case attr + when :mail then "email" + when :login then "username" + else attr.to_s end end + + private + + def custom_field_column_subject + user + end end end diff --git a/app/components/users/table_component.rb b/app/components/users/table_component.rb index 06590dbd44f..fcf8cf36016 100644 --- a/app/components/users/table_component.rb +++ b/app/components/users/table_component.rb @@ -33,21 +33,35 @@ module Users columns :login, :firstname, :lastname, :mail, :admin, :created_at, :last_login_on options :current_user + def before_render + @user_query = model if model.is_a?(Queries::BaseQuery) + super + end + + def columns + @columns ||= if @user_query&.selects&.any? + @user_query.selects + else + super + end + end + def initial_sort %i[id asc] end def headers - columns.map do |name| - [name.to_s, header_options(name)] + columns.map do |column| + key = column.respond_to?(:attribute) ? column.attribute.to_s : column.to_s + [key, header_options(column)] end end - def header_options(name) - options = { caption: User.human_attribute_name(name) } - - options[:default_order] = "desc" if desc_by_default.include? name - + def header_options(column) + attr = column.respond_to?(:attribute) ? column.attribute : column + caption = column.respond_to?(:caption) ? column.caption : User.human_attribute_name(attr) + options = { caption: } + options[:default_order] = "desc" if desc_by_default.include?(attr) options end diff --git a/app/components/users/user_filter_button_component.rb b/app/components/users/user_filter_button_component.rb new file mode 100644 index 00000000000..a055d4402a1 --- /dev/null +++ b/app/components/users/user_filter_button_component.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module Users + class UserFilterButtonComponent < Filter::FilterButtonComponent + HIDDEN_FILTERS = [ + Queries::Users::Filters::AnyNameAttributeFilter, + Queries::Users::Filters::BlockedFilter + ].freeze + + private + + def filters_count + query.filters.count { |f| HIDDEN_FILTERS.none? { |klass| f.is_a?(klass) } } + end + end +end diff --git a/app/components/users/user_filters_component.rb b/app/components/users/user_filters_component.rb new file mode 100644 index 00000000000..912b49e9f60 --- /dev/null +++ b/app/components/users/user_filters_component.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module Users + class UserFiltersComponent < Filter::FilterComponent + def turbo_requests? = true + + def allowed_filters + super + .grep_v(Queries::Users::Filters::AnyNameAttributeFilter) + .grep_v(Queries::Users::Filters::BlockedFilter) + .sort_by(&:human_name) + end + + protected + + def additional_filter_attributes(filter) + case filter + when Queries::Users::Filters::GroupFilter + { + autocomplete_options: { + component: "opce-group-autocompleter", + resource: "groups" + } + } + else + super + end + end + end +end diff --git a/app/components/work_package_types/form_configuration/blankslate_component.html.erb b/app/components/work_package_types/form_configuration/blankslate_component.html.erb new file mode 100644 index 00000000000..a98ef27cc1b --- /dev/null +++ b/app/components/work_package_types/form_configuration/blankslate_component.html.erb @@ -0,0 +1,7 @@ +<%= + render(Primer::Beta::Blankslate.new(border: true, test_selector: "type-form-configuration-blankslate")) do |blankslate| + blankslate.with_visual_icon(icon: :rows) + blankslate.with_heading(tag: :h3) { t("types.edit.form_configuration.blankslate_title") } + blankslate.with_description { t("types.edit.form_configuration.blankslate_description") } + end +%> diff --git a/app/components/users/user_filter_component.rb b/app/components/work_package_types/form_configuration/blankslate_component.rb similarity index 88% rename from app/components/users/user_filter_component.rb rename to app/components/work_package_types/form_configuration/blankslate_component.rb index ed4242adfa2..2814c3a1151 100644 --- a/app/components/users/user_filter_component.rb +++ b/app/components/work_package_types/form_configuration/blankslate_component.rb @@ -28,14 +28,9 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module Users - class UserFilterComponent < ::UserFilterComponent - def filter_role(query, role_id) - super.uniq - end - - def clear_url - users_path +module WorkPackageTypes + module FormConfiguration + class BlankslateComponent < ApplicationComponent end end end diff --git a/app/components/work_package_types/form_configuration/group_attribute_row_component.html.erb b/app/components/work_package_types/form_configuration/group_attribute_row_component.html.erb new file mode 100644 index 00000000000..8ad7d6a2a65 --- /dev/null +++ b/app/components/work_package_types/form_configuration/group_attribute_row_component.html.erb @@ -0,0 +1,74 @@ +<%= + flex_layout(justify_content: :space_between, align_items: :center) do |row| + row.with_column(flex_layout: true, align_items: :center, flex: 1) do |left| + left.with_column(mr: 2, classes: "hide-when-print type-form-configuration-page--drag-handle") do + render( + Primer::OpenProject::DragHandle.new( + classes: "attribute-handle", + "aria-label": t("types.edit.form_configuration.drag_to_reorder"), + test_selector: "type-form-configuration-attribute-handle-#{@attribute[:key]}" + ) + ) + end + left.with_column(flex: 1) do + render(Primer::Beta::Text.new) { @attribute[:translation] } + end + end + + row.with_column(classes: "hide-when-print type-form-configuration-page--actions") do + render(Primer::Alpha::ActionMenu.new) do |menu| + menu.with_show_button( + icon: "kebab-horizontal", + scheme: :invisible, + size: :small, + classes: "type-form-configuration-page--actions-button", + test_selector: "type-form-configuration-attribute-actions-#{@attribute[:key]}", + "aria-label": t("types.edit.form_configuration.row_actions") + ) + + if attribute_can_move_up? + move_action( + menu:, + href: row_move_path(:highest), + label: t("label_agenda_item_move_to_top"), + icon: "move-to-top" + ) + move_action( + menu:, + href: row_move_path(:higher), + label: t("label_agenda_item_move_up"), + icon: "chevron-up" + ) + end + + if attribute_can_move_down? + move_action( + menu:, + href: row_move_path(:lower), + label: t("label_agenda_item_move_down"), + icon: "chevron-down" + ) + move_action( + menu:, + href: row_move_path(:lowest), + label: t("label_agenda_item_move_to_bottom"), + icon: "move-to-bottom" + ) + end + + menu.with_divider if show_delete_divider? + + menu.with_item( + label: t("button_delete"), + test_selector: "type-form-configuration-delete-attribute-#{@attribute[:key]}", + scheme: :danger, + tag: :a, + href: row_destroy_path, + content_arguments: { data: { turbo_method: :delete, turbo_stream: true } } + ) do |item| + item.with_leading_visual_icon(icon: :trash) + end + end + end + end +%> diff --git a/app/components/work_package_types/form_configuration/group_attribute_row_component.rb b/app/components/work_package_types/form_configuration/group_attribute_row_component.rb new file mode 100644 index 00000000000..38f8d859adb --- /dev/null +++ b/app/components/work_package_types/form_configuration/group_attribute_row_component.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfiguration + class GroupAttributeRowComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + + def initialize(attribute:, type:, index:, total_count:) + super + @attribute = attribute + @type = type + @index = index + @total_count = total_count + end + + private + + def multiple_attributes? + @total_count > 1 + end + + def attribute_can_move_up? + multiple_attributes? && !@index.zero? + end + + def attribute_can_move_down? + multiple_attributes? && @index != @total_count - 1 + end + + def show_delete_divider? + attribute_can_move_up? || attribute_can_move_down? + end + + def row_move_path(move_to) + move_type_form_configuration_row_path(@type, @attribute[:key], move_to:) + end + + def row_destroy_path + type_form_configuration_row_path(@type, @attribute[:key]) + end + + def move_action(menu:, href:, label:, icon:) + menu.with_item( + label:, + tag: :a, + href:, + content_arguments: { data: { turbo_method: :put, turbo_stream: true } } + ) do |item| + item.with_leading_visual_icon(icon:) + end + end + end + end +end diff --git a/app/components/work_package_types/form_configuration/group_component.html.erb b/app/components/work_package_types/form_configuration/group_component.html.erb new file mode 100644 index 00000000000..fcd29a0c9e5 --- /dev/null +++ b/app/components/work_package_types/form_configuration/group_component.html.erb @@ -0,0 +1,94 @@ +<%#-- copyright +OpenProject is an open source project management software. +Copyright (C) the OpenProject GmbH + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 3. + +OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2010-2013 the ChiliProject Team + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +See COPYRIGHT and LICENSE files for more details. + +++#%> + +<%= + component_wrapper( + data: wrapper_data + ) do + render(border_box_container(mt: 3, mb: (last? ? 3 : 0), padding: :condensed, data: row_drop_target_config)) do |box| + box.with_header(font_weight: :bold) do + render( + WorkPackageTypes::FormConfiguration::GroupHeaderComponent.new( + group: @group, + type: @type, + ee_available: ee_available?, + first: first?, + last: last?, + edit_mode: edit_mode?, + form_model: @form_model + ) + ) + end + + if query_group? + box.with_row do + render( + WorkPackageTypes::FormConfiguration::GroupQueryRowComponent.new( + group: @group, + ee_available: ee_available? + ) + ) + end + elsif attributes.empty? + box.with_row(data: { "empty-list-item": true }) do + flex_layout(align_items: :center) do |empty_row| + empty_row.with_column(mr: 2, classes: "type-form-configuration-page--row-alignment-spacer") + empty_row.with_column(flex: 1) do + render(Primer::Beta::Text.new(color: :subtle, font_style: :italic)) do + t("types.edit.form_configuration.empty_group_hint") + end + end + end + end + else + attributes.each_with_index do |attribute, index| + box.with_row( + data: { + attr_key: attribute[:key], + attr_translation: attribute[:translation], + attr_is_cf: attribute[:is_cf], + "draggable-id": attribute[:key], + "draggable-type": "attribute", + "drop-url": row_drop_path(attribute) + } + ) do + render( + WorkPackageTypes::FormConfiguration::GroupAttributeRowComponent.new( + attribute:, + type: @type, + index:, + total_count: attributes.length + ) + ) + end + end + end + end + end +%> diff --git a/app/components/work_package_types/form_configuration/group_component.rb b/app/components/work_package_types/form_configuration/group_component.rb new file mode 100644 index 00000000000..ad825da1e6a --- /dev/null +++ b/app/components/work_package_types/form_configuration/group_component.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfiguration + class GroupComponent < ApplicationComponent + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + def initialize(group:, type: nil, ee_available: false, first: false, last: false, edit_mode: false, + form_model: nil) + super(group) + @group = group + @type = type + @ee_available = ee_available + @first = first + @last = last + @edit_mode = edit_mode + @form_model = form_model + @instance_uid = SecureRandom.hex(4) + end + + def wrapper_uniq_by + @group[:key].presence || @instance_uid + end + + def edit_mode? + @edit_mode + end + + def query_group? + @group[:type].to_s == "query" + end + + def attributes + @group[:attributes] || [] + end + + def first? + @first + end + + def last? + @last + end + + def ee_available? + @ee_available + end + + private + + def wrapper_data + { + group_type: @group[:type].to_s, + group_key: @group[:key].to_s, + group_query: @group[:query], + edit_mode: (true if edit_mode?) + }.compact.merge(draggable_item_config) + end + + def group_name + @group[:name] + end + + def temporary_group? + @group[:temporary] + end + + def draggable_item_config + return {} if @group[:key].blank? || temporary_group? + + { + "draggable-id": @group[:key], + "draggable-type": "group", + "drop-url": drop_type_form_configuration_group_path(@type, @group[:key]) + } + end + + def row_drop_target_config + return {} if query_group? || @group[:key].blank? || temporary_group? + + { + "admin--type-form-configuration--rows-drag-and-drop-target": "container", + "target-container-accessor": ".Box > ul", + "target-id": @group[:key], + "target-allowed-drag-type": "attribute" + } + end + + def edit_path + edit_type_form_configuration_group_path(@type, @group[:key]) + end + + def update_path + type_form_configuration_group_path(@type, @group[:key]) + end + + def cancel_edit_path + cancel_edit_type_form_configuration_group_path(@type, @group[:key]) + end + + def move_path(move_to) + move_type_form_configuration_group_path(@type, @group[:key], move_to:) + end + + def destroy_path + type_form_configuration_group_path(@type, @group[:key]) + end + + def row_drop_path(attribute) + drop_type_form_configuration_row_path(@type, attribute[:key]) + end + end + end +end diff --git a/app/components/work_package_types/form_configuration/group_header_component.html.erb b/app/components/work_package_types/form_configuration/group_header_component.html.erb new file mode 100644 index 00000000000..b4a49ebe404 --- /dev/null +++ b/app/components/work_package_types/form_configuration/group_header_component.html.erb @@ -0,0 +1,99 @@ +<% if edit_mode? %> + <%= + primer_form_with( + model: form_model, + scope: :group, + method: form_method, + url: update_path, + test_selector: "type-form-configuration-group-edit-form", + data: { + turbo_stream: true + } + ) do |form| + render( + WorkPackageTypes::FormConfiguration::GroupForm.new( + form, + cancel_path: cancel_edit_path + ) + ) + end + %> +<% else %> + <%= + flex_layout(justify_content: :space_between, align_items: :center) do |header| + header.with_column(flex_layout: true, align_items: :center, flex: 1) do |left| + left.with_column(mr: 2, classes: "hide-when-print type-form-configuration-page--drag-handle") do + render( + Primer::OpenProject::DragHandle.new( + classes: "group-handle", + "aria-label": t("types.edit.form_configuration.drag_to_reorder"), + test_selector: "type-form-configuration-group-handle-#{@group[:key]}" + ) + ) + end + left.with_column(flex: 1) do + render(Primer::Beta::Text.new(font_weight: :bold)) { group_name } + end + end + + header.with_column(classes: "hide-when-print type-form-configuration-page--actions") do + render(Primer::Alpha::ActionMenu.new) do |menu| + menu.with_show_button( + icon: "kebab-horizontal", + scheme: :invisible, + size: :small, + classes: "type-form-configuration-page--actions-button", + test_selector: "type-form-configuration-group-actions-#{@group[:key]}", + "aria-label": t("types.edit.form_configuration.group_actions") + ) + + if ee_available? + with_item_group(menu) do + menu.with_item( + label: t("types.edit.form_configuration.rename_group"), + test_selector: "type-form-configuration-group-rename-#{@group[:key]}", + tag: :a, + href: edit_path, + content_arguments: { data: { turbo_stream: true } } + ) do |item| + item.with_leading_visual_icon(icon: :pencil) + end + end + end + + with_item_group(menu) do + unless first? + move_action(menu:, href: move_path(:highest), label: t("label_agenda_item_move_to_top"), icon: "move-to-top") + move_action(menu:, href: move_path(:higher), label: t("label_agenda_item_move_up"), icon: "chevron-up") + end + + unless last? + move_action(menu:, href: move_path(:lower), label: t("label_agenda_item_move_down"), icon: "chevron-down") + move_action(menu:, href: move_path(:lowest), label: t("label_agenda_item_move_to_bottom"), icon: "move-to-bottom") + end + end + + if ee_available? + with_item_group(menu) do + menu.with_item( + label: t("button_delete"), + scheme: :danger, + tag: :a, + href: destroy_path, + content_arguments: { + data: { + turbo_method: :delete, + turbo_stream: true, + turbo_confirm: t("types.edit.form_configuration.confirm_delete_group") + } + } + ) do |item| + item.with_leading_visual_icon(icon: :trash) + end + end + end + end + end + end + %> +<% end %> diff --git a/app/components/work_package_types/form_configuration/group_header_component.rb b/app/components/work_package_types/form_configuration/group_header_component.rb new file mode 100644 index 00000000000..fc796b936a2 --- /dev/null +++ b/app/components/work_package_types/form_configuration/group_header_component.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfiguration + class GroupHeaderComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + + def initialize(group:, type:, ee_available:, first:, last:, edit_mode:, form_model: nil) + super + @group = group + @type = type + @ee_available = ee_available + @first = first + @last = last + @edit_mode = edit_mode + @form_model = form_model + end + + def edit_mode? + @edit_mode + end + + private + + def group_name + @group[:name] + end + + def form_model + @form_model || WorkPackageTypes::FormConfiguration::GroupFormModel.from_group(@group) + end + + def ee_available? + @ee_available + end + + def first? + @first + end + + def last? + @last + end + + def edit_path + edit_type_form_configuration_group_path(@type, @group[:key]) + end + + def update_path + return create_path if temporary_group? + + type_form_configuration_group_path(@type, @group[:key]) + end + + def form_method + temporary_group? ? :post : :patch + end + + def cancel_edit_path + cancel_edit_type_form_configuration_group_path(@type, @group[:key]) + end + + def move_path(move_to) + move_type_form_configuration_group_path(@type, @group[:key], move_to:) + end + + def destroy_path + type_form_configuration_group_path(@type, @group[:key]) + end + + def temporary_group? + @group[:temporary] + end + + def create_path + type_form_configuration_groups_path(@type) + end + + def move_action(menu:, href:, label:, icon:) + menu.with_item( + label:, + tag: :a, + href:, + content_arguments: { data: { turbo_method: :put, turbo_stream: true } } + ) do |item| + item.with_leading_visual_icon(icon:) + end + end + end + end +end diff --git a/app/components/work_package_types/form_configuration/group_query_row_component.html.erb b/app/components/work_package_types/form_configuration/group_query_row_component.html.erb new file mode 100644 index 00000000000..771902361c5 --- /dev/null +++ b/app/components/work_package_types/form_configuration/group_query_row_component.html.erb @@ -0,0 +1,49 @@ +<%= + flex_layout(justify_content: :space_between, align_items: :center) do |row| + row.with_column(flex_layout: true, align_items: :center, flex: 1) do |left| + left.with_column(mr: 2, classes: "type-form-configuration-page--row-alignment-spacer") + left.with_column(flex: 1) do + if ee_available? + render( + Primer::Beta::Button.new( + scheme: :link, + data: { action: "click->admin--type-form-configuration--main#editQuery" } + ) + ) do + t("types.edit.form_configuration.query_group_label") + end + else + render(Primer::Beta::Text.new(color: :subtle, font_style: :italic)) do + t("types.edit.form_configuration.query_group_label") + end + end + end + end + + if ee_available? + row.with_column(classes: "hide-when-print type-form-configuration-page--actions") do + render(Primer::Alpha::ActionMenu.new) do |menu| + menu.with_show_button( + icon: "kebab-horizontal", + scheme: :invisible, + size: :small, + classes: "type-form-configuration-page--actions-button", + test_selector: "type-form-configuration-query-actions-#{@group[:key]}", + "aria-label": t("types.edit.form_configuration.row_actions") + ) + + menu.with_item( + label: t("types.edit.form_configuration.edit_query"), + test_selector: "type-form-configuration-edit-query-#{@group[:key]}", + tag: :button, + content_arguments: { + data: { action: "click->admin--type-form-configuration--main#editQuery" } + } + ) do |item| + item.with_leading_visual_icon(icon: :pencil) + end + end + end + end + end +%> diff --git a/app/components/open_project/common/work_package_card_list_component/empty_item.rb b/app/components/work_package_types/form_configuration/group_query_row_component.rb similarity index 72% rename from app/components/open_project/common/work_package_card_list_component/empty_item.rb rename to app/components/work_package_types/form_configuration/group_query_row_component.rb index c6849109532..20e0073f58a 100644 --- a/app/components/open_project/common/work_package_card_list_component/empty_item.rb +++ b/app/components/work_package_types/form_configuration/group_query_row_component.rb @@ -28,23 +28,21 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module OpenProject - module Common - class WorkPackageCardListComponent - # Row bridge for caller-provided empty content. - class EmptyItem < ContentItem - include Primer::AttributesHelper +module WorkPackageTypes + module FormConfiguration + class GroupQueryRowComponent < ApplicationComponent + include OpPrimer::ComponentHelpers - def row_args - system_arguments = @system_arguments.deep_dup - system_arguments[:data] = merge_data( - { data: { empty_list_item: true } }, - system_arguments - ) - system_arguments - end + def initialize(group:, ee_available:) + super + @group = group + @ee_available = ee_available + end - def empty_item? = true + private + + def ee_available? + @ee_available end end end diff --git a/app/components/work_package_types/form_configuration/inactive_attributes_list_component.html.erb b/app/components/work_package_types/form_configuration/inactive_attributes_list_component.html.erb new file mode 100644 index 00000000000..8e125cd42ac --- /dev/null +++ b/app/components/work_package_types/form_configuration/inactive_attributes_list_component.html.erb @@ -0,0 +1,43 @@ +<%= + component_wrapper( + id: "type-form-configuration-inactive-container", + data: container_data + ) do + render( + Primer::Alpha::ActionList.new( + role: :list, + classes: "type-form-configuration-page--inactive-list", + data: { "test-selector": "type-form-configuration-inactive-list" } + ) + ) do |list| + if @inactive_attributes.empty? + list.with_item( + label: t("types.edit.form_configuration.no_inactive_attributes"), + disabled: true, + classes: "type-form-configuration-page--inactive-empty", + content_arguments: { tag: :div }, + data: { "empty-list-item": true, "filter--filter-list-target": "searchItem" } + ) + else + @inactive_attributes.each do |attribute| + list.with_item( + label: attribute[:translation], + classes: "type-form-configuration-page--inactive-item", + content_arguments: { tag: :div }, + data: item_data(attribute).merge("filter--filter-list-target": "searchItem") + ) do |item| + item.with_leading_visual_raw_content do + render( + Primer::OpenProject::DragHandle.new( + classes: "attribute-handle", + "aria-label": t("types.edit.form_configuration.drag_to_reorder"), + test_selector: "type-form-configuration-attribute-handle-#{attribute[:key]}" + ) + ) + end + end + end + end + end + end +%> diff --git a/app/components/work_package_types/form_configuration/inactive_attributes_list_component.rb b/app/components/work_package_types/form_configuration/inactive_attributes_list_component.rb new file mode 100644 index 00000000000..ff84d60d9d2 --- /dev/null +++ b/app/components/work_package_types/form_configuration/inactive_attributes_list_component.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfiguration + class InactiveAttributesListComponent < ApplicationComponent + include OpTurbo::Streamable + + def initialize(type:, inactive_attributes:) + super + @type = type + @inactive_attributes = inactive_attributes + end + + private + + def container_data + { + "test-selector": "type-form-configuration-inactive-container", + "admin--type-form-configuration--main-target": "inactiveContainer", + "admin--type-form-configuration--rows-drag-and-drop-target": "container", + "target-container-accessor": "[data-test-selector='type-form-configuration-inactive-list']", + "target-id": "inactive", + "target-allowed-drag-type": "attribute" + } + end + + def item_data(attribute) + { + attr_key: attribute[:key], + attr_translation: attribute[:translation], + attr_is_cf: attribute[:is_cf], + "draggable-id": attribute[:key], + "draggable-type": "attribute", + "drop-url": drop_type_form_configuration_row_path(@type, attribute[:key]) + } + end + end + end +end diff --git a/app/components/work_package_types/form_configuration/inactive_attributes_sidebar_component.html.erb b/app/components/work_package_types/form_configuration/inactive_attributes_sidebar_component.html.erb new file mode 100644 index 00000000000..02e359f19d6 --- /dev/null +++ b/app/components/work_package_types/form_configuration/inactive_attributes_sidebar_component.html.erb @@ -0,0 +1,42 @@ +<%= + flex_layout(data: { controller: "filter--filter-list" }) do |flex| + flex.with_row(mb: 1) do + render(Primer::Beta::Heading.new(tag: :h3, font_size: 4)) do + t("types.edit.form_configuration.inactive_attributes_heading") + end + end + + flex.with_row(mb: 3) do + render(Primer::Beta::Text.new(color: :subtle, font_size: :small)) do + t("types.edit.form_configuration.drag_to_activate") + end + end + + flex.with_row(mb: 3) do + render( + Primer::Alpha::TextField.new( + name: "inactive-filter", + label: t("types.edit.form_configuration.filter_inactive"), + visually_hide_label: true, + placeholder: t("types.edit.form_configuration.filter_inactive"), + leading_visual: { icon: :search }, + size: :medium, + "full-width": true, + data: { + action: "input->filter--filter-list#filterLists", + "filter--filter-list-target": "filter" + } + ) + ) + end + + flex.with_row do + render( + WorkPackageTypes::FormConfiguration::InactiveAttributesListComponent.new( + inactive_attributes: @inactive_attributes, + type: @type + ) + ) + end + end +%> diff --git a/app/components/work_package_types/form_configuration/inactive_attributes_sidebar_component.rb b/app/components/work_package_types/form_configuration/inactive_attributes_sidebar_component.rb new file mode 100644 index 00000000000..db4634d6127 --- /dev/null +++ b/app/components/work_package_types/form_configuration/inactive_attributes_sidebar_component.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfiguration + class InactiveAttributesSidebarComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + + def initialize(type:, inactive_attributes:) + super + @type = type + @inactive_attributes = inactive_attributes + end + end + end +end diff --git a/app/components/work_package_types/form_configuration/main_content_component.html.erb b/app/components/work_package_types/form_configuration/main_content_component.html.erb new file mode 100644 index 00000000000..2c6a832a94a --- /dev/null +++ b/app/components/work_package_types/form_configuration/main_content_component.html.erb @@ -0,0 +1,73 @@ +<%= + component_wrapper( + class: "type-form-configuration-page--main-inner", + data: main_inner_data + ) do + flex_layout do |main| + main.with_row do + render(Primer::OpenProject::SubHeader.new(mb: 0)) do |subheader| + subheader.with_action_button( + tag: :a, + scheme: :secondary, + leading_icon: :undo, + label: t("types.edit.form_configuration.reset_to_defaults"), + href: reset_dialog_type_form_configuration_path(@type), + "data-test-selector": "type-form-configuration-reset-button", + data: { controller: "async-dialog" } + ) do + t("types.edit.form_configuration.reset_to_defaults") + end + + if ee_available? + subheader.with_action_menu( + leading_icon: :plus, + trailing_icon: :"triangle-down", + label: t(:button_add), + anchor_align: :end, + button_arguments: { + scheme: :primary, + "aria-label": t(:button_add), + "data-test-selector": "type-form-configuration-add-button" + } + ) do |menu| + menu.with_item( + label: t("types.edit.form_configuration.add_attribute_group"), + tag: :a, + href: add_group_type_form_configuration_groups_path(@type, group_type: :attribute), + content_arguments: { + data: { turbo_method: :post, turbo_stream: true } + } + ) do |item| + item.with_leading_visual_icon(icon: :rows) + end + + menu.with_item( + label: t("types.edit.form_configuration.add_query_group"), + tag: :button, + content_arguments: { + data: { action: "click->admin--type-form-configuration--main#addQueryGroup" } + } + ) do |item| + item.with_leading_visual_icon(icon: :table) + end + end + end + end + end + + main.with_row do + content_tag( + :div, + id: "type-form-configuration-groups-container", + data: groups_container_data + ) do + if @group_components.empty? + render(WorkPackageTypes::FormConfiguration::BlankslateComponent.new) + else + safe_join(@group_components.map { |group| render(group) }) + end + end + end + end + end +%> diff --git a/app/components/work_package_types/form_configuration/main_content_component.rb b/app/components/work_package_types/form_configuration/main_content_component.rb new file mode 100644 index 00000000000..bae22743782 --- /dev/null +++ b/app/components/work_package_types/form_configuration/main_content_component.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfiguration + class MainContentComponent < ApplicationComponent + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + def initialize(type:, group_components:, ee_available:) + super + @type = type + @group_components = group_components + @ee_available = ee_available + end + + private + + def ee_available? + @ee_available + end + + def main_inner_data + { + controller: "admin--type-form-configuration--drag-and-drop", + "admin--type-form-configuration--drag-and-drop-handle-selector-value": ".group-handle" + } + end + + def groups_container_data + { + "test-selector": "type-form-configuration-groups-container", + "admin--type-form-configuration--main-target": "groupsContainer", + "admin--type-form-configuration--drag-and-drop-target": "container", + "target-allowed-drag-type": "group" + } + end + end + end +end diff --git a/app/components/work_package_types/form_configuration/reset_dialog_component.html.erb b/app/components/work_package_types/form_configuration/reset_dialog_component.html.erb new file mode 100644 index 00000000000..a77621e16c6 --- /dev/null +++ b/app/components/work_package_types/form_configuration/reset_dialog_component.html.erb @@ -0,0 +1,24 @@ +<%= + render( + Primer::OpenProject::DangerDialog.new( + id: "type-form-configuration-reset-dialog", + test_selector: "type-form-configuration-reset-dialog", + title: t("types.edit.form_configuration.reset_title"), + confirm_button_text: t("button_reset"), + form_arguments: { + action: type_form_configuration_path(@type), + method: :patch, + data: { turbo_stream: true } + } + ) + ) do |dialog| + dialog.with_confirmation_message do |message| + message.with_heading(tag: :h2) { t("types.edit.form_configuration.confirm_reset") } + message.with_description_content(t("types.edit.form_configuration.reset_description")) + end + + dialog.with_additional_details do + hidden_field_tag("type[attribute_groups]", "[]") + end + end +%> diff --git a/app/components/work_package_types/form_configuration/reset_dialog_component.rb b/app/components/work_package_types/form_configuration/reset_dialog_component.rb new file mode 100644 index 00000000000..875e62b8b56 --- /dev/null +++ b/app/components/work_package_types/form_configuration/reset_dialog_component.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfiguration + class ResetDialogComponent < ApplicationComponent + include OpTurbo::Streamable + + def initialize(type:) + super + @type = type + end + end + end +end diff --git a/modules/meeting/app/views/meeting_series_mailer/participant_added.text.erb b/app/components/work_package_types/form_configuration_component.html.erb similarity index 52% rename from modules/meeting/app/views/meeting_series_mailer/participant_added.text.erb rename to app/components/work_package_types/form_configuration_component.html.erb index 4bc98e5bae5..20cfedb36b4 100644 --- a/modules/meeting/app/views/meeting_series_mailer/participant_added.text.erb +++ b/app/components/work_package_types/form_configuration_component.html.erb @@ -27,10 +27,28 @@ See COPYRIGHT and LICENSE files for more details. ++#%> -<%= I18n.t("meeting.email.participant_added.summary_series", title: @series.title, actor: @actor, participant: @added_participant) %> +<%= component_wrapper( + class: "type-form-configuration-page--wrapper", + data: wrapper_data + ) do %> + <%= render(Primer::Alpha::Layout.new(stacking_breakpoint: :md, overflow: :hidden, h: :full, classes: "type-form-configuration-page")) do |content| %> + <% content.with_sidebar(row_placement: :start, col_placement: :start, border: true, border_bottom: 0, p: 3, overflow: :auto, classes: "type-form-configuration-page--sidebar") do %> + <%= render( + WorkPackageTypes::FormConfiguration::InactiveAttributesSidebarComponent.new( + type: @type, + inactive_attributes: @inactive_attributes + ) + ) %> + <% end %> -<%= @series.project.name %>: <%= @series.title %> (<%= recurring_meeting_url(@series) %>) - -<%= t :label_recurring_meeting_schedule %>: <%= @series.full_schedule_in_words %> (<%= formatted_time_zone_offset %>) -<%= Meeting.human_attribute_name(:location) %>: <%= @template.location %> -<%= Meeting.human_attribute_name(:participants_invited) %>: <%= @template.participants.invited.sort.join("; ") %> + <% content.with_main(overflow: :auto, classes: "type-form-configuration-page--main") do %> + <%= render( + WorkPackageTypes::FormConfiguration::MainContentComponent.new( + type: @type, + group_components: group_components, + ee_available: ee_available? + ) + ) %> + <% end %> + <% end %> +<% end %> diff --git a/app/components/work_package_types/form_configuration_component.rb b/app/components/work_package_types/form_configuration_component.rb new file mode 100644 index 00000000000..1d55de92325 --- /dev/null +++ b/app/components/work_package_types/form_configuration_component.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + class FormConfigurationComponent < ApplicationComponent + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + def initialize(type:, form_attributes:, no_filter_query:) + super(type) + @type = type + @groups = form_attributes[:actives].reject { |g| g[:key].to_s == "__empty" } + @inactive_attributes = form_attributes[:inactives] + @no_filter_query = no_filter_query + end + + def ee_available? + EnterpriseToken.allows_to?(:edit_attribute_groups) + end + + def wrapper_data + { + controller: "admin--type-form-configuration--main admin--type-form-configuration--rows-drag-and-drop", + "admin--type-form-configuration--main-no-filter-query-value": @no_filter_query, + "admin--type-form-configuration--main-add-group-url-value": add_group_type_form_configuration_groups_path(@type), + "admin--type-form-configuration--main-groups-url-value": type_form_configuration_groups_path(@type), + "admin--type-form-configuration--rows-drag-and-drop-handle-selector-value": ".attribute-handle" + } + end + + def group_components + @groups.map.with_index do |group, i| + WorkPackageTypes::FormConfiguration::GroupComponent.new( + group:, + type: @type, + ee_available: ee_available?, + first: i == 0, + last: i == @groups.length - 1 + ) + end + end + end +end diff --git a/app/components/work_packages/bulk_delete_dialog_component.rb b/app/components/work_packages/bulk_delete_dialog_component.rb index a3e75df4cbf..31f1f81202d 100644 --- a/app/components/work_packages/bulk_delete_dialog_component.rb +++ b/app/components/work_packages/bulk_delete_dialog_component.rb @@ -94,7 +94,10 @@ module WorkPackages end def projects - @projects ||= work_packages.filter_map(&:project).uniq + @projects ||= begin + all_work_packages = work_packages + descendants_by_work_package.values.flatten + all_work_packages.filter_map(&:project).uniq + end end def descendants_by_work_package diff --git a/app/contracts/work_package_types/update_form_configuration_contract.rb b/app/contracts/work_package_types/update_form_configuration_contract.rb index 8d14edc2866..4c6ed998532 100644 --- a/app/contracts/work_package_types/update_form_configuration_contract.rb +++ b/app/contracts/work_package_types/update_form_configuration_contract.rb @@ -48,7 +48,9 @@ module WorkPackageTypes seen = Set.new model.attribute_groups.each do |group| errors.add(:attribute_groups, :group_without_name) if group.key.blank? - errors.add(:attribute_groups, :duplicate_group, group: group.key) if seen.add?(group.key).nil? + + group_name = visible_group_name(group) + errors.add(:attribute_groups, :duplicate_group, group: group_name) if seen.add?(group_name).nil? end end @@ -65,14 +67,10 @@ module WorkPackageTypes end def validate_query_group(group) - query = group.query + query_call = rebuild_query_group(group) + return add_invalid_query_error(group, query_call.errors) unless query_call.success? - contract_class = query.persisted? ? Queries::UpdateContract : Queries::CreateContract - contract = contract_class.new(query, user) - - unless contract.validate - errors.add(:attribute_groups, :query_invalid, group: group.key, details: contract.errors.full_messages.join) - end + validate_rebuilt_query_group(group, query_call.result) end def validate_attribute_group(group) @@ -92,10 +90,42 @@ module WorkPackageTypes def custom_groups_modified? return false unless model.attribute_groups_changed? - old_keys = model.attribute_groups_was.map(&:first) + old_keys = normalized_old_keys new_keys = model.attribute_groups.map(&:key) (new_keys - old_keys - Type.default_groups.keys).any? end + + def normalized_old_keys + seen_keys = model.attribute_groups_was.filter_map(&:first).compact_blank.map(&:to_s) + + model.attribute_groups_was.map do |group| + key = group.first.presence&.to_s + key || normalized_legacy_group_key(seen_keys).tap { |legacy_key| seen_keys << legacy_key } + end + end + + def normalized_legacy_group_key(seen_keys) + Type::FormGroup.next_untitled_key(seen_keys) + end + + def visible_group_name(group) + group.translated_key.to_s.strip + end + + def rebuild_query_group(group) + ::WorkPackageTypes::FormConfiguration::EmbeddedQueryBuilder.rebuild(query: group.query, user:) + end + + def validate_rebuilt_query_group(group, query) + contract = Queries::CreateContract.new(query, user) + return if contract.validate + + add_invalid_query_error(group, contract.errors) + end + + def add_invalid_query_error(group, error_collection) + errors.add(:attribute_groups, :query_invalid, group: group.key, details: error_collection.full_messages.to_sentence) + end end end diff --git a/app/controllers/concerns/work_package_types/form_configuration_component_streams.rb b/app/controllers/concerns/work_package_types/form_configuration_component_streams.rb new file mode 100644 index 00000000000..a799d7b709f --- /dev/null +++ b/app/controllers/concerns/work_package_types/form_configuration_component_streams.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfigurationComponentStreams + extend ActiveSupport::Concern + + private + + def update_form_configuration_via_turbo_stream(**) + update_main_content_via_turbo_stream(**) + update_inactive_attributes_via_turbo_stream + end + + def update_main_content_via_turbo_stream(groups: active_groups_for_form, editing_group_key: nil, form_model: nil) + ee_available = EnterpriseToken.allows_to?(:edit_attribute_groups) + group_components = build_group_components( + groups:, + ee_available:, + editing_group_key:, + form_model: + ) + + update_via_turbo_stream( + component: WorkPackageTypes::FormConfiguration::MainContentComponent.new( + type: @type, + group_components:, + ee_available: + ) + ) + end + + def update_inactive_attributes_via_turbo_stream + replace_via_turbo_stream( + component: WorkPackageTypes::FormConfiguration::InactiveAttributesListComponent.new( + inactive_attributes: form_configuration_groups(@type)[:inactives], + type: @type + ), + target: "type-form-configuration-inactive-container" + ) + end + + def render_form_configuration_error(call) + render_error_flash_message_via_turbo_stream(message: call.errors.full_messages.to_sentence) + end + + def build_group_components(groups:, ee_available:, editing_group_key:, form_model:) + groups.map.with_index do |group, index| + is_editing = editing_group_key.present? && group[:key].to_s == editing_group_key.to_s + + WorkPackageTypes::FormConfiguration::GroupComponent.new( + group:, + type: @type, + ee_available:, + first: index.zero?, + last: index == groups.length - 1, + edit_mode: is_editing, + form_model: (form_model if is_editing) + ) + end + end + + def active_groups_for_form + form_configuration_groups(@type)[:actives].reject { |group| group[:key].to_s == "__empty" } + end + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index a2efeb23fc4..499acc91fb0 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -79,11 +79,19 @@ class UsersController < ApplicationController include SortHelper include CustomFieldsHelper include PaginationHelper + include Queries::Loading + + before_action :load_query_or_deny_access, only: %i[index configure_view_modal] def index - @groups = Group.visible.sort - @status = Users::UserFilterComponent.status_param params - @users = Users::UserFilterComponent.filter params + respond_to do |format| + format.html + format.turbo_stream { render_index_turbo_stream } + end + end + + def configure_view_modal + respond_with_dialog Users::ConfigureViewModalComponent.new(query: @query) end def show @@ -423,6 +431,15 @@ class UsersController < ApplicationController status: User.statuses[:invited]) end + def render_index_turbo_stream # rubocop:disable Metrics/AbcSize + replace_via_turbo_stream(component: Users::IndexPageHeaderComponent.new(query: @query)) + update_via_turbo_stream(component: Users::UserFilterButtonComponent.new(query: @query)) + replace_via_turbo_stream(component: Users::TableComponent.new(rows: @query, current_user:)) + turbo_streams << turbo_stream.push_state(url_for(params.permit(:filters, :sortBy, :sort, :page, :per_page, :columns))) + turbo_streams << turbo_stream.replace("primerized-flash-messages", helpers.render_flash_messages) + render turbo_stream: turbo_streams + end + def prepare_views_for_tab # rubocop:disable Metrics/AbcSize if params[:tab] == "non_working_times" authorize_manage_working_times diff --git a/app/controllers/work_package_types/form_configuration_groups_tab_controller.rb b/app/controllers/work_package_types/form_configuration_groups_tab_controller.rb new file mode 100644 index 00000000000..a5901f94b8c --- /dev/null +++ b/app/controllers/work_package_types/form_configuration_groups_tab_controller.rb @@ -0,0 +1,240 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + class FormConfigurationGroupsTabController < BaseTabController + include TypesHelper + include OpTurbo::ComponentStream + include WorkPackageTypes::FormConfigurationComponentStreams + + TEMPORARY_GROUP_KEY = "__new_form_configuration_group__" + + def edit + update_main_content_via_turbo_stream(editing_group_key: group_key_param) + + respond_with_turbo_streams + end + + def add_group + render_temporary_group_editor + + respond_with_turbo_streams + end + + def create + call = create_group_call + + if call.success? + update_form_configuration_via_turbo_stream + else + render_create_error(call) + end + + respond_with_turbo_streams(status: turbo_status_for(call)) + end + + def cancel_edit + if temporary_group_key?(group_key_param) + update_form_configuration_via_turbo_stream + respond_with_turbo_streams + return + end + + group = find_group(group_key_param) + return head :not_found if group.nil? + + update_main_content_via_turbo_stream + respond_with_turbo_streams + end + + def update + call = rename_group_call + + if call.success? + update_form_configuration_via_turbo_stream + else + render_existing_group_update_error(call) + end + + respond_with_turbo_streams(status: turbo_status_for(call)) + end + + def destroy + call = ::WorkPackageTypes::FormConfigurationGroups::DeleteService + .new(user: current_user, type: @type, group_key: group_key_param) + .call + + if call.success? + update_form_configuration_via_turbo_stream + else + render_form_configuration_error(call) + end + + respond_with_turbo_streams(status: turbo_status_for(call)) + end + + def drop + call = ::WorkPackageTypes::FormConfigurationGroups::UpdateService + .new(user: current_user, type: @type, group_key: group_key_param) + .call(position: params[:position]) + + if call.success? + update_main_content_via_turbo_stream + else + render_form_configuration_error(call) + end + + respond_with_turbo_streams(status: turbo_status_for(call)) + end + + def move + call = ::WorkPackageTypes::FormConfigurationGroups::UpdateService + .new(user: current_user, type: @type, group_key: group_key_param) + .call(move_to: params[:move_to]) + + if call.success? + update_main_content_via_turbo_stream + else + render_form_configuration_error(call) + end + + respond_with_turbo_streams(status: turbo_status_for(call)) + end + + def update_query + call = ::WorkPackageTypes::FormConfigurationGroups::UpdateService + .new(user: current_user, type: @type, group_key: group_key_param) + .call(query_props: params[:query]) + + if call.success? + head :ok + else + render_form_configuration_error(call) + respond_with_turbo_streams(status: turbo_status_for(call)) + end + end + + private + + def group_params + params.expect(group: %i[name group_type query]) + end + + def find_group(key) + @type.attribute_groups.find do |group| + [ + group.key, + group.display_name, + group.translated_key + ].compact.map(&:to_s).include?(key.to_s) + end + end + + def group_key_param + params[:key] || params[:id] + end + + def temporary_group_key?(key) + key.to_s == TEMPORARY_GROUP_KEY + end + + def temporary_group(group_type:, query:, name: "") + { + key: TEMPORARY_GROUP_KEY, + type: group_type.to_s, + name:, + attributes: [], + query:, + temporary: true + } + end + + def turbo_status_for(call) + call.success? ? :ok : :unprocessable_entity + end + + def create_group_call + ::WorkPackageTypes::FormConfigurationGroups::CreateService + .new(user: current_user, type: @type) + .call( + group_type: group_params[:group_type], + name: group_params[:name], + query_props: group_params[:query] + ) + end + + def rename_group_call + ::WorkPackageTypes::FormConfigurationGroups::UpdateService + .new(user: current_user, type: @type, group_key: group_key_param) + .call(name: group_params[:name]) + end + + def render_create_error(call) + @type.reload + group = temporary_group( + group_type: group_params[:group_type], + query: group_params[:query], + name: group_params[:name].to_s + ) + + render_temporary_group_editor( + group:, + form_model: group_form_model(group:, validation_message: call.errors.map(&:message).to_sentence) + ) + end + + def render_existing_group_update_error(call) + @type.reload + group = active_groups_for_form.find { |active_group| active_group[:key].to_s == group_key_param.to_s } + + update_main_content_via_turbo_stream( + editing_group_key: group_key_param, + form_model: group_form_model( + group:, + name: group_params[:name].to_s, + validation_message: call.errors.map(&:message).to_sentence + ) + ) + end + + def render_temporary_group_editor(group: temporary_group(group_type: params[:group_type], query: params[:query]), + form_model: nil) + update_main_content_via_turbo_stream( + groups: [group] + active_groups_for_form, + editing_group_key: TEMPORARY_GROUP_KEY, + form_model: + ) + end + + def group_form_model(group:, name: group[:name], validation_message: nil) + WorkPackageTypes::FormConfiguration::GroupFormModel.from_group(group, name:, validation_message:) + end + end +end diff --git a/app/controllers/work_package_types/form_configuration_tab_controller.rb b/app/controllers/work_package_types/form_configuration_tab_controller.rb index 3edf8c3b1ef..b094b28b4fd 100644 --- a/app/controllers/work_package_types/form_configuration_tab_controller.rb +++ b/app/controllers/work_package_types/form_configuration_tab_controller.rb @@ -30,36 +30,101 @@ module WorkPackageTypes class FormConfigurationTabController < BaseTabController - include PaginationHelper + include TypesHelper + include OpTurbo::ComponentStream + include WorkPackageTypes::FormConfigurationComponentStreams layout "admin" - current_menu_item [:edit, :update] do + current_menu_item [:edit, :update, :reset_dialog, :move, :drop, :destroy] do :types end def edit; end + def reset_dialog + respond_with_dialog( + WorkPackageTypes::FormConfiguration::ResetDialogComponent.new(type: @type) + ) + end + def update result = WorkPackageTypes::UpdateService .new(user: current_user, model: @type, contract_class: UpdateFormConfigurationContract) .call(permitted_type_params) if result.success? - redirect_to edit_type_form_configuration_path(@type), notice: t(:notice_successful_update) + respond_to_update_success else - flash.now[:error] = result.errors[:attribute_groups].to_sentence - render :edit, status: :unprocessable_entity + respond_to_update_failure(result) end end + def move + call = ::WorkPackageTypes::FormConfigurationRows::UpdateService + .new(user: current_user, type: @type, row_key: row_key_param) + .call(move_to: params[:move_to]) + + handle_row_update_response(call) + end + + def drop + call = ::WorkPackageTypes::FormConfigurationRows::UpdateService + .new(user: current_user, type: @type, row_key: row_key_param) + .call(target_id: params[:target_id], position: params[:position]) + + handle_row_update_response(call) + end + + def destroy + call = ::WorkPackageTypes::FormConfigurationRows::DeleteService + .new(user: current_user, type: @type, row_key: row_key_param) + .call + + handle_row_update_response(call) + end + private + def respond_to_update_success + respond_to do |format| + format.html { redirect_to edit_type_form_configuration_path(@type), notice: t(:notice_successful_update) } + format.turbo_stream do + update_form_configuration_via_turbo_stream + respond_with_turbo_streams + end + end + end + + def respond_to_update_failure(result) + respond_to do |format| + format.html do + flash.now[:error] = result.errors[:attribute_groups].to_sentence + render :edit, status: :unprocessable_entity + end + format.turbo_stream { head :unprocessable_entity } + end + end + + def handle_row_update_response(call) + if call.success? + update_form_configuration_via_turbo_stream + else + render_form_configuration_error(call) + end + + respond_with_turbo_streams(status: call.success? ? :ok : :unprocessable_entity) + end + def find_type @type = ::Type.includes(:projects, :custom_fields).find(params[:type_id]) show_error_not_found unless @type end + def row_key_param + params[:row_key] || params[:id] + end + def permitted_type_params # having to call #to_unsafe_h as a query hash the attribute_groups # parameters would otherwise still be an ActiveSupport::Parameter diff --git a/app/controllers/work_packages_controller.rb b/app/controllers/work_packages_controller.rb index ad87a9bc100..8de18e280e5 100644 --- a/app/controllers/work_packages_controller.rb +++ b/app/controllers/work_packages_controller.rb @@ -286,7 +286,7 @@ class WorkPackagesController < ApplicationController end def login_back_url_params - params.permit(:query_id, :state, :query_props) + params.permit(:query_id, :state, :query_props, :type, :parent_id) end def redirect_to_complete_route diff --git a/app/forms/work_package_types/form_configuration/group_form.rb b/app/forms/work_package_types/form_configuration/group_form.rb new file mode 100644 index 00000000000..cee1676e6f4 --- /dev/null +++ b/app/forms/work_package_types/form_configuration/group_form.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfiguration + class GroupForm < ApplicationForm + form do |group_form| + group_form.hidden(name: :group_type, value: model.group_type) + group_form.hidden(name: :query, value: model.query) if model.query.present? + + group_form.group(layout: :horizontal) do |row| + row.text_field( + name: :name, + label: I18n.t("types.edit.form_configuration.group_name_label"), + visually_hide_label: true, + value: model.name, + required: true, + autofocus: true, + autocomplete: "off", + validation_message: validation_message_for(:name), + data: { "test-selector": "type-form-configuration-group-name-input" } + ) + row.button( + name: :cancel, + tag: :a, + label: I18n.t("button_cancel"), + scheme: :secondary, + href: @cancel_path, + data: { + turbo_method: :post, + turbo_stream: true + }, + test_selector: "type-form-configuration-group-cancel" + ) + row.submit( + name: :submit, + label: I18n.t("button_save"), + scheme: :primary, + test_selector: "type-form-configuration-group-save" + ) + end + end + + def initialize(cancel_path:) + super() + @cancel_path = cancel_path + end + + private + + def validation_message_for(attribute) + model.errors.messages_for(attribute).to_sentence.presence + end + end + end +end diff --git a/app/helpers/types_helper.rb b/app/helpers/types_helper.rb index 922a63eec72..9ade9394193 100644 --- a/app/helpers/types_helper.rb +++ b/app/helpers/types_helper.rb @@ -136,13 +136,12 @@ module ::TypesHelper def get_active_groups(type, available, inactive) type.attribute_groups.map do |group| { + key: group.key, type: group.group_type, name: group.translated_key, attributes: active_group_attributes_map(group, available, inactive), query: query_to_query_props(group) - }.tap do |group_obj| - group_obj[:key] = group.key if group.internal_key? - end + } end end diff --git a/app/helpers/work_packages_helper.rb b/app/helpers/work_packages_helper.rb index 9a1d0ddd663..4011e03baac 100644 --- a/app/helpers/work_packages_helper.rb +++ b/app/helpers/work_packages_helper.rb @@ -35,9 +35,9 @@ module WorkPackagesHelper # Displays a link to +work_package+ with its subject. # Examples: # - # link_to_work_package(package) # => Defect #6: This is the subject - # link_to_work_package(package, link_subject: true) # => Defect #6: This is the subject (everything within the link) - # link_to_work_package(package, display_project: true) # => Foo - Defect #6: This is the subject + # link_to_work_package(package) # => Defect : This is the subject + # link_to_work_package(package, link_subject: true) # => Defect : This is the subject (everything within the link) + # link_to_work_package(package, display_project: true) # => Foo - Defect : This is the subject def link_to_work_package(work_package, display_project: false, link_subject: false) # rubocop:disable Metrics/AbcSize output = ActiveSupport::SafeBuffer.new output << "#{work_package.project} - " if display_project && work_package.project_id @@ -47,7 +47,7 @@ module WorkPackagesHelper class: link_to_work_package_css_classes(work_package)) do link_parts = [] link_parts << work_package.type.to_s if work_package.type_id - link_parts << "##{work_package.id}:" + link_parts << "#{work_package.formatted_id}:" link_parts << content_tag(:span, I18n.t(:label_closed_work_packages), class: "sr-only") if work_package.closed? link_parts << work_package.subject if link_subject diff --git a/app/models/exports/pdf/common/common.rb b/app/models/exports/pdf/common/common.rb index 5d17e963970..fed4754959e 100644 --- a/app/models/exports/pdf/common/common.rb +++ b/app/models/exports/pdf/common/common.rb @@ -428,9 +428,9 @@ module Exports::PDF::Common::Common "#{make_link_href(href, caption)}" end - def get_id_column_cell(work_package, value) + def get_id_column_cell(work_package) href = url_helpers.work_package_url(work_package) - make_link_href_cell(href, value) + make_link_href_cell(href, work_package.display_id) end def get_subject_column_cell(work_package, value) @@ -469,7 +469,7 @@ module Exports::PDF::Common::Common def get_value_cell_by_column(work_package, column_name, format_subject) value = get_column_value(work_package, column_name) return get_cf_link_cell(value) if value.is_a?(::Exports::Formatters::LinkFormatter) - return get_id_column_cell(work_package, value) if column_name == :id + return get_id_column_cell(work_package) if column_name == :id return get_subject_column_cell(work_package, value) if format_subject && column_name == :subject escape_tags(value) diff --git a/app/models/exports/pdf/common/markdown.rb b/app/models/exports/pdf/common/markdown.rb index 6121610f74d..197ef474e11 100644 --- a/app/models/exports/pdf/common/markdown.rb +++ b/app/models/exports/pdf/common/markdown.rb @@ -35,6 +35,7 @@ module Exports::PDF::Common::Markdown include MarkdownToPDF::Core include MarkdownToPDF::Parser include Exports::PDF::Common::Common + include Exports::PDF::Common::WorkPackageMentions def initialize(styling_yml, pdf, hyphenation_language) @styles = MarkdownToPDF::Styles.new(styling_yml) @@ -94,45 +95,14 @@ module Exports::PDF::Common::Markdown wp_mention_macro(tag.attr("data-text") || "", tag.attr("data-id") || "", opts) end - def expand_wp_mention(work_package, content) - detail_level = content.count("#") - return content if detail_level == 1 - - # ##1234: {Type} #{ID}: {Subject} - content = "#{work_package.type} ##{work_package.id}: #{work_package.subject}" - return content if detail_level == 2 - - # ###1234: {Status} {Type} #{ID}: {Subject} ({Start Date} - {End Date}) - "#{work_package.status.name} #{content}#{work_package_dates(work_package)}" - end - def wp_mention_macro(content, id, opts) - id = id[/\d+/] return [text_hash(content, opts)] if id.blank? - work_package = WorkPackage.find_by(id: id) + work_package = WorkPackage.find_by_display_id(id) return [text_hash(content, opts)] unless work_package&.visible? content = expand_wp_mention(work_package, content) - [text_hash(content, opts.merge({ link: url_helpers.work_package_url(id) }))] - end - - def work_package_dates(work_package) - return "" if work_package.start_date.blank? && work_package.due_date.blank? - - if work_package.due_date.present? && work_package.start_date == work_package.due_date - return " (#{format_date(work_package.due_date)})" - end - - work_package_date_range(work_package) - end - - def work_package_date_range(work_package) - content = [ - work_package.start_date.present? ? format_date(work_package.start_date) : I18n.t("label_no_start_date"), - work_package.due_date.present? ? format_date(work_package.due_date) : I18n.t("label_no_due_date") - ].join(" - ") - " (#{content})" + [text_hash(content, opts.merge({ link: url_helpers.work_package_url(work_package) }))] end def handle_mention_html_tag(tag, node, opts) diff --git a/app/models/exports/pdf/common/work_package_mentions.rb b/app/models/exports/pdf/common/work_package_mentions.rb new file mode 100644 index 00000000000..06db3a0a7b1 --- /dev/null +++ b/app/models/exports/pdf/common/work_package_mentions.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Exports::PDF::Common::WorkPackageMentions + include Redmine::I18n + + def expand_wp_mention(work_package, content) + detail_level = content.count("#") + return work_package.formatted_id if detail_level == 1 + + # ##: {Type} {formatted_id}: {Subject} + content = "#{work_package.type} #{work_package.formatted_id}: #{work_package.subject}" + return content if detail_level == 2 + + # ###: {Status} {Type} {formatted_id}: {Subject} ({Start Date} - {End Date}) + "#{work_package.status.name} #{content}#{work_package_dates(work_package)}" + end + + def work_package_dates(work_package) + return "" if work_package.start_date.blank? && work_package.due_date.blank? + + if work_package.due_date.present? && work_package.start_date == work_package.due_date + return " (#{format_date(work_package.due_date)})" + end + + work_package_date_range(work_package) + end + + def work_package_date_range(work_package) + content = [ + work_package.start_date.present? ? format_date(work_package.start_date) : I18n.t("label_no_start_date"), + work_package.due_date.present? ? format_date(work_package.due_date) : I18n.t("label_no_due_date") + ].join(" - ") + " (#{content})" + end +end diff --git a/app/models/exports/pdf/components/gantt/gantt_builder.rb b/app/models/exports/pdf/components/gantt/gantt_builder.rb index 1d676164a64..aec81b947f0 100644 --- a/app/models/exports/pdf/components/gantt/gantt_builder.rb +++ b/app/models/exports/pdf/components/gantt/gantt_builder.rb @@ -742,7 +742,7 @@ module Exports::PDF::Components::Gantt # @param [WorkPackage] work_package # @return [String] def work_package_info_line(work_package) - "#{work_package.type} ##{work_package.id} • #{work_package.status} • #{work_package_info_line_date work_package}" + "#{work_package.type} #{work_package.formatted_id} • #{work_package.status} • #{work_package_info_line_date work_package}" end def work_package_info_line_date(work_package) diff --git a/app/models/journal.rb b/app/models/journal.rb index 89986f8337c..ebebd091bb8 100644 --- a/app/models/journal.rb +++ b/app/models/journal.rb @@ -59,6 +59,7 @@ class Journal < ApplicationRecord register_journal_formatter OpenProject::JournalFormatter::MeetingStartTime register_journal_formatter OpenProject::JournalFormatter::MeetingState register_journal_formatter OpenProject::JournalFormatter::MeetingWorkPackageId + register_journal_formatter OpenProject::JournalFormatter::ParticipantChange register_journal_formatter OpenProject::JournalFormatter::ProjectPhaseActive register_journal_formatter OpenProject::JournalFormatter::ProjectPhaseDates register_journal_formatter OpenProject::JournalFormatter::ProjectPhaseDefinition @@ -110,6 +111,7 @@ class Journal < ApplicationRecord belongs_to :data, polymorphic: true, dependent: :destroy has_many :agenda_item_journals, class_name: "Journal::MeetingAgendaItemJournal", dependent: :delete_all + has_many :participant_journals, class_name: "Journal::MeetingParticipantJournal", dependent: :delete_all has_many :attachable_journals, class_name: "Journal::AttachableJournal", dependent: :delete_all has_many :customizable_journals, class_name: "Journal::CustomizableJournal", dependent: :delete_all has_many :custom_comment_journals, class_name: "Journal::CustomCommentJournal", dependent: :delete_all @@ -223,12 +225,6 @@ class Journal < ApplicationRecord end end - private - - def has_file_links? - journable.respond_to?(:file_links) - end - def predecessor return @predecessor if defined?(@predecessor) @@ -242,4 +238,10 @@ class Journal < ApplicationRecord .first end end + + private + + def has_file_links? + journable.respond_to?(:file_links) + end end diff --git a/app/models/permitted_params.rb b/app/models/permitted_params.rb index d0a44db3c3a..6934800f519 100644 --- a/app/models/permitted_params.rb +++ b/app/models/permitted_params.rb @@ -235,7 +235,7 @@ class PermittedParams whitelisted = type_params.permit(*permitted) if type_params[:attribute_groups] - whitelisted[:attribute_groups] = JSON.parse(type_params[:attribute_groups]) + whitelisted[:attribute_groups] = type_params[:attribute_groups] end whitelisted diff --git a/app/models/projects/identifier.rb b/app/models/projects/identifier.rb index 0649ce411cf..c1527cf77c4 100644 --- a/app/models/projects/identifier.rb +++ b/app/models/projects/identifier.rb @@ -36,6 +36,10 @@ module Projects::Identifier # Classic identifier format: lowercase letters, digits, hyphens, underscores — but not all-numeric. CLASSIC_IDENTIFIER_FORMAT = /\A(?!\d+\z)[a-z0-9\-_]+\z/ + # Unanchored shape of a semantic project identifier ("PROJ", "MY_PROJECT_1"). + # Composed into `WorkPackage::SemanticIdentifier::SEMANTIC_ID_PATTERN`. + SEMANTIC_FORMAT = /[A-Z][A-Z0-9_]*/ + RESERVED_IDENTIFIERS = %w[new menu queries filters identifier_update_dialog identifier_suggestion].freeze included do @@ -43,31 +47,18 @@ module Projects::Identifier normalizes :identifier, with: OpenProject::RemoveInvisibleCharacters - # Generators - # There are two supported formats: - # 1. slug identifiers (e.g. "project_one"), generated by acts_as_url - # * work package ID = global ID (e.g. "#123") - # 2. semantic identifiers (e.g. "PROJ1"), generated by the :generate_semantic_identifier hook - # * work package ID = {project identifier + dash + project-local sequence number ID} (e.g. "PROJ1-123") - acts_as_url :name, - url_attribute: :identifier, - sync_url: false, # Don't update identifier when name changes - only_when_blank: true, # Only generate when identifier not set - limit: CLASSIC_IDENTIFIER_MAX_LENGTH, - blacklist: RESERVED_IDENTIFIERS, - adapter: OpenProject::ActsAsUrl::Adapter::OpActiveRecord, # use a custom adapter able to handle edge cases - skip_if: -> { Setting::WorkPackageIdentifier.semantic? } - - # Generate semantic identifier (when in the semantic mode) - before_validation :generate_semantic_identifier, - on: :create, - if: -> { Setting::WorkPackageIdentifier.semantic? && identifier.blank? } + # Generate identifier from name unless specified + before_validation on: :create, if: -> { identifier.blank? && name.present? } do + self.identifier = suggest_identifier + end + # Basic mode-agnostic identifier validation validates :identifier, presence: true, uniqueness: { case_sensitive: false }, if: ->(p) { p.persisted? || p.identifier.present? } + # Extended validation that runs checks specific to each format (classic & semantic) validates :identifier, "projects/identifier" => true, if: :identifier_changed? friendly_id :identifier, use: %i[finders history], slug_column: :identifier @@ -75,7 +66,7 @@ module Projects::Identifier # FriendlyId::Slugged adds after_validation :unset_slug_if_invalid, which reverts the # slug column to its previous value when validation fails. With slug_column: :identifier, # this would reset a manually-set identifier back to nil on new records. Since the - # identifier is managed by acts_as_url and user input (not FriendlyId's slug generator), + # identifier is managed by our own callbacks and user input (not FriendlyId's slug generator), # we disable this behaviour entirely. # Must be inside `included` to override FriendlyId::Slugged in the MRO. def unset_slug_if_invalid; end @@ -121,6 +112,11 @@ module Projects::Identifier FriendlyId::Slug.where(sluggable_type: name).extending(IdentifierSlugScopes) end + # There are two supported formats: + # 1. slug identifiers (e.g. "project_one"), generated by ClassicIdentifierSuggestionGenerator + # * work package ID = global ID (e.g. "#123") + # 2. semantic identifiers (e.g. "PROJ1"), generated by ProjectIdentifierSuggestionGenerator + # * work package ID = {project identifier + dash + project-local sequence number ID} (e.g. "PROJ1-123") def suggest_identifier(name, mode: Setting[:work_packages_identifier]) if mode == Setting::WorkPackageIdentifier::SEMANTIC exclude = ProjectIdentifiers::IdentifierAutofix::ProblematicIdentifiers.reserved_identifiers @@ -137,14 +133,9 @@ module Projects::Identifier end # Override the `validation_context` getter to include the `default_validation_context` when the - # context is `:saving_custom_fields`. This is required, because the `acts_as_url` plugin from - # `stringex` defines a callback on the `:create` context for initialising the `identifier` field. - # Providing a custom context while creating the project, will not execute the callbacks on the - # `:create` or `:update` contexts, meaning the identifier will not get initialised. - # In order to initialise the identifier, the `default_validation_context` (`:create`, or `:update`) - # should be included when validating via the `:saving_custom_fields`. This way every create - # or update callback will also be executed alongside the `:saving_custom_fields` callbacks. - # This problem does not affect the contextless callbacks, they are always executed. + # context is `:saving_custom_fields`. Our identifier-generation callbacks fire on `:create`, so + # providing only a custom context would skip them, leaving identifier blank on new records. + # Including the default context ensures `:create` callbacks run alongside `:saving_custom_fields`. def validation_context case Array(super) in [*, :saving_custom_fields, *] => context @@ -154,11 +145,4 @@ module Projects::Identifier end end - private - - def generate_semantic_identifier - return if name.blank? - - self.identifier = self.class.suggest_identifier(name) - end end diff --git a/app/models/queries/users/orders/default_order.rb b/app/models/queries/users/orders/default_order.rb index 7a30b6e0ae4..64c55796eb0 100644 --- a/app/models/queries/users/orders/default_order.rb +++ b/app/models/queries/users/orders/default_order.rb @@ -32,6 +32,6 @@ class Queries::Users::Orders::DefaultOrder < Queries::Orders::Base self.model = User def self.key - /\A(id|lastname|firstname|mail|login)\z/ + /\A(id|lastname|firstname|mail|login|admin|created_at|last_login_on)\z/ end end diff --git a/app/models/queries/users/selects/default.rb b/app/models/queries/users/selects/default.rb new file mode 100644 index 00000000000..3a73617063b --- /dev/null +++ b/app/models/queries/users/selects/default.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +class Queries::Users::Selects::Default < Queries::Selects::Base + KEYS = %i[login firstname lastname mail admin created_at last_login_on].freeze + + def self.key + /\A(#{Regexp.union(KEYS.map(&:to_s))})\z/ + end + + def self.all_available + KEYS.map { new(it) } + end + + def caption + User.human_attribute_name(attribute) + end +end diff --git a/app/models/type/attribute_group.rb b/app/models/type/attribute_group.rb index 6e742da4a71..bc88702058d 100644 --- a/app/models/type/attribute_group.rb +++ b/app/models/type/attribute_group.rb @@ -47,7 +47,8 @@ class Type::AttributeGroup < Type::FormGroup other.is_a?(self.class) && key == other.key && type == other.type && - attributes == other.attributes + attributes == other.attributes && + display_name == other.display_name end def active_members(project) diff --git a/app/models/type/attribute_groups.rb b/app/models/type/attribute_groups.rb index 74bdbcef5dd..9277c83d50c 100644 --- a/app/models/type/attribute_groups.rb +++ b/app/models/type/attribute_groups.rb @@ -190,14 +190,15 @@ module Type::AttributeGroups attributes = group[1] first_attribute = attributes[0] key = group[0] + display_name = group[2] if group.length > 2 if first_attribute.is_a?(Query) - new_query_group(key, first_attribute) + new_query_group(key, first_attribute, display_name:) elsif first_attribute.is_a?(Symbol) && Type::QueryGroup.query_attribute?(first_attribute) query = Query.find_by(id: Type::QueryGroup.query_attribute_id(first_attribute)) - new_query_group(key, query) + new_query_group(key, query, display_name:) else - new_attribute_group(key, attributes) + new_attribute_group(key, attributes, display_name:) end end end @@ -213,16 +214,18 @@ module Type::AttributeGroups else group.attributes end - [group.key, attributes] + result = [group.key, attributes] + result << group.display_name if group.display_name.present? + result end end - def new_attribute_group(key, attributes) - Type::AttributeGroup.new(self, key, attributes) + def new_attribute_group(key, attributes, display_name: nil) + Type::AttributeGroup.new(self, key, attributes, display_name:) end - def new_query_group(key, query) - Type::QueryGroup.new(self, key, query) + def new_query_group(key, query, display_name: nil) + Type::QueryGroup.new(self, key, query, display_name:) end def cleanup_query_groups_queries @@ -231,7 +234,7 @@ module Type::AttributeGroups new_groups = self[:attribute_groups] old_groups = attribute_groups_was - ids = (old_groups.map(&:last).flatten - new_groups.map(&:last).flatten) + ids = (old_groups.map { |g| g[1] }.flatten - new_groups.map { |g| g[1] }.flatten) .filter_map { |k| ::Type::QueryGroup.query_attribute_id(k) } Query.where(id: ids).destroy_all diff --git a/app/models/type/form_group.rb b/app/models/type/form_group.rb index dbdb95d27af..115447c80d1 100644 --- a/app/models/type/form_group.rb +++ b/app/models/type/form_group.rb @@ -31,12 +31,27 @@ class Type::FormGroup attr_accessor :key, :attributes, - :type + :type, + :display_name - def initialize(type, key, attributes) + def self.next_untitled_key(seen_keys) + base_name = I18n.t("types.edit.form_configuration.untitled_group") + candidate = base_name + suffix = 2 + + while seen_keys.include?(candidate) + candidate = "#{base_name} #{suffix}" + suffix += 1 + end + + candidate + end + + def initialize(type, key, attributes, display_name: nil) self.key = key self.attributes = attributes self.type = type + self.display_name = display_name end ## @@ -49,10 +64,14 @@ class Type::FormGroup # Translate the given attribute group if its internal # (== if it's a symbol) def translated_key - if internal_key? + if display_name.present? + display_name + elsif internal_key? I18n.t(Type.default_groups[key], default: key.to_s) - else + elsif key.present? key + else + I18n.t("types.edit.form_configuration.untitled_group") end end diff --git a/app/models/user.rb b/app/models/user.rb index 5ccc10b72d8..eb2a27050c7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -112,6 +112,18 @@ class User < Principal has_many :reminders, foreign_key: "creator_id", dependent: :destroy, inverse_of: :creator has_many :remote_identities, dependent: :destroy + # Resource allocations assigned to this user. Normal user-deletion goes + # through Principals::DeleteJob, which rewrites principal_id to a + # DeletedUser placeholder before destroy fires (registered in the + # resource_management engine). The `dependent: :nullify` here is a + # defensive fallback if a user is destroyed outside that flow — the column + # is already nullable for the unassigned/filter-only state. + has_many :resource_allocations, + class_name: "ResourceAllocation", + foreign_key: :principal_id, + dependent: :nullify, + inverse_of: :principal + # Users blocked via brute force prevention # use lambda here, so time is evaluated on each query scope :blocked, -> { create_blocked_scope(self, true) } diff --git a/app/models/user_queries/static.rb b/app/models/user_queries/static.rb new file mode 100644 index 00000000000..e1293d55284 --- /dev/null +++ b/app/models/user_queries/static.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +class UserQueries::Static + DEFAULT = "active" + + class << self + def query(id) + case id + when DEFAULT, nil + static_query_active + end + end + + private + + def static_query_active + UserQuery.new(name: I18n.t(:status_active)) do |query| + query.where("status", "=", "active") + query.select(*Queries::Users::Selects::Default::KEYS, add_not_existing: false) + query.clear_changes_information + end + end + end +end diff --git a/app/models/user_query.rb b/app/models/user_query.rb index 19c4c32ce60..abebfac7a20 100644 --- a/app/models/user_query.rb +++ b/app/models/user_query.rb @@ -29,13 +29,15 @@ #++ class UserQuery < PersistedQuery + scope :visible, ->(user = User.current) { where(principal: user) } + def self.model User end def default_scope # Excludes the SystemUser, DeletedUser, AnonymousUser STI descendants of User. - User.user + User.user.visible end register_query do @@ -52,6 +54,7 @@ class UserQuery < PersistedQuery order Queries::Users::Orders::GroupOrder order Queries::Users::Orders::CustomFieldOrder + select Queries::Users::Selects::Default select Queries::Users::Selects::CustomField end end diff --git a/app/models/work_package.rb b/app/models/work_package.rb index bb8057560ed..d4a110bbdd4 100644 --- a/app/models/work_package.rb +++ b/app/models/work_package.rb @@ -276,7 +276,7 @@ class WorkPackage < ApplicationRecord end def to_s - "#{type.name unless type.is_standard} ##{id}: #{subject}" + "#{type.name unless type.is_standard} #{formatted_id}: #{subject}" end def infoline(show_standard_type: true) diff --git a/app/models/work_package/pdf_export/document_generator.rb b/app/models/work_package/pdf_export/document_generator.rb index 24ff567ad66..e9bf70d064e 100644 --- a/app/models/work_package/pdf_export/document_generator.rb +++ b/app/models/work_package/pdf_export/document_generator.rb @@ -83,7 +83,7 @@ class WorkPackage::PDFExport::DocumentGenerator < Exports::Exporter def title # ____.pdf build_pdf_filename([work_package.project, work_package.type, - "##{work_package.id}", work_package.subject].join("_")) + work_package.display_id, work_package.subject].join("_")) end def with_images? diff --git a/app/models/work_package/pdf_export/generator/generator.rb b/app/models/work_package/pdf_export/generator/generator.rb index aaf20aa824d..f199aa22031 100644 --- a/app/models/work_package/pdf_export/generator/generator.rb +++ b/app/models/work_package/pdf_export/generator/generator.rb @@ -36,6 +36,7 @@ module WorkPackage::PDFExport::Generator::Generator include MarkdownToPDF::Core include MarkdownToPDF::Parser include MarkdownToPDF::StyleSchema + include Exports::PDF::Common::WorkPackageMentions def initialize(styling_yml) symbol_yml = symbolize(styling_yml) @@ -94,17 +95,40 @@ module WorkPackage::PDFExport::Generator::Generator @hyphens.hyphenate(text) end + def handle_wp_mention_html_tag(tag, node, opts) + # #185 + # ##185 + # ###185 + next_node = node&.next + if next_node && next_node.type == :text && next_node.respond_to?(:string_content) + next_node.string_content = "" + end + render_wp_mention(tag.attr("data-text") || "", tag.attr("data-id") || "", opts) + end + + def render_wp_mention(content, id, opts) + return [text_hash(content, opts)] if id.blank? + + work_package = WorkPackage.find_by_display_id(id) + return [text_hash(content, opts)] unless work_package&.visible? + + [text_hash(expand_wp_mention(work_package, content), opts)] + end + def handle_mention_html_tag(tag, node, opts) - if tag.text.blank? - # + if tag.attr("data-type") == "work_package" + handle_wp_mention_html_tag(tag, node, opts) + elsif tag.text.blank? # text = tag.attr("data-text") if text.present? && !node.next.respond_to?(:string_content) && node.next.string_content != text return [text_hash(text, opts)] end + [] + else + # @Some User + [] end - # @Some User - [] end def handle_unknown_inline_html_tag(tag, node, opts) diff --git a/app/models/work_package/pdf_export/work_package_to_pdf.rb b/app/models/work_package/pdf_export/work_package_to_pdf.rb index 541dcb1203a..ebb0759a769 100644 --- a/app/models/work_package/pdf_export/work_package_to_pdf.rb +++ b/app/models/work_package/pdf_export/work_package_to_pdf.rb @@ -108,7 +108,7 @@ class WorkPackage::PDFExport::WorkPackageToPdf < Exports::Exporter end def heading - "#{work_package.type} ##{work_package.id} - #{work_package.subject}" + "#{work_package.type} #{work_package.formatted_id} - #{work_package.subject}" end def footer_title @@ -118,7 +118,7 @@ class WorkPackage::PDFExport::WorkPackageToPdf < Exports::Exporter def title # ____.pdf build_pdf_filename([work_package.project, work_package.type, - "##{work_package.id}", work_package.subject].join("_")) + work_package.display_id, work_package.subject].join("_")) end def write_description!(work_package) diff --git a/app/models/work_package/semantic_identifier.rb b/app/models/work_package/semantic_identifier.rb index a1608d74860..7346676c69c 100644 --- a/app/models/work_package/semantic_identifier.rb +++ b/app/models/work_package/semantic_identifier.rb @@ -31,10 +31,15 @@ module WorkPackage::SemanticIdentifier extend ActiveSupport::Concern + # Semantic-identifier shape ("PROJ-42"). Use this when the numeric and + # semantic branches need different boundary rules; use `ID_ROUTE_CONSTRAINT` + # when both branches share one regex. + SEMANTIC_ID_PATTERN = /#{Projects::Identifier::SEMANTIC_FORMAT.source}-\d+/ + # Matches either a numeric ID ("12345") or a semantic identifier ("PROJ-42"). # Used in Rails route constraints so both forms are accepted in URLs. # The frontend equivalent lives in WP_ID_URL_PATTERN (work-package-id-pattern.ts). - ID_ROUTE_CONSTRAINT = /\d+|[A-Z][A-Z0-9_]*-\d+/ + ID_ROUTE_CONSTRAINT = /\d+|#{SEMANTIC_ID_PATTERN.source}/ # Raised when a finder is invoked in a way that cannot resolve a semantic # identifier — e.g. find_by(id: "PROJ-42") which reduces to a raw SQL @@ -79,6 +84,41 @@ module WorkPackage::SemanticIdentifier end end + # Returns true when value looks like a semantic work package identifier + # ("PROJ-42"). Non-strings (Integer, Hash, nil, Array), empty and numeric strings + # ("123", " 456 ", " ") return false — these fall through to standard PK lookup. + # + # The round-trip check (rather than a regex) is intentional for performance. + # Every value that reaches a work-package finder either parses as an integer + # or doesn't, and that's enough to dispatch correctly. Don't tighten it. + def self.semantic_id?(value) + return false unless value.is_a?(String) + + stripped = value.strip + return false if stripped.empty? + + stripped.to_i.to_s != stripped + end + + # Returns true when value is a canonical numeric ID — + # an Integer, or a String that round-trips through `to_i.to_s` ("0", "123"). + # Rejects leading-zero strings ("0123"), non-numeric strings, empty strings and nil. + # + # A numeric ID is always a routable primary key; a semantic ID is always + # routed through the identifier/alias path. Anything else — nil, blank + # strings, Hashes, Arrays — is neither, and both predicates return false + # so the caller short-circuits before any lookup. + def self.numeric_id?(value) + case value + when Integer then true + when String + return false if value.strip.blank? + + !semantic_id?(value) + else false + end + end + # Returns the user-facing identifier for this work package. # In semantic mode: the project-based identifier (e.g. "PROJ-42") # In classic mode: the numeric database ID diff --git a/app/models/work_package/semantic_identifier/finder_methods.rb b/app/models/work_package/semantic_identifier/finder_methods.rb index d03c7904b8e..d8557760fb9 100644 --- a/app/models/work_package/semantic_identifier/finder_methods.rb +++ b/app/models/work_package/semantic_identifier/finder_methods.rb @@ -110,8 +110,13 @@ module WorkPackage::SemanticIdentifier::FinderMethods # historical alias matches one of the supplied display ids. Numeric and # semantic strings may be freely mixed; unknown values produce no match # rather than poisoning the rest of the set. - def where_display_id_in(values) - values = Array(values).map(&:to_s) + # + # @param values [String, Integer, Array] one or more + # display ids. Pass scalars (`where_display_id_in("PROJ-1")`), varargs + # (`where_display_id_in("PROJ-1", "PROJ-2")`), or a pre-built array + # (`where_display_id_in(ids)`) interchangeably. + def where_display_id_in(*values) + values = values.flatten(1).compact_blank.map(&:to_s) return none if values.empty? semantic, numeric = values.partition { semantic_id?(it) } @@ -152,14 +157,8 @@ module WorkPackage::SemanticIdentifier::FinderMethods end end - # Returns true when value looks like a semantic work package identifier (e.g. "PROJ-42"). - # Non-string values (Integer, Hash, nil, Array) and numeric strings ("123", " 456 ") - # return false — these fall through to standard ActiveRecord lookup. def semantic_id?(value) - return false unless value.is_a?(String) - - stripped = value.strip - stripped.to_i.to_s != stripped + WorkPackage::SemanticIdentifier.semantic_id?(value) end def find_by_semantic_identifier(identifier) diff --git a/app/models/work_package_types/form_configuration/group_form_model.rb b/app/models/work_package_types/form_configuration/group_form_model.rb new file mode 100644 index 00000000000..b0217d17a7e --- /dev/null +++ b/app/models/work_package_types/form_configuration/group_form_model.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfiguration + class GroupFormModel + include ActiveModel::Model + include ActiveModel::Attributes + include Tableless + + attribute :name, :string + attribute :group_type, :string + attribute :query, :string + attribute :key, :string + attribute :temporary, :boolean, default: false + + def self.model_name + ActiveModel::Name.new(self, nil, "Group") + end + + def self.from_group(group, name: group[:name], validation_message: nil) + new( + name:, + group_type: group[:type], + query: group[:query], + key: group[:key], + temporary: group[:temporary] + ).tap do |form_model| + form_model.errors.add(:name, validation_message) if validation_message.present? + end + end + end + end +end diff --git a/app/services/journals/create_service.rb b/app/services/journals/create_service.rb index 7f9650795af..7e73dc95095 100644 --- a/app/services/journals/create_service.rb +++ b/app/services/journals/create_service.rb @@ -566,7 +566,9 @@ module Journals end def within_aggregation_time?(predecessor) - predecessor.updated_at >= (Time.zone.now - Setting.journal_aggregation_time_minutes.to_i.minutes) + minutes = journable.class.try(:journal_aggregation_time_minutes) || + Setting.journal_aggregation_time_minutes.to_i + predecessor.updated_at >= (Time.zone.now - minutes.minutes) end def same_user?(predecessor) diff --git a/app/services/project_identifiers/classic_identifier_suggestion_generator.rb b/app/services/project_identifiers/classic_identifier_suggestion_generator.rb index ca74eea1687..f4513d2e513 100644 --- a/app/services/project_identifiers/classic_identifier_suggestion_generator.rb +++ b/app/services/project_identifiers/classic_identifier_suggestion_generator.rb @@ -29,12 +29,13 @@ #++ module ProjectIdentifiers - # Generates a unique classic-format (acts_as_url-style) identifier from a project name, - # mirroring acts_as_url's own duplicate loop: appends -1, -2, … until a free slug is found. + # Generates a unique classic-format identifier from a project name. + # Appends -1, -2, … until a free slug is found. # # Instantiate once to load the taken-identifier set from the DB, then call +suggest_identifier+. class ClassicIdentifierSuggestionGenerator FALLBACK_BASE = "project" + BLANK_SLUG_SUBSTITUTIONS = { "." => "dot", "!" => "bang" }.freeze def initialize(project: nil) @exclude = taken_identifiers(project:) @@ -49,9 +50,9 @@ module ProjectIdentifiers .find { |slug| Project.classic_identifier_format?(slug) } end - # Generates a unique classic-format identifier from +name+, mirroring acts_as_url's - # duplicate loop: appends -1, -2, … until a slug not in the taken set is found. - # Falls back to a randomised +FALLBACK_BASE+ slug when +name+ produces a blank slug. + # Generates a unique classic-format identifier from +name+. + # Appends -1, -2, … until a slug not in the taken set is found. + # Falls back to a randomised +FALLBACK_BASE+ slug when +name+ produces no valid slug. def suggest_identifier(name) base = slugify(name) || fallback_base @@ -68,7 +69,9 @@ module ProjectIdentifiers private def slugify(name) - name.to_url.first(Projects::Identifier::CLASSIC_IDENTIFIER_MAX_LENGTH).presence + slug = name.to_url.first(Projects::Identifier::CLASSIC_IDENTIFIER_MAX_LENGTH).presence + slug ||= BLANK_SLUG_SUBSTITUTIONS[name] + slug if slug && Projects::Identifier::CLASSIC_IDENTIFIER_FORMAT.match?(slug) end def fallback_base diff --git a/app/services/project_identifiers/convert_project_to_semantic_service.rb b/app/services/project_identifiers/convert_project_to_semantic_service.rb index 788f120665f..226c2fe9515 100644 --- a/app/services/project_identifiers/convert_project_to_semantic_service.rb +++ b/app/services/project_identifiers/convert_project_to_semantic_service.rb @@ -80,8 +80,11 @@ module ProjectIdentifiers raise "Generated identifier is blank for project #{project.id}" if new_identifier.blank? project.identifier = new_identifier - # Save with the validation context that allows to save semantic ID while system is in classic mode - project.save!(context: :semantic_conversion) + # Save with the validation context that allows to save semantic ID while system is in classic mode. + # Suppress notifications: this is a background system operation, not a user edit. + Journal::NotificationConfiguration.with(false) do + project.save!(context: :semantic_conversion) + end end def reset_stale_identifiers diff --git a/app/services/project_identifiers/revert_project_to_classic_service.rb b/app/services/project_identifiers/revert_project_to_classic_service.rb index 7a1045d574e..718d3321409 100644 --- a/app/services/project_identifiers/revert_project_to_classic_service.rb +++ b/app/services/project_identifiers/revert_project_to_classic_service.rb @@ -53,7 +53,10 @@ module ProjectIdentifiers def restore_classic_identifier generator = ProjectIdentifiers::ClassicIdentifierSuggestionGenerator.new classic = generator.restore_identifier(project) || generator.suggest_identifier(project.name) - project.update!(identifier: classic) + # Suppress notifications: this is a background system operation, not a user edit. + Journal::NotificationConfiguration.with(false) do + project.update!(identifier: classic) + end end end end diff --git a/app/services/user_queries/set_attributes_service.rb b/app/services/user_queries/set_attributes_service.rb new file mode 100644 index 00000000000..88140e9e56c --- /dev/null +++ b/app/services/user_queries/set_attributes_service.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +class UserQueries::SetAttributesService < BaseServices::SetAttributes + private + + def set_attributes(params) + set_filters(params.delete(:filters)) + set_order(params.delete(:orders)) + set_select(params.delete(:selects)) + + super + end + + def set_default_attributes(_params) + set_default_selects + end + + def set_default_selects + return if model.selects.any? + + model.select(*Queries::Users::Selects::Default::KEYS, add_not_existing: false) + end + + def set_filters(filters) + return unless filters + + model.filters.clear + filters.each do |filter| + model.where(filter[:attribute], filter[:operator], filter[:values]) + end + end + + def set_select(selects) + return unless selects + + model.selects.clear + model.select(*selects, add_not_existing: false) + end + + def set_order(orders) + return unless orders + + model.orders.clear + model.order(orders.to_h { |o| [o[:attribute], o[:direction]] }) + end +end diff --git a/app/services/work_package_types/attribute_groups/transformer.rb b/app/services/work_package_types/attribute_groups/transformer.rb index e367cdc70b6..b9e2d9ecd8e 100644 --- a/app/services/work_package_types/attribute_groups/transformer.rb +++ b/app/services/work_package_types/attribute_groups/transformer.rb @@ -37,15 +37,23 @@ module WorkPackageTypes end def call - return [] if groups.blank? + return ServiceResult.success(result: []) if groups.blank? - groups.map do |group| - if group[:type] == "query" - transform_query_group(group) - else - transform_attribute_group(group) - end + transformed_groups = [] + + groups.each do |group| + transformed_group = if group[:type] == "query" + transform_query_group(group) + else + transform_attribute_group(group) + end + + return transformed_group if transformed_group.is_a?(ServiceResult) + + transformed_groups << transformed_group end + + ServiceResult.success(result: transformed_groups) end private @@ -53,28 +61,43 @@ module WorkPackageTypes attr_reader :groups, :user def transform_attribute_group(group) - name = group[:key]&.to_sym || group[:name] attributes = group[:attributes].pluck(:key) - [name, attributes] + return [group[:name], attributes] if group[:key].blank? + + build_default_attribute_group(group, attributes) end def transform_query_group(group) name = group[:name] - props = JSON.parse(group[:query]) + result = ::WorkPackageTypes::FormConfiguration::EmbeddedQueryBuilder.build( + query_props: group[:query], + name: "Embedded table: #{name}", + user: + ) - query = Query.new_default(name: "Embedded table: #{name}") - query.extend(OpenProject::ChangedBySystem) - query.change_by_system { query.user = User.system } + return result if result.failure? - result = ::API::V3::UpdateQueryFromV3ParamsService - .new(query, user) - .call(props.with_indifferent_access) + [name, [result.result]] + end - if result.success? - query.show_hierarchies = false - [name, [query]] - end + def build_default_attribute_group(group, attributes) + key = group[:key].to_sym + display_name = customized_display_name(group, key) + result = [key, attributes] + result << display_name if display_name.present? + result + end + + def customized_display_name(group, key) + return if group[:name].blank? + + group[:name] unless group[:name] == default_group_name(key) + end + + def default_group_name(key) + label = Type.default_groups[key] + label ? I18n.t(label, default: key.to_s) : key.to_s end end end diff --git a/app/services/work_package_types/form_configuration/concern.rb b/app/services/work_package_types/form_configuration/concern.rb new file mode 100644 index 00000000000..801cb3f055b --- /dev/null +++ b/app/services/work_package_types/form_configuration/concern.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfiguration + module Concern + extend ActiveSupport::Concern + + def initialize(user:, type:, **) + super() + @user = user + @type = type + end + + private + + attr_reader :type, + :user + + def active_groups + type.attribute_groups.reject { |group| group.key.to_s == "__empty" } + end + + def find_group(group_key) + active_groups.find { |group| group_identifier_match?(group, group_key) } + end + + def find_attribute_group(group_key) + type.attribute_groups.find do |group| + group.group_type == :attribute && group_identifier_match?(group, group_key) + end + end + + def find_row(row_key) + active_groups + .select { |group| group.group_type == :attribute } + .each do |group| + index = group.attributes.index { |attribute| attribute_identifier_match?(attribute, row_key) } + return { group:, index: } if index + end + + nil + end + + def group_identifier_match?(group, identifier) + expected = identifier.to_s.strip + + [ + group.key, + group.display_name, + group.translated_key + ].compact.map { |value| value.to_s.strip }.include?(expected) + end + + def attribute_identifier_match?(attribute, identifier) + attribute.to_s.strip == identifier.to_s.strip + end + + def persist_groups(groups) + assign_groups(groups) + return contract_failure unless form_configuration_contract.validate + + persist_type + end + + def failure_with_message(message) + type.errors.clear + type.errors.add(:base, message) + + ServiceResult.failure(result: type, errors: type.errors) + end + + def build_query(query_props, name:) + ::WorkPackageTypes::FormConfiguration::EmbeddedQueryBuilder.build(query_props:, name:, user:) + end + + def normalized_groups(groups) + groups = groups + .reject { |group| group.key.to_s == "__empty" } + seen_keys = groups.filter_map(&:key).compact_blank.map(&:to_s) + groups = groups.map { |group| normalize_group(group, seen_keys:) } + + if groups.empty? + [::Type::AttributeGroup.new(type, :__empty, [])] + else + groups + end + end + + def normalize_group(group, seen_keys:) + return group if group.key.present? + + group.key = next_untitled_group_name(seen_keys) + group + end + + def next_untitled_group_name(seen_keys) + Type::FormGroup.next_untitled_key(seen_keys).tap { |key| seen_keys << key } + end + + def sync_active_custom_fields! + type.custom_field_ids = active_groups + .select { |group| group.group_type == :attribute } + .flat_map(&:members) + .filter_map do |attribute| + next unless CustomField.custom_field_attribute?(attribute) + + attribute.delete_prefix("custom_field_").to_i + end + .uniq + end + + def assign_groups(groups) + type.attribute_groups_will_change! + type.attribute_groups_objects = normalized_groups(groups) + sync_active_custom_fields! + end + + def form_configuration_contract + @form_configuration_contract ||= ::WorkPackageTypes::UpdateFormConfigurationContract.new(type, user, options: {}) + end + + def contract_failure + ServiceResult.failure(result: type, errors: form_configuration_contract.errors) + end + + def persist_type + if type.save + ServiceResult.success(result: type) + else + ServiceResult.failure(result: type, errors: type.errors) + end + end + end + end +end diff --git a/app/services/work_package_types/form_configuration/embedded_query_builder.rb b/app/services/work_package_types/form_configuration/embedded_query_builder.rb new file mode 100644 index 00000000000..2aedb279cf7 --- /dev/null +++ b/app/services/work_package_types/form_configuration/embedded_query_builder.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfiguration + module EmbeddedQueryBuilder + module_function + + def build(query_props:, name:, user:) + query = Query.new_default(name:) + query.filters = [] + assign_system_user(query) + + query_call = ::API::V3::UpdateQueryFromV3ParamsService + .new(query, user) + .call(query_parameters(query_props), valid_subset: true) + + return query_call if query_call.failure? + + query.show_hierarchies = false + query_call + rescue JSON::ParserError + invalid_query_result + end + + def rebuild(query:, user:) + return invalid_query_result if query.nil? + + build( + query_props: ::API::V3::Queries::QueryParamsRepresenter.new(query).to_h, + name: query.name, + user: + ) + end + + def query_parameters(query_props) + return {} if query_props.blank? + if query_props.is_a?(String) + return JSON.parse(query_props).deep_symbolize_keys + end + + params = query_props.respond_to?(:to_unsafe_h) ? query_props.to_unsafe_h : query_props + params.deep_symbolize_keys + end + + def assign_system_user(query) + query.extend(OpenProject::ChangedBySystem) + query.change_by_system { query.user = User.system } + end + + def invalid_query_result + errors = Query.new.errors + errors.add(:base, I18n.t("types.edit.form_configuration.invalid_query")) + + ServiceResult.failure(errors:) + end + end + end +end diff --git a/app/services/work_package_types/form_configuration_groups/create_service.rb b/app/services/work_package_types/form_configuration_groups/create_service.rb new file mode 100644 index 00000000000..3ee9937e5fe --- /dev/null +++ b/app/services/work_package_types/form_configuration_groups/create_service.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfigurationGroups + class CreateService < ::BaseServices::BaseCallable + include ::WorkPackageTypes::FormConfiguration::Concern + + def perform + name = resolve_group_name + + if query_group? + query_call = build_query(params[:query_props], name: "Embedded table: #{name}") + return query_call if query_call.failure? + end + + group = build_group(name, query_result: query_call&.result) + + groups = active_groups + groups.unshift(group) + + persist_groups(groups).tap do |call| + call.result = group if call.success? + end + end + + private + + def resolve_group_name + name = params[:name].to_s.strip + return name if name.present? + + seen_keys = active_groups.map { |g| g.key.to_s } + Type::FormGroup.next_untitled_key(seen_keys) + end + + def query_group? + params[:group_type].to_s == "query" + end + + def build_group(name, query_result: nil) + if query_result + ::Type::QueryGroup.new(type, name, query_result) + else + ::Type::AttributeGroup.new(type, name, []) + end + end + end + end +end diff --git a/app/services/work_package_types/form_configuration_groups/delete_service.rb b/app/services/work_package_types/form_configuration_groups/delete_service.rb new file mode 100644 index 00000000000..a2851cbdb9e --- /dev/null +++ b/app/services/work_package_types/form_configuration_groups/delete_service.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfigurationGroups + class DeleteService < ::BaseServices::BaseCallable + include ::WorkPackageTypes::FormConfiguration::Concern + + def initialize(user:, type:, group_key:) + super(user:, type:) + @group_key = group_key + end + + def perform + group = find_group(@group_key) + return failure_with_message(I18n.t("types.edit.form_configuration.not_found")) unless group + + groups = active_groups.reject { |group| group.key.to_s == @group_key.to_s } + + persist_groups(groups).tap do |call| + call.result = group if call.success? + end + end + end + end +end diff --git a/app/services/work_package_types/form_configuration_groups/update_service.rb b/app/services/work_package_types/form_configuration_groups/update_service.rb new file mode 100644 index 00000000000..b5999b3e37b --- /dev/null +++ b/app/services/work_package_types/form_configuration_groups/update_service.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfigurationGroups + class UpdateService < ::BaseServices::BaseCallable + include ::WorkPackageTypes::FormConfiguration::Concern + + def initialize(user:, type:, group_key:) + super(user:, type:) + @group_key = group_key + end + + def perform + group = find_group(@group_key) + return failure_with_message(I18n.t("types.edit.form_configuration.not_found")) unless group + + groups = active_groups + update_result = perform_update(group, groups) + return update_result if update_result.is_a?(ServiceResult) && update_result.failure? + + persist_groups(groups).tap do |call| + call.result = group if call.success? + end + end + + private + + def perform_update(group, groups) + return move_group(groups, move_to: params[:move_to], position: params[:position]) if move_requested? + return update_query(group, params[:query_props]) if params[:query_props].present? + + rename_group!(group, params[:name]) + end + + def move_group(groups, move_to:, position:) + current_index = groups.index { |group| group.key.to_s == @group_key.to_s } + return if current_index.nil? + + new_index = group_move_index(groups:, current_index:, move_to:, position:) + + groups.insert(new_index, groups.delete_at(current_index)) if new_index != current_index + end + + def rename_group!(group, name) + stripped_name = name.to_s.strip + return blank_name_error if stripped_name.blank? + + rename_group(group, stripped_name) + nil + end + + def rename_group(group, name) + if group.internal_key? + group.display_name = name.presence == default_name_for(group) ? nil : name.presence + else + group.key = name + group.display_name = nil + end + end + + def default_name_for(group) + I18n.t(Type.default_groups[group.key], default: group.key.to_s) + end + + def move_requested? + params[:move_to].present? || params[:position].present? + end + + def update_query(group, query_props) + query_call = build_query(query_props, name: group.query&.name || "Embedded table: #{@group_key}") + return query_call if query_call.failure? + + group.attributes = query_call.result + nil + end + + def group_move_index(groups:, current_index:, move_to:, position:) + return (position.to_i - 1).clamp(0, groups.length - 1) if position.present? + + case move_to&.to_sym + when :highest + 0 + when :higher + [current_index - 1, 0].max + when :lower + [current_index + 1, groups.length - 1].min + when :lowest + groups.length - 1 + else + current_index + end + end + + def blank_name_error + failure_with_message( + I18n.t("activerecord.errors.models.type.attributes.attribute_groups.group_without_name") + ) + end + end + end +end diff --git a/app/services/work_package_types/form_configuration_rows/delete_service.rb b/app/services/work_package_types/form_configuration_rows/delete_service.rb new file mode 100644 index 00000000000..9c4214bc628 --- /dev/null +++ b/app/services/work_package_types/form_configuration_rows/delete_service.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfigurationRows + class DeleteService < ::BaseServices::BaseCallable + include ::WorkPackageTypes::FormConfiguration::Concern + + def initialize(user:, type:, row_key:) + super(user:, type:) + @row_key = row_key + end + + def perform + row = find_row(@row_key) + return failure_with_message(I18n.t("types.edit.form_configuration.not_found")) unless row + + attributes = row[:group].attributes.dup + attributes.delete_at(row[:index]) + row[:group].attributes = attributes + + persist_groups(active_groups).tap do |call| + call.result = row[:group] if call.success? + end + end + end + end +end diff --git a/app/services/work_package_types/form_configuration_rows/update_service.rb b/app/services/work_package_types/form_configuration_rows/update_service.rb new file mode 100644 index 00000000000..a33f7038e84 --- /dev/null +++ b/app/services/work_package_types/form_configuration_rows/update_service.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTypes + module FormConfigurationRows + class UpdateService < ::BaseServices::BaseCallable + include ::WorkPackageTypes::FormConfiguration::Concern + + INACTIVE_TARGET = "inactive" + + def initialize(user:, type:, row_key:) + super(user:, type:) + @row_key = row_key + end + + def perform + return move_row(params[:move_to].to_sym) if move_requested? + return drop_row(target_id: params[:target_id], position: params[:position]) if drop_requested? + + failure_with_message(I18n.t("types.edit.form_configuration.not_found")) + end + + private + + def move_row(move_to) + row = find_row(@row_key) + return failure_with_message(I18n.t("types.edit.form_configuration.not_found")) unless row + + update_row_position(row, move_to) + persist_group_result(row[:group]) + end + + def drop_row(target_id:, position:) + row = find_row(@row_key) + + return drop_row_to_inactive(row) if inactive_target?(target_id) + + target_group = find_attribute_group(target_id) + return failure_with_message(I18n.t("types.edit.form_configuration.not_found")) unless target_group + + move_row_to_target_group(row, target_group, position) + persist_group_result(target_group) + end + + def row_move_index(move_to, current_index:, size:) + case move_to + when :highest + 0 + when :higher + [current_index - 1, 0].max + when :lower + [current_index + 1, size - 1].min + when :lowest + size - 1 + else + current_index + end + end + + def inactive_target?(target_id) + target_id.to_s == INACTIVE_TARGET + end + + def drop_row_to_inactive(row) + remove_row_from_source(row) + persist_group_result(row&.dig(:group)) + end + + def move_requested? + params[:move_to].present? + end + + def drop_requested? + params[:target_id].present? + end + + def update_row_position(row, move_to) + attributes = row[:group].attributes.dup + current_index = row[:index] + new_index = row_move_index(move_to, current_index:, size: attributes.length) + + attributes.insert(new_index, attributes.delete_at(current_index)) if new_index != current_index + row[:group].attributes = attributes + end + + def move_row_to_target_group(row, target_group, position) + remove_row_from_source(row) + target_attributes = target_group.attributes.dup + target_attributes.insert(drop_insert_position(position, target_attributes), @row_key) + target_group.attributes = target_attributes + end + + def persist_group_result(group) + persist_groups(active_groups).tap do |call| + call.result = group if call.success? + end + end + + def remove_row_from_source(row) + return unless row + + source_attributes = row[:group].attributes.dup + source_attributes.delete_at(row[:index]) + row[:group].attributes = source_attributes + end + + def drop_insert_position(position, target_attributes) + [position.to_i - 1, 0].max.clamp(0, target_attributes.length) + end + end + end +end diff --git a/app/services/work_package_types/update_service.rb b/app/services/work_package_types/update_service.rb index cc98f7a8095..36ac8e82429 100644 --- a/app/services/work_package_types/update_service.rb +++ b/app/services/work_package_types/update_service.rb @@ -36,7 +36,11 @@ module WorkPackageTypes def validate_params # Only set attribute groups when it exists (Regression #28400) - set_attribute_groups(params) if params[:attribute_groups] + if params[:attribute_groups] + result = set_attribute_groups(params) + return result if result.failure? + end + set_active_custom_fields set_active_custom_fields_for_project_ids(params[:project_ids]) if params[:project_ids].present? @@ -48,14 +52,49 @@ module WorkPackageTypes def default_contract_class = UpdateSettingsContract def set_attribute_groups(params) - if params[:attribute_groups].empty? + normalize_result = normalize_attribute_groups_param(params[:attribute_groups]) + return normalize_result if normalize_result.failure? + + assign_result = assign_attribute_groups(normalize_result.result) + return assign_result if assign_result.failure? + + params.delete(:attribute_groups) + ServiceResult.success(result: model) + end + + def normalize_attribute_groups_param(attribute_groups) + parsed_groups = case attribute_groups + when String + JSON.parse(attribute_groups) + else + attribute_groups + end + + return invalid_attribute_groups_result unless parsed_groups.is_a?(Array) + + ServiceResult.success(result: parsed_groups.map(&:deep_symbolize_keys)) + rescue JSON::ParserError + invalid_attribute_groups_result + end + + def assign_attribute_groups(attribute_groups) + if attribute_groups.empty? model.reset_attribute_groups else - # FIXME: We lost the ability to react to errors on the transformation. Might not be a big issue on day to day, but still - # a regression - 2025-08-07 noted by @mereghost - model.attribute_groups = AttributeGroups::Transformer.new(groups: params[:attribute_groups], user: user).call - params.delete(:attribute_groups) + transform_result = AttributeGroups::Transformer.new(groups: attribute_groups, user: user).call + return transform_result if transform_result.failure? + + model.attribute_groups = transform_result.result end + + ServiceResult.success(result: model) + end + + def invalid_attribute_groups_result + model.errors.clear + model.errors.add(:attribute_groups, I18n.t("types.edit.form_configuration.invalid_attribute_groups")) + + ServiceResult.failure(result: model, errors: model.errors) end ## diff --git a/app/validators/projects/identifier_validator.rb b/app/validators/projects/identifier_validator.rb index c1703d68e44..4960a37b9b1 100644 --- a/app/validators/projects/identifier_validator.rb +++ b/app/validators/projects/identifier_validator.rb @@ -30,6 +30,9 @@ module Projects class IdentifierValidator < ActiveModel::EachValidator + # Two anchored patterns, one per rule, so the start-character and body + # checks produce distinct error messages. The full unanchored shape + # lives at `Projects::Identifier::SEMANTIC_FORMAT`. SEMANTIC_START_FORMAT = /\A[A-Z]/ SEMANTIC_BODY_FORMAT = /\A[A-Z0-9_]*\z/ diff --git a/app/views/custom_styles/_primer_color_mapping.erb b/app/views/custom_styles/_primer_color_mapping.erb index 37ad04283f5..f828167737f 100644 --- a/app/views/custom_styles/_primer_color_mapping.erb +++ b/app/views/custom_styles/_primer_color_mapping.erb @@ -83,7 +83,6 @@ --primary-button-color: var(--primary-button-color--dark-mode); --button--primary-background-disabled-color: var(--primary-button-color--major1); --button--primary-border-disabled-color: var(--primary-button-color--major1); - --type-form-conf-attribute--background: var(--overlay-bgColor); --select-arrow-bg-color-url: url("data:image/svg+xml;utf8,"); --ck-color-mention-text: var(--display-red-fgColor); --ck-color-button-on-background: var(--button-default-bgColor-active); diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index d7afd805ede..0e20e0f36b1 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -28,8 +28,8 @@ See COPYRIGHT and LICENSE files for more details. ++#%> <% html_title t(:label_administration), t(:label_user_plural) -%> -<%= render Users::IndexPageHeaderComponent.new %> +<%= render Users::IndexPageHeaderComponent.new(query: @query) %> -<%= render Users::IndexSubHeaderComponent.new(groups: @groups, status: @status, params: params) %> +<%= render Users::IndexSubHeaderComponent.new(query: @query) %> -<%= render Users::TableComponent.new(rows: @users, current_user:) %> +<%= render Users::TableComponent.new(rows: @query, current_user:) %> diff --git a/app/views/work_package_types/form_configuration_tab/_form.html.erb b/app/views/work_package_types/form_configuration_tab/_form.html.erb deleted file mode 100644 index 54232eea089..00000000000 --- a/app/views/work_package_types/form_configuration_tab/_form.html.erb +++ /dev/null @@ -1,71 +0,0 @@ -<%#-- copyright -OpenProject is an open source project management software. -Copyright (C) the OpenProject GmbH - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License version 3. - -OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -Copyright (C) 2006-2013 Jean-Philippe Lang -Copyright (C) 2010-2013 the ChiliProject Team - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -See COPYRIGHT and LICENSE files for more details. - -++#%> - -<% form_attributes = form_configuration_groups(@type) %> - -
-
-
-
- <%= render(EnterpriseEdition::BannerComponent.new(:edit_attribute_groups, mb: 3)) %> - - <% unless EnterpriseToken.allows_to?(:edit_attribute_groups) %> - <%= angular_component_tag "opce-no-results", - inputs: { - title: t("text_form_configuration") + t("text_custom_field_hint_activate_per_project") - } %> - <% end %> -
-
-
-
- - <% no_filter_query = ::API::V3::Queries::QueryParamsRepresenter.new(Query.new_default.tap { |q| q.filters = [] }).to_json %> - <%= f.hidden_field :attribute_groups, value: "", class: "admin-type-form--hidden-field" %> - <%= content_tag( - "opce-admin-type-form-configuration", - "", - data: { - "active-groups": form_attributes[:actives], - "inactive-attributes": form_attributes[:inactives], - "no-filter-query": no_filter_query - } - ) %> -
-
-
-
- -
-
- <%= styled_button_tag t(@type.new_record? ? :button_create : :button_save), - data: { turbo_submits_with: t(@type.new_record? ? :button_create : :button_save) }, - class: "form-configuration--save -primary -with-icon icon-checkmark" %> -
-
diff --git a/app/views/work_package_types/form_configuration_tab/edit.html.erb b/app/views/work_package_types/form_configuration_tab/edit.html.erb index d523df84aff..d477197c93e 100644 --- a/app/views/work_package_types/form_configuration_tab/edit.html.erb +++ b/app/views/work_package_types/form_configuration_tab/edit.html.erb @@ -31,13 +31,19 @@ See COPYRIGHT and LICENSE files for more details. <%= render ::Types::EditPageHeaderComponent.new(type: @type, tabs: types_tabs) %> -<%= - primer_form_with( - model: @type, - url: type_form_configuration_path(@type), - builder: TabularFormBuilder, - lang: current_language - ) do |f| - render partial: "form", locals: { f: f } - end -%> +<%= render(EnterpriseEdition::BannerComponent.new(:edit_attribute_groups, mb: 3)) %> + +<% unless EnterpriseToken.allows_to?(:edit_attribute_groups) %> + <%= render(Primer::Alpha::Banner.new(scheme: :default, icon: :info, mb: 3)) do %> + <%= t("text_form_configuration") %> <%= t("text_custom_field_hint_activate_per_project") %> + <% end %> +<% end %> + +<% no_filter_query = ::API::V3::Queries::QueryParamsRepresenter.new(Query.new_default.tap { |q| q.filters = [] }).to_json %> +<%= render( + WorkPackageTypes::FormConfigurationComponent.new( + type: @type, + form_attributes: form_configuration_groups(@type), + no_filter_query: + ) + ) %> diff --git a/app/workers/project_identifiers/convert_project_to_semantic_ids_job.rb b/app/workers/project_identifiers/convert_project_to_semantic_ids_job.rb index b44ac1c1bd6..451622e9065 100644 --- a/app/workers/project_identifiers/convert_project_to_semantic_ids_job.rb +++ b/app/workers/project_identifiers/convert_project_to_semantic_ids_job.rb @@ -29,9 +29,6 @@ #++ class ProjectIdentifiers::ConvertProjectToSemanticIdsJob < ApplicationJob - include GoodJob::ActiveJobExtensions::Concurrency - - good_job_control_concurrency_with(perform_limit: 5) queue_with_priority :above_normal retry_on StandardError, wait: :polynomially_longer, attempts: 8 discard_on ActiveRecord::RecordNotFound diff --git a/config/initializers/02-secret_key_base_check.rb b/config/initializers/02-secret_key_base_check.rb new file mode 100644 index 00000000000..8849ba62bc4 --- /dev/null +++ b/config/initializers/02-secret_key_base_check.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +# Refuse to boot in production if SECRET_KEY_BASE is a known-weak default that +# we have shipped in our Dockerfile or referenced in our documentation. +# An attacker who knows the secret can forge signed cookies and session data +# and decrypt anything derived from it. +insecure_secret_key_bases = [ + "OVERWRITE_ME", # default value in Dockerfile + "secret", # old sample from documentation + "" # new sample from documentation +].freeze + +no_rake_task = !(Rake.respond_to?(:application) && Rake.application.top_level_tasks.present?) +no_override = ENV["OPENPROJECT_DISABLE__SECRET_KEY_BASE__CHECK"] != "true" +if Rails.env.production? && no_rake_task && no_override + secret = ENV["SECRET_KEY_BASE"].to_s + fatal_reason = + if secret.empty? + "SECRET_KEY_BASE is not set." + elsif insecure_secret_key_bases.include?(secret) + "SECRET_KEY_BASE is set to a well-known default value (#{secret.inspect})." + end + + if fatal_reason + abort <<~ERROR # rubocop:disable Rails/Exit + ======= INSECURE SECRET_KEY_BASE DETECTED ======= + #{fatal_reason} + + OpenProject uses SECRET_KEY_BASE to sign cookies, sessions, and other + security-sensitive data. Running with a default or weak value allows + anyone to forge signed data and compromise the installation. + + Generate a strong, random value (for example via `openssl rand -hex 64`) + and provide it via the SECRET_KEY_BASE environment variable. The same + value MUST be reused on every container/process start, otherwise + existing sessions and encrypted database content become unreadable. + + If you know what you are doing and want to disable this check, set the environment + variable OPENPROJECT_DISABLE__SECRET_KEY_BASE__CHECK to "true" (not recommended). + + Documentation: + - https://www.openproject.org/docs/installation-and-operations/installation/docker/ + + ================================================= + ERROR + end +end diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb index b728aeaf9c2..4346142d319 100644 --- a/config/initializers/cookies_serializer.rb +++ b/config/initializers/cookies_serializer.rb @@ -32,4 +32,4 @@ # Specify a serializer for the signed and encrypted cookie jars. # Valid options are :json, :marshal, and :hybrid. -Rails.application.config.action_dispatch.cookies_serializer = :marshal +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb index dd30f593c64..785337e54b0 100644 --- a/config/initializers/permissions.rb +++ b/config/initializers/permissions.rb @@ -70,7 +70,7 @@ Rails.application.reloader.to_prepare do map.permission :create_user, { - users: %i[index show new create resend_invitation], + users: %i[index show new create resend_invitation configure_view_modal], "users/memberships": %i[create], admin: %i[index] }, @@ -85,7 +85,8 @@ Rails.application.reloader.to_prepare do update_reminders update_email_alerts update_workdays update_participating update_non_participating update_date_alerts new_project_settings create_project_settings - edit_project_settings update_project_settings destroy_project_settings], + edit_project_settings update_project_settings destroy_project_settings + configure_view_modal], "users/memberships": %i[create update destroy], admin: %i[index] }, @@ -96,7 +97,7 @@ Rails.application.reloader.to_prepare do map.permission :view_all_principals, { - users: %i[index show] + users: %i[index show configure_view_modal] }, permissible_on: :global, require: :loggedin, diff --git a/config/initializers/subscribe_listeners.rb b/config/initializers/subscribe_listeners.rb index ab907b2238b..26a34655726 100644 --- a/config/initializers/subscribe_listeners.rb +++ b/config/initializers/subscribe_listeners.rb @@ -41,10 +41,11 @@ Rails.application.config.after_initialize do # A job is scheduled immediately that creates notifications (in-app if # supported) right away and schedules jobs to be run for mail and digest # mails. - Notifications::WorkflowJob - .perform_later(:create_notifications, - journal, - send_notifications) + if send_notifications + Notifications::WorkflowJob.perform_later(:create_notifications, + journal, + send_notifications) + end # A job is scheduled for the end of the journal aggregation time. If the # journal still exists with a matching updated_at value (it might be updated diff --git a/config/locales/crowdin/af.yml b/config/locales/crowdin/af.yml index 6f00a64c2db..9bf293b9cdd 100644 --- a/config/locales/crowdin/af.yml +++ b/config/locales/crowdin/af.yml @@ -1339,6 +1339,37 @@ af: edit: form_configuration: tab: Vorm konfigurasie + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projekte enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ af: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ af: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ af: one: 1 comment other: "%{count} comments" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4752,13 +4787,6 @@ af: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/ar.yml b/config/locales/crowdin/ar.yml index f24e598680e..031149f7ab7 100644 --- a/config/locales/crowdin/ar.yml +++ b/config/locales/crowdin/ar.yml @@ -1411,6 +1411,37 @@ ar: edit: form_configuration: tab: تشكيل النموذج + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: المشاريع enable_all: Enable for all projects @@ -1519,8 +1550,8 @@ ar: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2502,7 +2533,7 @@ ar: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4674,6 +4705,10 @@ ar: one: 1 comment other: "%{count} تعليقات" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} مفتوح" @@ -5028,13 +5063,6 @@ ar: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: إضافة مجموعات عمل permission_add_messages: نشر الرسائل diff --git a/config/locales/crowdin/az.yml b/config/locales/crowdin/az.yml index b40220de0da..ad11f65b2b9 100644 --- a/config/locales/crowdin/az.yml +++ b/config/locales/crowdin/az.yml @@ -1339,6 +1339,37 @@ az: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projects enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ az: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ az: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ az: one: 1 comment other: "%{count} comments" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4752,13 +4787,6 @@ az: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/be.yml b/config/locales/crowdin/be.yml index e40cf51ac1e..da557552d4f 100644 --- a/config/locales/crowdin/be.yml +++ b/config/locales/crowdin/be.yml @@ -1375,6 +1375,37 @@ be: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projects enable_all: Enable for all projects @@ -1479,8 +1510,8 @@ be: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2458,7 +2489,7 @@ be: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4548,6 +4579,10 @@ be: one: 1 comment other: "%{count} comments" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4890,13 +4925,6 @@ be: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/bg.yml b/config/locales/crowdin/bg.yml index 51ba46806ac..f490f790c1b 100644 --- a/config/locales/crowdin/bg.yml +++ b/config/locales/crowdin/bg.yml @@ -1339,6 +1339,37 @@ bg: edit: form_configuration: tab: Конфигурация на формата + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Проекти enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ bg: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ bg: attribute_unknown_name: 'Използван е невалиден атрибут на работния пакет: %{attribute}' duplicate_group: Името на групата '%{group}' се използва повече от веднъж. Имената на групите трябва да са уникални. query_invalid: 'Вградената заявка ''%{group}'' е невалидна: %{details}' - group_without_name: Не са разрешени неименовани групи. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4418,6 +4449,10 @@ bg: one: 1 коментар other: "%{count} коментара" zero: няма коментари + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 отворен other: "%{count} отворени" @@ -4748,13 +4783,6 @@ bg: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/ca.yml b/config/locales/crowdin/ca.yml index 46cbe0e83cb..4a25555bb42 100644 --- a/config/locales/crowdin/ca.yml +++ b/config/locales/crowdin/ca.yml @@ -1338,6 +1338,37 @@ ca: edit: form_configuration: tab: Configuració del formulari + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projectes enable_all: Enable for all projects @@ -1438,8 +1469,8 @@ ca: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2413,7 +2444,7 @@ ca: attribute_unknown_name: 'L''atribut de paquet de treball utilitzat és invàlid: %{attribute}' duplicate_group: El nom del grup "%{group}" ja està utilitzat. El nom del grup ha de ser únic. query_invalid: 'La consulta incrustrada "%{group}" és invàlida: %{details}' - group_without_name: No estan permesos els grups sense nom. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4417,6 +4448,10 @@ ca: one: Un comentari other: "%{count} comentaris" zero: sense comentaris + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: Un obert other: "%{count} oberts" @@ -4751,13 +4786,6 @@ ca: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Afegir paquets de treball permission_add_messages: Publicar missatges diff --git a/config/locales/crowdin/ckb-IR.yml b/config/locales/crowdin/ckb-IR.yml index cb18e72a804..27ca349cb2e 100644 --- a/config/locales/crowdin/ckb-IR.yml +++ b/config/locales/crowdin/ckb-IR.yml @@ -1339,6 +1339,37 @@ ckb-IR: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projects enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ ckb-IR: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ ckb-IR: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ ckb-IR: one: 1 comment other: "%{count} comments" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4752,13 +4787,6 @@ ckb-IR: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/cs.yml b/config/locales/crowdin/cs.yml index eb12be06660..fd40ba2342b 100644 --- a/config/locales/crowdin/cs.yml +++ b/config/locales/crowdin/cs.yml @@ -1375,6 +1375,37 @@ cs: edit: form_configuration: tab: Konfigurace formuláře + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projekty enable_all: Povolit pro všechny projekty @@ -1479,8 +1510,8 @@ cs: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2460,7 +2491,7 @@ cs: attribute_unknown_name: 'Použitý neplatný atribut pracovního balíčku: %{attribute}' duplicate_group: Název skupiny '%{group}' je použit více než jednou. Názvy skupin musí být jedinečné. query_invalid: 'Vložený dotaz ''%{group}'' je neplatný: %{details}' - group_without_name: Nepojmenované skupiny nejsou povoleny. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4550,6 +4581,10 @@ cs: one: 1 komentář other: "%{count} komentáře" zero: žádné komentáře + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 otevřený other: "%{count} otevřených" @@ -4892,13 +4927,6 @@ cs: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Přidat komentáře permission_add_work_packages: Přidat pracovní balíčky permission_add_messages: Odesílat zprávy diff --git a/config/locales/crowdin/da.yml b/config/locales/crowdin/da.yml index 790a7276542..33d9dfbe088 100644 --- a/config/locales/crowdin/da.yml +++ b/config/locales/crowdin/da.yml @@ -1338,6 +1338,37 @@ da: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projekter enable_all: Enable for all projects @@ -1438,8 +1469,8 @@ da: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2413,7 +2444,7 @@ da: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4419,6 +4450,10 @@ da: one: 1 kommentar other: "%{count} kommentarer" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 åben other: "%{count} åbne" @@ -4751,13 +4786,6 @@ da: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Tilføj arbejdspakker permission_add_messages: Send beskeder diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index 6c5685616fd..12b9146f012 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -266,7 +266,7 @@ de: permissions: Berechtigungen projects: Projekte issues: Tickets - issue_details: Beschreibung, Verlaud, Kommentare und Anhänge + issue_details: Beschreibung, Verlauf, Kommentare und Anhänge custom_fields: Ein Teil der benutzerdefinierten Felder users: Beteiligte Benutzer und Gruppen confirm_import: @@ -465,8 +465,8 @@ de: label: 'Rolle: %{role}' no_role: Rolle auswählen roles: - one: One role selected - other: "%{count} roles selected" + one: Eine Rolle ausgewählt + other: "%{count} Rollen ausgewählt" blankslate: title: Keine Statusübergänge konfiguriert description: Status hinzufügen, um mit der Konfiguration von Arbeitsabläufen für diese Rolle zu beginnen @@ -1127,8 +1127,8 @@ de: matrix_check_uncheck_all_in_row_label_html: Umschalten von %{permission} Berechtigung für alle Rollen matrix_check_uncheck_all_in_col_label_html: Umschalten aller %{module} Berechtigungen für die Rolle %{role} users: - force_password_change_hint: Der Benutzer muss bei seiner nächsten Anmeldung ein neues Passwort festlegen. Wird automatisch aktiviert, wenn die Anmeldedaten per E-Mail gesendet werden. - send_information_hint: Sendet das Passwort im Klartext per E-Mail. Wenn diese Option aktiviert ist, wird der Benutzer aufgefordert, sein Passwort bei der ersten Anmeldung zu ändern. + force_password_change_hint: Bei der nächsten Anmeldung muss ein neues Passwort festgelegt werden. Wird automatisch aktiviert, wenn Zugangsdaten per E-Mail versendet werden. + send_information_hint: Sendet das Passwort als Klartext per E-Mail. Wenn aktiviert, ist eine Passwortänderung bei der ersten Anmeldung erforderlich. autologins: prompt: "%{num_days} lang angemeldet bleiben" sessions: @@ -1335,6 +1335,37 @@ de: edit: form_configuration: tab: Formularkonfiguration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projekte enable_all: Für alle Projekte aktivieren @@ -1437,8 +1468,8 @@ de: bulk_delete_dialog: title: "%{count} Arbeitspakete löschen" heading: Diese %{count} Arbeitspakete unwiderruflich löschen? - description: 'Die folgenden Arbeitspakete, einschließlich der Unteraufgaben und aller zugehörigen Daten, werden dauerhaft gelöscht:' - description_with_children: 'Die folgenden Arbeitspakete, einschließlich der Unteraufgaben und aller zugehörigen Daten, werden dauerhaft gelöscht:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: Ich bestätige, dass alle ausgewählten Arbeitspakete inklusive ihrer Unteraufgaben dauerhaft gelöscht werden. cross_project_warning: 'Diese Arbeitspakete umfassen mehrere Projekte: %{projects}' children_label: 'Die folgenden Unteraufgaben werden ebenfalls gelöscht:' @@ -2410,7 +2441,7 @@ de: attribute_unknown_name: 'Ungültige Arbeitspaket-Attribute verwendet: %{attribute}' duplicate_group: Der Gruppen-Name %{group} wird mehr als einmal verwendet. Gruppen-Namen müssen eindeutig sein. query_invalid: 'Die eingebettete Ansicht ''%{group}'' ist ungültig: %{details}' - group_without_name: Unbenannte Gruppen sind nicht erlaubt. + group_without_name: Group name can't be blank. patterns: invalid_tokens: Ein oder mehrere Attribute des Feldes sind ungültig. Bitte korrigieren Sie die Attribute vor dem Speichern. user: @@ -2744,7 +2775,7 @@ de: button_confirm: Bestätigen button_configure: Konfigurieren button_continue: Fortfahren - button_complete: Fertigstellen + button_complete: Abschließen button_copy: Kopieren button_copy_to_clipboard: In Zwischenablage kopieren button_copy_link_to_clipboard: Link in Zwischenablage kopieren @@ -3420,22 +3451,22 @@ de: report_component: checks: failures: - one: "%{count} check failed" - other: "%{count} checks failed" + one: "%{count} Überprüfung fehlgeschlagen" + other: "%{count} Überprüfungen fehlgeschlagen" success: All checks passed warnings: one: "%{count} check returned a warning" other: "%{count} checks returned a warning" summary: failure: Some checks failed and the system does not work as expected. - success: All connections and systems are working as expected. - warning: Some checks returned a warning. This can lead to unexpected behaviour. + success: Alle Verbindungen und Systeme funktionieren wie erwartet. + warning: Einige Überprüfungen ergaben eine Warnung. Dies kann zu unerwartetem Verhalten führen. result_component: status: - failed: Failed - passed: Passed - skipped: Skipped - warning: Warning + failed: Fehlgeschlagen + passed: Bestanden + skipped: Übersprungen + warning: Warnung homescreen: additional: projects: Neueste sichtbare Projekte in dieser Instanz. @@ -3618,7 +3649,7 @@ de: personal_reminder: Bei persönlichen Erinnerungen benachrichtigen daily_reminders: title: Tägliche E-Mail-Erinnerungen für ungelesene Benachrichtigungen zusenden - description: You will receive these reminders only for unread notifications and only at hours you specify. Until you configure a time zone for your account, the times will be interpreted to be in UTC. + description: Sie erhalten diese Erinnerungen nur für ungelesene Benachrichtigungen und nur zu den von Ihnen angegebenen Zeiten. Solange Sie keine Zeitzone für Ihr Konto konfigurieren, werden die Zeiten in UTC interpretiert. enabled: Tägliche E-Mail-Erinnerungen aktivieren add_time: Zeit hinzufügen remove_time: Zeit entfernen @@ -3632,7 +3663,7 @@ de: date_range: Pausenzeiten email_alerts: title: E-Mail-Benachrichtigungen für andere Objekte (die keine Arbeitspakete sind) - description: Notifications today are limited to work packages. You can choose to continue receiving email alerts for these events until they are included in notifications. + description: Die Benachrichtigungen sind aktuell auf Arbeitspakete beschränkt. Sie können die E-Mail-Benachrichtigungen für diese Ereignisse so lange erhalten, bis sie in den Benachrichtigungen enthalten sind. news_added: Neuigkeit hinzugefügt news_commented: Kommentar zu einer Neuigkeit document_added: Dokument hinzugefügt @@ -3645,7 +3676,7 @@ de: notifications: participating: title: Beteiligt - description: Notifications for all activities in work packages you are involved in (assignee, accountable or watcher). + description: Benachrichtigungen für alle Aktivitäten in Arbeitspaketen, an denen Sie beteiligt sind (Zugewiesen an, Verantwortlich(e/r) oder Beobachter). submit_button: Einstellungen aktualisieren mentioned: Erwähnt watched: Beobachtet @@ -3654,7 +3685,7 @@ de: shared: Mit mir geteilt date_alerts: title: Datums-Erinnerungen - description: Automatic notifications when important dates are approaching for open work packages you are involved in (assignee, accountable or watcher). + description: Automatische Benachrichtigungen, wenn wichtige Termine für offene Arbeitspakete bevorstehen, an denen Sie beteiligt (z. B. zugewiesen, verantwortlich oder Beobachter) sind. submit_button: Datums-Erinnerungen aktualisieren start_date: Anfangstermin due_date: Endtermin @@ -3669,7 +3700,7 @@ de: seven_days_after: 7 Tage danach non_participating: title: Nicht beteiligt - description: Additional notifications for activities in all projects. + description: Zusätzliche Benachrichtigungen für Aktivitäten in allen Projekten. submit_button: Einstellungen aktualisieren work_package_created: Neue Arbeitspakete work_package_commented: Alle neuen Kommentare @@ -3678,7 +3709,7 @@ de: work_package_scheduled: Alle Datumsänderungen project_specific_settings: title: Projektspezifische Benachrichtigungen - description: These project-specific settings override default settings above. + description: Diese projektspezifischen Einstellungen überschreiben die Standardeinstellungen oben add_button: Projektspezifische Benachrichtigungen hinzufügen dialog_title: Projektspezifische Benachrichtigungen hinzufügen list_header: Projekte mit spezifischen Benachrichtigungen @@ -3737,7 +3768,7 @@ de: label_age: Geändert vor label_ago: vor (Tage) label_all: alle - label_all_uppercase: All + label_all_uppercase: Alle label_all_time: gesamter Zeitraum label_all_words: Alle Wörter label_all_open_wps: Alle offenen @@ -3839,7 +3870,7 @@ de: label_commits_per_month: Übertragungen pro Monat label_confirmation: Bestätigung label_contains: enthält - label_starts_with: starts with + label_starts_with: beginnt mit label_content: Inhalt label_color_plural: Farben label_copied: kopiert @@ -3847,8 +3878,8 @@ de: label_copy_source: Quelle label_copy_target: Ziel label_copy_workflow_from: Workflow kopieren von - label_copy_workflow_from_type: Copy to another type - label_copy_workflow_from_role: Copy to other roles + label_copy_workflow_from_type: In einen anderen Typ kopieren + label_copy_workflow_from_role: In andere Rollen kopieren label_copy_project: Projekt kopieren label_core_version: Core Version label_core_build: Core-Buildinformation @@ -4047,8 +4078,8 @@ de: label_custom_pdf_export_settings: Benutzerdefinierte PDF-Export-Einstellungen label_custom_favicon: Benutzerdefiniertes Favicon label_custom_touch_icon: Benutzerdefiniertes Touch-Icon - label_departments: Organization - label_departments_description_html: 'Define your company’s structure by creating departments and sub-departments in a hierarchical way. This allows you to reflect reporting lines and maintain a clear, structured overview of your organization within OpenProject. You can also import an existing organization structure through [LDAP group synchronisation](ldap_docs_article). + label_departments: Organisation + label_departments_description_html: 'Definieren Sie die Struktur Ihres Unternehmens, indem Sie Abteilungen und Unterabteilungen in hierarchischer Form anlegen. So können Sie Berichtslinien abbilden und einen klaren, strukturierten Überblick über Ihre Organisation in OpenProject behalten. Sie können auch eine bestehende Organisationsstruktur über [LDAP-Gruppensynchronisation](ldap_docs_article) importieren. ' label_logout: Abmelden @@ -4156,7 +4187,7 @@ de: label_password_rule_numeric: Ziffern label_password_rule_special: Sonderzeichen label_password_rule_uppercase: Großbuchstaben - label_password_requirement_lowercase: Must contain at least one lowercase character. + label_password_requirement_lowercase: Muss mindestens einen Kleinbuchstaben enthalten. label_password_requirement_numeric: Muss mindestens ein numerisches Zeichen enthalten. label_password_requirement_special: Muss mindestens ein Sonderzeichen enthalten. label_password_requirement_uppercase: Muss mindestens einen Großbuchstaben enthalten. @@ -4414,6 +4445,10 @@ de: one: Ein Kommentar other: "%{count} Kommentare" zero: Keine Kommentare + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 offen other: "%{count} offen" @@ -4527,7 +4562,7 @@ de: note: 'Anmerkung: „%{note}“' sharing: work_packages: - allowed_actions_html: 'Sie haben auf diesem Arbeitspakte folgende Berechtigungen: %{allowed_actions}. Dies kann sich je nach Ihrer Projektrolle und Berechtigungen ändern.' + allowed_actions_html: 'Sie haben auf diesem Arbeitspaket folgende Berechtigungen: %{allowed_actions}. Dies kann sich je nach Ihrer Projektrolle und Berechtigungen ändern.' create_account: Um auf dieses Arbeitspaket zuzugreifen, müssen Sie ein Konto für %{instance} erstellen und aktivieren. open_work_package: Arbeitspaket öffnen subject: Arbeitspaket %{id} wurde mit Ihnen geteilt @@ -4750,13 +4785,6 @@ de: work_package_card_component: menu: label_actions: Arbeitspaket-Aktionen - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Kommentare hinzufügen permission_add_work_packages: Arbeitspakete hinzufügen permission_add_messages: Forenbeiträge hinzufügen @@ -5079,7 +5107,7 @@ de: ' setting_after_login_default_redirect_url: Weiterleitung nach Anmeldung - setting_after_login_default_redirect_url_example_html: 'Set a default path to redirect users after login, if no back link was provided. Redirects to home page if not set.
Example: %{example_code} + setting_after_login_default_redirect_url_example_html: 'Legen Sie einen Standardpfad fest, zu dem nach der Anmeldung weitergeleitet wird, wenn kein Back-Link vorhanden ist. Falls nicht gesetzt, wird zur Startseite weitergeleitet.
Beispiel: %{example_code} ' setting_apiv3_cors_title: Cross-Origin Resource Sharing (CORS) diff --git a/config/locales/crowdin/el.yml b/config/locales/crowdin/el.yml index b29a7fbe3b8..4e957791f65 100644 --- a/config/locales/crowdin/el.yml +++ b/config/locales/crowdin/el.yml @@ -1338,6 +1338,37 @@ el: edit: form_configuration: tab: Διαμόρφωση φόρμας + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Έργα enable_all: Enable for all projects @@ -1438,8 +1469,8 @@ el: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2413,7 +2444,7 @@ el: attribute_unknown_name: 'Χρησιμοποιήθηκε μη έγκυρο χαρακτηριστικό πακέτου εργασίας: %{attribute}' duplicate_group: Το όνομα της ομάδας %{group} χρησιμοποιείται περισσότερες από μία φορές. Τα ονόματα ομάδων πρέπει να είναι μοναδικά. query_invalid: 'Η ενσωματωμένη αναζήτηση ''%{group}'' δεν είναι έγκυρη: %{details}' - group_without_name: Δεν επιτρέπονται ομάδες χωρίς όνομα. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4419,6 +4450,10 @@ el: one: 1 σχόλιο other: "%{count} σχόλια" zero: κανένα σχόλιο + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 ανοικτό other: "%{count} ανοικτά" @@ -4751,13 +4786,6 @@ el: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Προσθήκη πακέτων εργασίας permission_add_messages: Δημοσίευση μηνυμάτων diff --git a/config/locales/crowdin/eo.yml b/config/locales/crowdin/eo.yml index a6ee444a3e5..80678ab9ad5 100644 --- a/config/locales/crowdin/eo.yml +++ b/config/locales/crowdin/eo.yml @@ -1339,6 +1339,37 @@ eo: edit: form_configuration: tab: Formulara agordo + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projektoj enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ eo: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ eo: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ eo: one: 1 komento other: "%{count} komentoj" zero: sen komentoj + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 malfermita other: "%{count} malfermitaj" @@ -4752,13 +4787,6 @@ eo: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/es.yml b/config/locales/crowdin/es.yml index d15d9773268..e67f50787eb 100644 --- a/config/locales/crowdin/es.yml +++ b/config/locales/crowdin/es.yml @@ -1336,6 +1336,37 @@ es: edit: form_configuration: tab: Configuración del formulario + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Proyectos enable_all: Habilitar en todos los proyectos @@ -1436,8 +1467,8 @@ es: bulk_delete_dialog: title: Eliminar %{count} paquetes de trabajo heading: "¿Desea eliminar estos %{count} paquetes de trabajo de forma definitiva?" - description: 'Los siguientes paquetes de trabajo, incluidos los subpaquetes y todos los datos asociados, se eliminarán de forma definitiva:' - description_with_children: 'Se eliminarán de forma definitiva los siguientes paquetes de trabajo, incluidos los paquetes de trabajo secundarios, así como todos los datos asociados:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: Entiendo que todos los paquetes de trabajo seleccionados y sus elementos secundarios se eliminarán de forma permanente. cross_project_warning: 'Estos paquetes de trabajo abarcan varios proyectos: %{projects}' children_label: 'También se eliminarán los siguientes elementos secundarios:' @@ -2409,7 +2440,7 @@ es: attribute_unknown_name: 'Se ha utilizado un atributo de paquete de trabajo no válido: %{attribute}' duplicate_group: El nombre de grupo “%{group}” se usó más de una vez. Los nombres de grupo tienen que ser únicos. query_invalid: 'La consulta insertada “%{group}” no es válida: %{details}' - group_without_name: No se permiten grupos sin nombre. + group_without_name: Group name can't be blank. patterns: invalid_tokens: Uno o más atributos dentro del campo no son válidos. Corrija los atributos antes de guardar. user: @@ -4411,6 +4442,10 @@ es: one: 1 comentario other: "%{count} comentarios" zero: sin comentarios + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 abierto other: "%{count} abiertos" @@ -4743,13 +4778,6 @@ es: work_package_card_component: menu: label_actions: Acciones del paquete de trabajo - work_package_card_list_component: - header: - label_actions: Abrir menú - label_work_package_count: - zero: No hay paquetes de trabajo - one: "%{count} paquete de trabajo" - other: "%{count} paquetes de trabajo" permission_add_work_package_comments: Añadir comentarios permission_add_work_packages: Añadir paquetes de trabajo permission_add_messages: Publicar mensajes diff --git a/config/locales/crowdin/et.yml b/config/locales/crowdin/et.yml index 4875fd0bf23..9d7273c50d3 100644 --- a/config/locales/crowdin/et.yml +++ b/config/locales/crowdin/et.yml @@ -1339,6 +1339,37 @@ et: edit: form_configuration: tab: Vormi seadistamine + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projektid enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ et: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ et: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ et: one: 1 kommentaar other: "%{count} kommentaari" zero: kommentaare pole + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 avatud other: "%{count} avatud" @@ -4750,13 +4785,6 @@ et: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Lisa kommentaare permission_add_work_packages: Teemasid lisada permission_add_messages: Postitusi lisada diff --git a/config/locales/crowdin/eu.yml b/config/locales/crowdin/eu.yml index c0fd17a98c8..c1ca9d12e2c 100644 --- a/config/locales/crowdin/eu.yml +++ b/config/locales/crowdin/eu.yml @@ -1339,6 +1339,37 @@ eu: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projects enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ eu: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ eu: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ eu: one: 1 comment other: "%{count} comments" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4752,13 +4787,6 @@ eu: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/fa.yml b/config/locales/crowdin/fa.yml index 2008bc4b7a1..6f5e7fa524a 100644 --- a/config/locales/crowdin/fa.yml +++ b/config/locales/crowdin/fa.yml @@ -1339,6 +1339,37 @@ fa: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: پروژه‌ها enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ fa: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ fa: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ fa: one: 1 comment other: نظر %{count} zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} باز" @@ -4752,13 +4787,6 @@ fa: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/fi.yml b/config/locales/crowdin/fi.yml index 76357525c9c..2d7e2390dd1 100644 --- a/config/locales/crowdin/fi.yml +++ b/config/locales/crowdin/fi.yml @@ -1339,6 +1339,37 @@ fi: edit: form_configuration: tab: Lomakkeen muokkaus + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projektit enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ fi: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ fi: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Nimeämätön ryhmät eivät ole sallittuja. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4418,6 +4449,10 @@ fi: one: 1 kommentti other: "%{count} kommenttia" zero: ei kommentteja + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 avoin other: "%{count} avointa" @@ -4750,13 +4785,6 @@ fi: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Lisää tehtävä permission_add_messages: Jätä viesti diff --git a/config/locales/crowdin/fil.yml b/config/locales/crowdin/fil.yml index 13f02783881..0c69c58fd6b 100644 --- a/config/locales/crowdin/fil.yml +++ b/config/locales/crowdin/fil.yml @@ -1339,6 +1339,37 @@ fil: edit: form_configuration: tab: Form kompigurasyon + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Mga proyekto enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ fil: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ fil: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Wala pangalan ng grupo ay hindi pinapayagan. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ fil: one: Isang komento other: "%{count} mga komento" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: Isa ang bukas other: "%{count} ang bukas" @@ -4754,13 +4789,6 @@ fil: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Magdagdag ng mga work package permission_add_messages: Mga post na mensahe diff --git a/config/locales/crowdin/fr.yml b/config/locales/crowdin/fr.yml index 5130a8b690d..a9af9fd867d 100644 --- a/config/locales/crowdin/fr.yml +++ b/config/locales/crowdin/fr.yml @@ -247,8 +247,8 @@ fr: label_info: Veuillez noter que cet outil d'importation est en version bêta et qu'il ne peut pas importer tous les types de données. Voici un résumé de ce que l'instance Jira hôte offre pour l'importation et de ce que cet outil est capable d'importer pour le moment. description: Sélectionnez les données que vous souhaitez importer parmi les données disponibles extraites de l'instance Jira hôte. label_supported_data: Données prises en charge - label_coming_soon: Coming soon (Q2 2026) - label_coming_later: Coming later + label_coming_soon: Prochainement (T2 2026) + label_coming_later: À venir label_available_server_data: Données disponibles sur %{server_info} button_select_projects: Sélectionnez les projets à importer button_continue: Continuer @@ -465,8 +465,8 @@ fr: label: 'Rôle : %{role}' no_role: Sélectionner un rôle roles: - one: One role selected - other: "%{count} roles selected" + one: Un rôle sélectionné + other: "%{count} rôles sélectionnés" blankslate: title: Aucune transition de statut configurée description: Ajouter des statuts pour commencer à configurer des flux de travail pour ce rôle @@ -1332,6 +1332,37 @@ fr: edit: form_configuration: tab: Configuration du formulaire + label_group: Groupe + reset_to_defaults: Rétablir les valeurs par défaut + add_attribute_group: Ajouter un groupe d’attributs + add_query_group: Ajouter un tableau des lots de travaux associés + delete_group: Supprimer le groupe + remove_attribute: Retirer du groupe + drag_to_activate: Faites glisser les champs à partir d'ici pour les activer + drag_to_reorder: Glisser pour réorganiser + edit_query: Modifier la requête + custom_field: Champ personalisé + filter_inactive: Filtrer les attributs + inactive_attributes_heading: Attributs inactifs + no_inactive_attributes: Pas d'attributs inactifs + blankslate_title: Aucun groupe pour le moment + blankslate_description: Ajoutez des groupes à l'aide du bouton ci-dessus ou faites glisser des attributs depuis le panneau de gauche. + group_actions: Actions de groupe + rename_group: Renommer le groupe + confirm_delete_group: Êtes-vous sûr de vouloir supprimer ce groupe ? Cette action ne peut pas être annulée. + group_name_label: Nom du groupe + row_actions: Actions de ligne + query_group_label: Tableau des lots de travaux + empty_group_hint: Glisser les attributs ici + invalid_attribute_groups: La charge utile de la configuration du formulaire n'est pas valide. + invalid_query: La configuration de la requête intégrée n'est pas valide. + not_found: L'élément de configuration du formulaire demandé n'a pas été trouvé. + untitled_group: Groupe sans titre + reset_title: Réinitialiser la configuration du formulaire + confirm_reset: Êtes-vous sûr de vouloir réinitialiser la configuration du formulaire ? + reset_description: 'Cette opération rétablit les attributs dans leur groupe par défaut et désactive TOUS les champs personnalisés. + + ' projects: tab: Projets enable_all: Activer pour tous les projets @@ -1432,8 +1463,8 @@ fr: bulk_delete_dialog: title: Supprimer %{count} lots de travaux heading: Supprimer définitivement ces %{count} lots de travaux ? - description: 'Les lots de travaux suivants, y compris les enfants et toutes les données associées, seront définitivement supprimés :' - description_with_children: 'Les lots de travaux suivants, y compris les lots de travaux enfants, et toutes les données associées seront définitivement supprimés :' + description: 'Les lots de travaux suivants et toutes les données qui y sont associées seront définitivement supprimés :' + description_with_children: 'Les lots de travaux suivants, y compris les enfants et toutes les données associées, seront définitivement supprimés :' confirm_children_deletion: Je reconnais que tous les lots de travaux sélectionnés et leurs enfants seront définitivement supprimés. cross_project_warning: 'Ces lots de travaux s''étendent sur plusieurs projets : %{projects}' children_label: 'Les enfants suivants seront également supprimés :' @@ -2407,7 +2438,7 @@ fr: attribute_unknown_name: 'Attribut du lot de travaux invalide : %{attribute}' duplicate_group: Le nom de groupe "%{group}" est utilisé plus d'une fois. Les noms de groupe doivent être uniques. query_invalid: 'La requête intégrée "%{group}" est invalide : %{details}' - group_without_name: Les groupes sans nom ne sont pas autorisés. + group_without_name: Le nom du groupe ne peut pas être vide patterns: invalid_tokens: Un ou plusieurs attributs dans le champ ne sont pas valides. Veuillez corriger les attributs avant d'enregistrer. user: @@ -2540,7 +2571,7 @@ fr: unsupported_storage_type: n'est pas un type de stockage pris en charge. storage_error: Une erreur s'est produite lors de la connexion à l'espace de stockage. invalid_input: L'entrée n'est pas valide. - invalid_child_for_parent: is not allowed as a parent for this view type. + invalid_child_for_parent: n'est pas autorisé en tant que parent pour ce type de vue. activity: item: created_by_on: créé par %{user} le %{datetime} @@ -3419,22 +3450,22 @@ fr: report_component: checks: failures: - one: "%{count} check failed" - other: "%{count} checks failed" - success: All checks passed + one: "%{count} vérification a échoué" + other: "%{count} vérifications ont échoué" + success: Toutes les vérifications ont été validées warnings: - one: "%{count} check returned a warning" - other: "%{count} checks returned a warning" + one: "%{count} vérification a renvoyé un avertissement" + other: "%{count} vérifications ont renvoyé un avertissement" summary: - failure: Some checks failed and the system does not work as expected. - success: All connections and systems are working as expected. - warning: Some checks returned a warning. This can lead to unexpected behaviour. + failure: Certaines vérifications ont échoué et le système ne fonctionne pas comme prévu. + success: Toutes les connexions et les systèmes fonctionnent comme prévu. + warning: Certaines vérifications ont renvoyé un avertissement. Cela peut entraîner un comportement inattendu. result_component: status: - failed: Failed - passed: Passed - skipped: Skipped - warning: Warning + failed: Échec + passed: Approuvé + skipped: Ignoré + warning: Avertissement homescreen: additional: projects: Les projets visibles les plus récents de cette instance. @@ -3736,7 +3767,7 @@ fr: label_age: Âge label_ago: il y a label_all: tous - label_all_uppercase: All + label_all_uppercase: Tout label_all_time: tout le temps label_all_words: Tous les mots label_all_open_wps: Tous les ouverts @@ -4413,6 +4444,10 @@ fr: one: 1 commentaire other: "%{count} commentaires" zero: aucun commentaire + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 ouvert other: "%{count} ouverts" @@ -4491,7 +4526,7 @@ fr: you_have: Vous avez logo_alt_text: Logo mention: - subject: "%{user_name} mentioned you in %{id} - %{subject}" + subject: "%{user_name} vous a mentionné(e) dans %{id} - %{subject}" notification: center: Vers le centre de notifications see_in_center: Voir le commentaire dans le centre de notifications @@ -4529,7 +4564,7 @@ fr: allowed_actions_html: 'Vous disposez des autorisations suivantes sur ce lot de travaux : %{allowed_actions}. Cela peut changer en fonction de votre rôle dans le projet et de vos autorisations.' create_account: Pour accéder à ce lot de travaux, vous aurez besoin de créer et activer un compte sur %{instance}. open_work_package: Ouvrir ce lot de travaux - subject: Work package %{id} was shared with you + subject: Le lot de travaux %{id} a été partagé avec vous enterprise_text: Partagez les lots de travaux avec des utilisateurs qui ne sont pas membres du projet. summary: user: "%{user} a partagé un lot de travaux avec vous avec les droits %{role_rights}" @@ -4745,13 +4780,6 @@ fr: work_package_card_component: menu: label_actions: Actions du lot de travaux - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Ajouter des commentaires permission_add_work_packages: Ajouter des lots de travaux permission_add_messages: Poster des messages @@ -5074,7 +5102,7 @@ fr: ' setting_after_login_default_redirect_url: Après la redirection de connexion - setting_after_login_default_redirect_url_example_html: 'Set a default path to redirect users after login, if no back link was provided. Redirects to home page if not set.
Example: %{example_code} + setting_after_login_default_redirect_url_example_html: 'Définissez un chemin par défaut pour rediriger les utilisateurs après leur connexion, si aucun lien de retour n''est fourni. Les utilisateurs sont redirigés vers la page d''accueil si ce champ n''est pas défini.
Exemple : %{example_code} ' setting_apiv3_cors_title: Partage croisé des ressources (CORS) @@ -5624,7 +5652,7 @@ fr: text_project_identifier_format: Doit commencer par une lettre minuscule. Seules les lettres minuscules (a-z), les chiffres, les tirets et les traits de soulignement sont autorisés. text_reassign: 'Réaffecter au lot de travaux :' text_regexp_multiline: L'expression régulière est appliquée en mode multi-ligne, e.g. ^---\\s+ - text_rename_wiki_page: Rename wiki page + text_rename_wiki_page: Renommer la page wiki text_repository_usernames_mapping: |- Définir ou modifier les associations entre les utilisateurs d'OpenProject et les utilisateurs trouvés dans le dépôt. Les utilisateurs ayant des noms ou des adresses e-mail identiques sont automatiquement associés. @@ -6068,8 +6096,8 @@ fr: ' confirm_revoke_my_application: - one: Do you really want to remove this application? This will revoke one token active for it. - other: Do you really want to remove this application? This will revoke %{count} tokens active for it. + one: Voulez-vous vraiment supprimer cette demande ? Cela aura pour effet de révoquer un jeton actif associé. + other: Voulez-vous vraiment supprimer cette demande ? Cela aura pour effet de révoquer %{count} jetons actifs associés. authorization_error: Une erreur d'autorisation s'est produite. my_registered_applications: Applications OAuth enregistrées oauth_client: diff --git a/config/locales/crowdin/he.yml b/config/locales/crowdin/he.yml index e40fa418308..2de4862d427 100644 --- a/config/locales/crowdin/he.yml +++ b/config/locales/crowdin/he.yml @@ -1375,6 +1375,37 @@ he: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: פרויקטים enable_all: Enable for all projects @@ -1479,8 +1510,8 @@ he: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2458,7 +2489,7 @@ he: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4548,6 +4579,10 @@ he: one: תגובה אחד other: "%{count} תגובות" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: אחד פתוח other: "%{count} פתוחים" @@ -4890,13 +4925,6 @@ he: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/hi.yml b/config/locales/crowdin/hi.yml index 852936c2adc..aa98a5be33f 100644 --- a/config/locales/crowdin/hi.yml +++ b/config/locales/crowdin/hi.yml @@ -1339,6 +1339,37 @@ hi: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projects enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ hi: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ hi: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: समूह का नाम ' %{group} ' एक से अधिक बार प्रयोग किया जाता है । समूह नाम अनंय होना आवश्यक है । query_invalid: 'एंबेडेड क्वेरी ''%{group} '' अमान्य है: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ hi: one: 1 comment other: "%{count} comments" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4752,13 +4787,6 @@ hi: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/hr.yml b/config/locales/crowdin/hr.yml index 5753f04fb6f..e9cedf38308 100644 --- a/config/locales/crowdin/hr.yml +++ b/config/locales/crowdin/hr.yml @@ -1357,6 +1357,37 @@ hr: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projekti enable_all: Enable for all projects @@ -1459,8 +1490,8 @@ hr: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2436,7 +2467,7 @@ hr: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4482,6 +4513,10 @@ hr: one: 1 komentar other: "%{count} komentara" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 započet other: "%{count} započeta" @@ -4819,13 +4854,6 @@ hr: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Dodaj radne pakete permission_add_messages: Pošalji poruke diff --git a/config/locales/crowdin/hu.yml b/config/locales/crowdin/hu.yml index cbc0dd6c430..8fbba99af00 100644 --- a/config/locales/crowdin/hu.yml +++ b/config/locales/crowdin/hu.yml @@ -1357,6 +1357,37 @@ hu: edit: form_configuration: tab: Űrlap konfiguráció + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projektek enable_all: Enable for all projects @@ -1459,8 +1490,8 @@ hu: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2466,7 +2497,7 @@ hu: attribute_unknown_name: Érvénytelen munkacsomag értek %{attribute} duplicate_group: A %{group} név már használatban van. A csoport névnek egyedinek kell lennie. query_invalid: 'A beágyazott lekérdezés ''%{group}'' érvénytelen: %{details}' - group_without_name: Névtelen csoport nem engedélyezett. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4484,6 +4515,10 @@ hu: one: 1 komment other: "%{count} hozzászólás" zero: nincsenek hozzászólások + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 nyitott other: "%{count} nyitott" @@ -4840,13 +4875,6 @@ hu: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: feladatcsoport hozzáadása permission_add_messages: Elküldött üzenetek diff --git a/config/locales/crowdin/hy.seeders.yml b/config/locales/crowdin/hy.seeders.yml new file mode 100644 index 00000000000..2c7f7ea49a7 --- /dev/null +++ b/config/locales/crowdin/hy.seeders.yml @@ -0,0 +1,507 @@ +#This file has been generated by script/i18n/generate_seeders_i18n_source_file. +#Please do not edit directly. +#This file is part of the sources sent to crowdin for translation. +--- +hy: + seeds: + common: + colors: + item_0: + name: Blue (dark) + item_1: + name: Blue + item_2: + name: Blue (light) + item_3: + name: Green (light) + item_4: + name: Green (dark) + item_5: + name: Yellow + item_6: + name: Orange + item_7: + name: Red + item_8: + name: Magenta + item_9: + name: White + item_10: + name: Grey (light) + item_11: + name: Grey + item_12: + name: Grey (dark) + item_13: + name: Black + project_phase_colors: + item_0: + name: PM2 Orange + item_1: + name: PM2 Red + item_2: + name: PM2 Magenta + item_3: + name: PM2 Green Yellow + project_query_roles: + item_0: + name: Project query viewer + item_1: + name: Project query editor + work_package_roles: + item_0: + name: Work package editor + item_1: + name: Work package commenter + item_2: + name: Work package viewer + project_roles: + item_0: + name: Non member + item_1: + name: Anonymous + item_2: + name: Member + item_3: + name: Reader + item_4: + name: Project admin + global_roles: + item_0: + name: Staff and projects manager + item_1: + name: Standard global role + standard: + project_phases: + item_0: + name: Initiating + item_1: + name: Planning + start_gate: Ready for Planning + item_2: + name: Executing + start_gate: Ready for Executing + item_3: + name: Closing + start_gate: Ready for Closing + priorities: + item_0: + name: Low + item_1: + name: Normal + item_2: + name: High + item_3: + name: Immediate + projects: + demo-project: + name: Demo project + status_explanation: All tasks are on schedule. The people involved know their tasks. The system is completely set up. + description: This is a short summary of the goals of this demo project. + news: + item_0: + title: Welcome to your demo project + summary: | + We are glad you joined. + In this module you can communicate project news to your team members. + description: The actual news + categories: + item_0: Category 1 (to be changed in Project settings) + queries: + item_0: + name: Project plan + item_1: + name: Milestones + item_2: + name: Tasks + item_3: + name: Team planner + boards: + kanban: + name: Kanban board + basic: + name: Basic board + lists: + item_0: + name: Wish list + item_1: + name: Short list + item_2: + name: Priority list for today + item_3: + name: Never + parent_child: + name: Work breakdown structure + project-overview: + widgets: + item_0: + options: + name: Welcome + item_1: + options: + name: Getting started + text: | + We are glad you joined! We suggest to try a few things to get started in OpenProject. + + Discover the most important features with our [Guided Tour]({{opSetting:base_url}}/projects/demo-project/work_packages/?start_onboarding_tour=true). + + _Try the following steps:_ + + 1. *Invite new members to your project*: → Go to [Members]({{opSetting:base_url}}/projects/demo-project/members) in the project navigation. + 2. *View the work in your project*: → Go to [Work packages]({{opSetting:base_url}}/projects/demo-project/work_packages) in the project navigation. + 3. *Create a new work package*: → Go to [Work packages → Create]({{opSetting:base_url}}/projects/demo-project/work_packages/new). + 4. *Create and update a project plan*: → Go to [Project plan]({{opSetting:base_url}}/projects/demo-project/work_packages?query_id=##query.id:demo_project__query__project_plan) in the project navigation. + 5. *Activate further modules*: → Go to [Project settings → Modules]({{opSetting:base_url}}/projects/demo-project/settings/modules). + 6. *Complete your tasks in the project*: → Go to [Work packages → Tasks]({{opSetting:base_url}}/projects/demo-project/work_packages/details/##wp.id:set_date_and_location_of_conference/overview?query_id=##query.id:demo_project__query__tasks). + + Here you will find our [User Guides](https://www.openproject.org/docs/user-guide/). + Please let us know if you have any questions or need support. Contact us: [support[at]openproject.com](mailto:support@openproject.com). + item_5: + options: + name: Work packages + item_6: + options: + name: Milestones + work_packages: + item_0: + subject: Start of project + item_1: + subject: Organize open source conference + children: + item_0: + subject: Set date and location of conference + children: + item_0: + subject: Send invitation to speakers + item_1: + subject: Contact sponsoring partners + item_2: + subject: Create sponsorship brochure and hand-outs + item_1: + subject: Invite attendees to conference + item_2: + subject: Setup conference website + item_2: + subject: Conference + item_3: + subject: Follow-up tasks + children: + item_0: + subject: Upload presentations to website + item_1: + subject: Party for conference supporters :-) + description: |- + * [ ] Beer + * [ ] Snacks + * [ ] Music + * [ ] Even more beer + item_4: + subject: End of project + wiki: | + _In this wiki you can collaboratively create and edit pages and sub-pages to create a project wiki._ + + **You can:** + + * Insert text and images, also with copy and paste from other documents + * Create a page hierarchy with parent pages + * Include wiki pages to the project menu + * Use macros to include, e.g. table of contents, work package lists, or Gantt charts + * Include wiki pages in other text fields, e.g. project overview page + * Include links to other documents + * View the change history + * View as Markdown + + More information: [https://www.openproject.org/docs/user-guide/wiki/](https://www.openproject.org/docs/user-guide/wiki/) + scrum-project: + name: Scrum project + status_explanation: All tasks are on schedule. The people involved know their tasks. The system is completely set up. + description: This is a short summary of the goals of this demo Scrum project. + news: + item_0: + title: Welcome to your Scrum demo project + summary: | + We are glad you joined. + In this module you can communicate project news to your team members. + versions: + item_0: + name: Bug Backlog + item_1: + name: Product Backlog + item_2: + name: Sprint 1 + wiki: + title: Sprint 1 + content: | + ### Sprint planning meeting + + _Please document here topics to the Sprint planning meeting_ + + * Time boxed (8 h) + * Input: Product Backlog + * Output: Sprint Backlog + + * Divided into two additional time boxes of 4 h: + + * The Product Owner presents the [Product Backlog]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) and the priorities to the team and explains the Sprint Goal, to which the team must agree. Together, they prioritize the topics from the Product Backlog which the team will take care of in the next sprint. The team commits to the discussed delivery. + * The team plans autonomously (without the Product Owner) in detail and breaks down the tasks from the discussed requirements to consolidate a [Sprint Backlog]({{opSetting:base_url}}/projects/your-scrum-project/backlogs). + + + ### Daily Scrum meeting + + _Please document here topics to the Daily Scrum meeting_ + + * Short, daily status meeting of the team. + * Time boxed (max. 15 min). + * Stand-up meeting to discuss the following topics from the [Task board](##sprint:scrum_project__version__sprint_1). + * What do I plan to do until the next Daily Scrum? + * What has blocked my work (Impediments)? + * Scrum Master moderates and notes down [Sprint Impediments](##sprint:scrum_project__version__sprint_1). + * Product Owner may participate may participate in order to stay informed. + + ### Sprint Review meeting + + _Please document here topics to the Sprint Review meeting_ + + * Time boxed (4 h). + * A maximum of one hour of preparation time per person. + * The team shows the product owner and other interested persons what has been achieved in this sprint. + * Important: no dummies and no PowerPoint! Just finished product functionality (Increments) should be demonstrated. + * Feedback from Product Owner, stakeholders and others is desired and will be included in further work. + * Based on the demonstrated functionalities, the Product Owner decides to go live with this increment or to develop it further. This possibility allows an early ROI. + + + ### Sprint Retrospective + + _Please document here topics to the Sprint Retrospective meeting_ + + * Time boxed (3 h). + * After Sprint Review, will be moderated by Scrum Master. + * The team discusses the sprint: what went well, what needs to be improved to be more productive for the next sprint or even have more fun. + item_3: + name: Sprint 2 + categories: + item_0: Category 1 (to be changed in Project settings) + queries: + item_0: + name: Project plan + item_1: + name: Product backlog + item_2: + name: Sprint 1 + item_3: + name: Tasks + boards: + kanban: + name: Kanban board + basic: + name: Task board + lists: + item_0: + name: Wish list + item_1: + name: Short list + item_2: + name: Priority list for today + item_3: + name: Never + project-overview: + widgets: + item_0: + options: + name: Welcome + item_1: + options: + name: Getting started + text: | + We are glad you joined! We suggest to try a few things to get started in OpenProject. + + _Try the following steps:_ + + 1. *Invite new members to your project*: → Go to [Members]({{opSetting:base_url}}/projects/your-scrum-project/members) in the project navigation. + 2. *View your Product backlog and Sprint backlogs*: → Go to [Backlogs]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) in the project navigation. + 3. *View your Task board*: → Go to [Backlogs]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) → Click on right arrow on Sprint → Select [Task Board](##sprint:scrum_project__version__sprint_1). + 4. *Create a new work package*: → Go to [Work packages → Create]({{opSetting:base_url}}/projects/your-scrum-project/work_packages/new). + 5. *Create and update a project plan*: → Go to [Project plan](##query:scrum_project__query__project_plan) in the project navigation. + 6. *Create a Sprint wiki*: → Go to [Backlogs]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) and open the sprint wiki from the right drop down menu in a sprint. You can edit the [wiki template]({{opSetting:base_url}}/projects/your-scrum-project/wiki/) based on your needs. + 7. *Activate further modules*: → Go to [Project settings → Modules]({{opSetting:base_url}}/projects/your-scrum-project/settings/modules). + + Here you will find our [User Guides](https://www.openproject.org/docs/user-guide/). + Please let us know if you have any questions or need support. Contact us: [support[at]openproject.com](mailto:support@openproject.com). + item_5: + options: + name: Work packages + item_6: + options: + name: Project plan + work_packages: + item_0: + subject: New login screen + item_1: + subject: Password reset does not send email + item_2: + subject: New website + children: + item_0: + subject: Newsletter registration form + item_1: + subject: Implement product tour + item_2: + subject: New landing page + children: + item_0: + subject: Create wireframes for new landing page + item_3: + subject: Contact form + item_4: + subject: Feature carousel + children: + item_0: + subject: Make screenshots for feature tour + item_5: + subject: Wrong hover color + item_6: + subject: SSL certificate + item_7: + subject: Set-up Staging environment + item_8: + subject: Choose a content management system + item_9: + subject: Website navigation structure + children: + item_0: + subject: Set up navigation concept for website. + item_10: + subject: Internal link structure + item_11: + subject: Develop v1.0 + item_12: + subject: Release v1.0 + item_13: + subject: Develop v1.1 + item_14: + subject: Release v1.1 + item_15: + subject: Develop v2.0 + item_16: + subject: Release v2.0 + wiki: | + ### Sprint planning meeting + + _Please document here topics to the Sprint planning meeting_ + + * Time boxed (8 h) + * Input: Product Backlog + * Output: Sprint Backlog + + * Divided into two additional time boxes of 4 h: + + * The Product Owner presents the [Product Backlog]({{opSetting:base_url}}/projects/your-scrum-project/backlogs) and the priorities to the team and explains the Sprint Goal, to which the team must agree. Together, they prioritize the topics from the Product Backlog which the team will take care of in the next sprint. The team commits to the discussed delivery. + * The team plans autonomously (without the Product Owner) in detail and breaks down the tasks from the discussed requirements to consolidate a [Sprint Backlog]({{opSetting:base_url}}/projects/your-scrum-project/backlogs). + + + ### Daily Scrum meeting + + _Please document here topics to the Daily Scrum meeting_ + + * Short, daily status meeting of the team. + * Time boxed (max. 15 min). + * Stand-up meeting to discuss the following topics from the Task board. + * What do I plan to do until the next Daily Scrum? + * What has blocked my work (Impediments)? + * Scrum Master moderates and notes down Sprint Impediments. + * Product Owner may participate may participate in order to stay informed. + + ### Sprint Review meeting + + _Please document here topics to the Sprint Review meeting_ + + * Time boxed (4 h). + * A maximum of one hour of preparation time per person. + * The team shows the product owner and other interested persons what has been achieved in this sprint. + * Important: no dummies and no PowerPoint! Just finished product functionality (Increments) should be demonstrated. + * Feedback from Product Owner, stakeholders and others is desired and will be included in further work. + * Based on the demonstrated functionalities, the Product Owner decides to go live with this increment or to develop it further. This possibility allows an early ROI. + + + ### Sprint Retrospective + + _Please document here topics to the Sprint Retrospective meeting_ + + * Time boxed (3 h). + * After Sprint Review, will be moderated by Scrum Master. + * The team discusses the sprint: what went well, what needs to be improved to be more productive for the next sprint or even have more fun. + statuses: + item_0: + name: New + item_1: + name: In specification + item_2: + name: Specified + item_3: + name: Confirmed + item_4: + name: To be scheduled + item_5: + name: Scheduled + item_6: + name: In progress + item_7: + name: Developed + item_8: + name: In testing + item_9: + name: Tested + item_10: + name: Test failed + item_11: + name: Closed + item_12: + name: On hold + item_13: + name: Rejected + time_entry_activities: + item_0: + name: Management + item_1: + name: Specification + item_2: + name: Development + item_3: + name: Testing + item_4: + name: Support + item_5: + name: Other + types: + item_0: + name: Task + item_1: + name: Milestone + item_2: + name: Summary task + item_3: + name: Feature + item_4: + name: Epic + item_5: + name: User story + item_6: + name: Bug + welcome: + title: Welcome to OpenProject! + text: | + OpenProject is the leading open source project management software. It supports classic, agile as well as hybrid project management and gives you full control over your data. + + Core features and use cases: + + * [Project Portfolio Management](https://www.openproject.org/collaboration-software-features/project-portfolio-management/) + * [Project Planning and Scheduling](https://www.openproject.org/collaboration-software-features/project-planning-scheduling/) + * [Task Management and Issue Tracking](https://www.openproject.org/collaboration-software-features/task-management/) + * [Agile Boards (Scrum and Kanban)](https://www.openproject.org/collaboration-software-features/agile-project-management/) + * [Requirements Management and Release Planning](https://www.openproject.org/collaboration-software-features/product-development/) + * [Time and Cost Tracking, Budgets](https://www.openproject.org/collaboration-software-features/time-tracking/) + * [Team Collaboration and Documentation](https://www.openproject.org/collaboration-software-features/team-collaboration/) + + Welcome to the future of project management. + + For Admins: You can change this welcome text [here]({{opSetting:base_url}}/admin/settings/general). diff --git a/config/locales/crowdin/hy.yml b/config/locales/crowdin/hy.yml new file mode 100644 index 00000000000..63a227e69ec --- /dev/null +++ b/config/locales/crowdin/hy.yml @@ -0,0 +1,6182 @@ +#-- copyright +#OpenProject is an open source project management software. +#Copyright (C) the OpenProject GmbH +#This program is free software; you can redistribute it and/or +#modify it under the terms of the GNU General Public License version 3. +#OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +#Copyright (C) 2006-2013 Jean-Philippe Lang +#Copyright (C) 2010-2013 the ChiliProject Team +#This program is free software; you can redistribute it and/or +#modify it under the terms of the GNU General Public License +#as published by the Free Software Foundation; either version 2 +#of the License, or (at your option) any later version. +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +#You should have received a copy of the GNU General Public License +#along with this program; if not, write to the Free Software +#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +#See COPYRIGHT and LICENSE files for more details. +#++ +--- +hy: + no_results_title_text: There is currently nothing to display. + activities: + index: + no_results_title_text: There has not been any activity for the project within this time frame. + work_packages: + activity_tab: + no_results_title_text: No activity to display + no_results_description_text: Choose "Show everything" to show all activity and comments + label_activity_show_all: Show everything + label_activity_show_only_comments: Show comments only + label_activity_show_only_changes: Show changes only + label_sort_asc: Newest at the bottom + label_sort_desc: Newest on top + label_type_to_comment: Add a comment. Type @ to notify people. + label_submit_comment: Submit comment + label_who: Who? + changed_on: changed on + created_on: created this on + changed: changed + created: created + commented: commented + internal_comment: Internal comment + internal_journal: Internal comments are visible to a limited group of members. + unsaved_changes_confirmation_message: You have unsaved changes. Are you sure you want to close the editor? + internal_comment_confirmation: + title: Make this comment public? + heading: Make this comment public? + description: Your comment will be visible to anyone who can access this work package. Are you sure you want to do this? + confirm_button_text: Make public + admin: + plugins: + no_results_title_text: There are currently no plugins installed. + no_results_content_text: See our integrations and plugins page for more information. + custom_styles: + color_theme: Color theme + color_theme_custom: "(Custom)" + tab_interface: Interface + tab_branding: Branding + tab_pdf_export_styles: PDF export styles + tab_pdf_export_font: PDF export font + fonts: + file_too_large: is too large (maximum size is %{count} MB). + file_is_invalid: is not a valid TTF font file. + colors: + primary-button-color: Primary button + accent-color: Accent + header-bg-color: Header background + main-menu-bg-color: Main menu background + main-menu-bg-selected-background: Main menu when selected + custom_colors: Custom colors + manage_colors: Edit color select options + instructions: + primary-button-color: Strong accent color, used for the most important button on a screen. + accent-color: Color for links and other decently highlighted elements. + main-menu-bg-color: Left side menu's background color. + theme_warning: Changing the theme will overwrite you custom style. The design will then be lost. Are you sure you want to continue? + enterprise: + delete_dialog: + title: Delete enterprise token + heading: Delete this enterprise token? + confirmation: Are you sure you want to delete this Enterprise edition support token? + create_dialog: + title: Add Enterprise token + type_token_text: Your Enterprise token text + token_placeholder: Paste your Enterprise edition support token here + token_caption: To learn more about how to activate Enterprise edition check our [documentation](docs_url). + add_token: Upload an Enterprise edition support token + replace_token: Replace your current support token + order: Order Enterprise on-premises edition + paste: Paste your Enterprise edition support token + required_for_feature: This add-on is only available with an active Enterprise edition support token. + enterprise_link: For more information, click here. + start_trial: Start free trial + book_now: Book now + get_quote: Get a quote + buttons: + upgrade: Upgrade now + contact: Contact us for a demo + status: + expired: Expired + expiring_soon: Expiring soon + in_grace_period: In grace period + invalid_domain: Invalid domain + not_active: Not active + trial: Trial + jemalloc_allocator: Jemalloc memory allocator + journal_aggregation: + caption: 'User actions on a work package (changing description, status, values, or writing comments) are grouped if performed within this period. It also controls notification and [webhook](webhook_link) delays. + + ' + import: + title: Import + jira: + title: Jira Migrator + description: Use this tool to import data from your Jira instance. You can configure multiple Jira hosts and choose what to import in each import run. + errors: + cannot_delete_with_imports: Cannot delete Jira host with existing imports + custom_field_creation_failed: 'Failed to create custom field ''%{name}'': %{message}' + blank: + title: No Jira hosts configured yet + description: Configure a Jira host to start importing items from Jira to this OpenProject instance. + configuration: + title: Jira configuration + new: New configuration + banner: + title: Beta - Try it out! + description: This Jira Migrator is currently in beta. We currently only support Jira Server/Data Center versions 10.x and 11.x. Cloud instances are not supported at this time. + contribution_callout: 'Please, help us improve the Jira Migrator with your feedback and private data donations. You can [join the development community](link) of the Jira Migrator. + + ' + supported_versions: '' + form: + fields: + name: Name + url: Jira Server/Data Center URL + personal_access_token: Personal Access Token + button_add: Add configuration + button_save: Save configuration + button_test: Test configuration + button_delete_token: Delete token + delete_token_confirm: Are you sure you want to delete the token? This will disable the Jira connection. + label_testing: Testing configuration... + token_deleted: Token was successfully deleted. + test: + success: Successfully connected to %{server} (version %{version}) + failed: 'Connection failed: Unable to retrieve server information' + error: An unexpected error occurred while testing the connection + connection_error: 'Connection error: %{message}' + parse_error: Failed to parse the response from the server. The server may not be a valid Jira instance. + api_error: Jira API returned error status %{status}. Please check your Jira instance URL and API token. + token_error: Invalid API token. Please check your credentials in the configuration. + missing_credentials: Please provide both URL and Personal Access Token to test the connection + invalid_url: Please provide a valid URL + client: + connection_error: 'Failed to connect to Jira server: %{message}' + connection_timeout: 'Connection to Jira server timed out: %{message}' + parse_error: 'Failed to parse Jira API response: %{message}' + api_error: Jira API returned error status %{status} + 401_error: Jira API returned a 401 error. Your authentication token may have expired or lack the required permissions. Please ensure the token belongs to a Jira administrator. + columns: + projects: Projects + last_change: Last change + added: Added + label_ago: "%{amount} ago" + run: + title: Import run + history: History + remove_error: A Jira import run cannot be removed while it is running + import_blocked_error: Another Jira import run is currently in progress or awaiting review. Please complete or revert it before starting a new import. + project_identifier_taken: 'You are trying to import a project with an already used identifier: %{taken_identifier}. Please update the project identifier in Jira then click on Retry.' + blank: + title: No import runs set up yet + description: Create an import run to start importing information from this Jira instance + index: + description: You can import different sets of data with each import run. It is possible to undo an import run immediately after in review mode but not after finalizing. + button_import_run: Import run + button_edit_configuration: Edit configuration + status: + initial: Start + instance_meta_fetching: Fetching meta data + instance_meta_error: Error fetching meta data + instance_meta_done: Meta data fetched + import_scope: Select scope + configuring: Select scope + projects_meta_fetching: Fetching project data + projects_meta_error: Error fetching project data + projects_meta_done: Data gathered + importing: In progress + import_error: Error during import + imported: Review mode + reverting: Reverting + revert_error: Error during revert + revert_cancelling: Cancelling revert + revert_cancelled: Revert cancelled + reverted: Reverted + finalizing: Finalizing + finalizing_error: Error during finalizing + finalizing_done: Completed + wizard: + button_retry: Retry + parts: + projects: + one: 1 project + other: "%{count} projects" + issues: + one: 1 issue + other: "%{count} issues" + work_packages: + one: 1 work package + other: "%{count} work packages" + types: + one: 1 type + other: "%{count} types" + statuses: + one: 1 status + other: "%{count} statuses" + users: + one: 1 user + other: "%{count} users" + groups: + fetch: + title: Get base data + groups_and_users: + title: Groups and Users + configuration: + title: Configure import + confirming: + title: Confirm and import + review: + title: Review import + sections: + fetch_data: + title: Fetch instance meta data + caption_done: Completed + description: Check what data is available for import in the host Jira instance. + button_fetch: Check available data + label_progress: Fetching data from Jira... + groups_and_users: + title: Groups and Users + import_scope: + title: Import scope + caption: Choose what you want to import into OpenProject + caption_done: Completed + label_info: Please note that this import tool is in beta and cannot import all types of data. Here is a summary of what the host Jira instance offers for import and what this tool is able to import right now. + description: Select what data you want to import from the available data fetched from the host Jira instance. + label_supported_data: Supported data + label_coming_soon: Coming soon (Q2 2026) + label_coming_later: Coming later + label_available_server_data: Available data on %{server_info} + button_select_projects: Select projects to import + button_continue: Continue + label_import: Select which projects you would like to import. + button_select: Select projects + label_selected_data: Selected data for import + label_progress: Fetching data from Jira... + elements: + relations: Relations between issues + project_ids: Project identifiers + issue_ids: Issues identifiers + sprints: Sprint assignments + workflows: Project-level workflows + schemes: Schemas + permissions: Permissions + projects: Projects + issues: Issues + issue_details: Issue description, history, comments and attachments + custom_fields: A subset of custom fields + users: Involved users and groups + confirm_import: + title: Import data + caption: Review your import settings and start the import + caption_done: Completed + label_available_data: Data to be imported + label_users_import_explanation: Users that are involved in selected projects (group memberships included) + button_start: Start import + description: You are about to start an import run with the following settings. + label_progress: Import in progress... + label_import_data: Currently importing + import_result: + title: Import run results + caption: Review import run or revert import + info: Import run was successful. + label_results: Imported + label_revert: Revert import + button_revert: Revert import + button_done: Approve import + preview_description: The imported data is currently in review mode. Click "Approve import" to make the import permanent or "Revert import" to undo all changes made in this import run. + label_finalize_import: Approve import + label_finalizing: Approving import... + label_finalizing_done: Import approved. + label_revert_progress: Reverting import... + label_reverted: Import reverted. + select_dialog: + filter_projects: Filter by text + import_dialog: + title: Please make sure you have a backup! + confirm_button: Start import + description: 'Imports change your OpenProject configuration. After the import you will have the opportunity to review the changes. While in review, you have an option to revert or approve the import. After approving the import reverting will no longer be possible. Therefore, please, make sure that you have [a backup of your OpenProject instance](link) before proceeding. + + ' + confirm: I understand and have a backup + revert_dialog: + title: Permanently revert this import? + description: This will delete all imported objects (including whole projects). + confirm: I understand that this reversion will delete data permanently + finalize_dialog: + title: Approve this import? + description: Once approved, this import can no longer be reverted. All imported data will become permanent. + confirm: I understand that this action cannot be undone + confirm_button: Understood + select_projects: + title: Select projects + mcp_configurations: + index: + description: The model context protocol allows AI agents to provide its users with tools and resources exposed by this OpenProject instance. This feature is still in beta. + resources_heading: Resources + resources_description: OpenProject implements the following resources. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP resources](docs_url). + resources_submit: Update resources + tools_heading: Tools + tools_description: OpenProject implements the following tools. Each can be enabled, renamed and described as you want. For more information, please refer to the [documentation on MCP tools](docs_url). + tools_submit: Update tools + multi_update: + success: MCP configurations were updated successfully. + server_form: + description_caption: How the MCP server will be described to other applications who connect to it. + title_caption: A short title shown to applications that connect to the MCP server. + tool_response_format: Tool response format + tool_response_format_content_only_label: Content only + tool_response_format_content_only_caption: 'Choose this if MCP clients connecting to this instance do not support structured content. Tool responses will only contain plain text content and leave out the structured version. + + ' + tool_response_format_full_label: Full + tool_response_format_full_caption: 'The most compatible option. Tool responses will include both regular and structured content, allowing MCP clients to choose which format they want to read. This may increase the number of tokens that the language model has to process, potentially increasing cost and decreasing performance. + + ' + tool_response_format_structured_only_label: Structured content only + tool_response_format_structured_only_caption: 'Choose this if you are certain that MCP clients connecting to this instance support structured content. Tool responses will only include structured content and leave out its text representation. + + ' + update: + failure: MCP configuration could not be updated. + success: MCP configuration was updated successfully. + scim_clients: + authentication_methods: + sso: JWT from identity provider + oauth2_client: OAuth 2.0 client credentials + oauth2_token: Static access token + created_client_credentials_dialog_component: + title: Client credentials created + heading: Client credentials have been generated + one_time_hint: This is the only time you will see the client secret. Make sure to copy it now. + created_token_dialog_component: + title: Token created + heading: A token has been generated + label_token: Token + one_time_hint: This is the only time you will see this token. Make sure to copy it now. + delete_scim_client_dialog_component: + title: Delete SCIM client + heading: Are you sure you want to delete this SCIM client? + description: Users managed by this SCIM client can no longer be updated by it. + edit: + label_delete_scim_client: Delete SCIM client + form: + auth_provider_description: This is the service that users added by the SCIM provider will use to authenticate in OpenProject. + authentication_method_description_html: This is how the SCIM client authenticates at OpenProject. Please ensure that OAuth tokens include the scim_v2 scope. + description: Please refer to our [documentation on configuring SCIM clients](docs_url) for more information on these configuration options. + jwt_sub_description: For example, for Keycloak, this is the UUID of the service account associated with the SCIM client. Consult [our documentation](docs_url) to learn how to find the subject claim for your use case. + name_description: Choose a name that will help other admins better understand why this client was configured. + index: + description: SCIM clients configured here are able to interact with OpenProject SCIM server API to provision, update, and deprovision user accounts and groups. + label_create_button: Add SCIM client + new: + title: New SCIM client + revoke_static_token_dialog_component: + confirm_button: Revoke + title: Revoke static token + heading: Are you sure you want to revoke this token? + description: The SCIM client that uses this token will no longer be able to access OpenProject's SCIM server API. + table_component: + blank_slate: + title: No SCIM clients configured yet + description: Add clients to see them here + user_count: Users + token_list_component: + description: The tokens you generate here can be passed by a SCIM client to access the OpenProject SCIM API. + heading: Tokens + label_add_token: Token + label_aria_add_token: Add token + token_table_component: + blank_slate: + title: No tokens have been created yet + description: You can create one now + expired: Expired on %{date} + revoked: Revoked on %{date} + title: Access token table + settings: + new_project: + project_creation: Project creation + notification_text_default: "

Hello,

A new project has been created: projectValue:name

Thank you

\n" + work_packages_identifier: + page_header: + description: Choose between classic numerical work package IDs or semantic project-specific ones that prepend the project identifier to the work package ID. + banner: + existing_identifiers_notice: 'Existing identifiers for %{project_count} projects don''t meet requirements for project-based semantic identifiers. OpenProject can automatically update these so that they are valid as in the examples below. Click on ''Autofix and save'' to update identifiers for all projects in this manner and enable project-based semantic identifiers. + + ' + box_header: + label_project: Project + label_previous_identifier: Previous identifier + label_autofixed_suggestion: Future identifier + label_example_work_package_id: Example work package ID + autofix_preview: + error_too_long: Has to be 10 characters or fewer + error_numerical: Cannot be purely numerical + error_does_not_start_with_letter: Must start with an uppercase letter + error_special_characters: Special characters not allowed + error_not_fully_uppercased: Must be uppercase + error_in_use: Already in use as another project's active handle + error_used_in_past: Reserved by another project's handle history + error_reserved_by_system: Reserved as a system keyword + error_unknown: Needs manual review + remaining_projects: + one: "... 1 more project" + other: "... %{count} more projects" + button_autofix: Autofix and save + dialog: + title: Change work package identifiers + heading: Enable project-based work package IDs? + description: 'This will change IDs for all work packages in all projects in this instance. Previous identifiers and URLs will continue to redirect properly. This change will take some time to complete. + + ' + confirm_button: Change identifiers + checkbox_label: I understand that this will permanently change all work package IDs + success_banner: Successfully updated work package identifier format. + in_progress: + converting_banner_message: Project identifiers are currently being converted to semantic format. This may take some time. + reverting_banner_message: Project identifiers are currently being reverted to classic format. This may take some time. + workflows: + tabs: + default_transitions: Default transitions + user_author: User is author + user_assignee: User is assignee + status_button: Status + statuses_dialog: + title: Statuses + label: Statuses enabled for this type + caption: Add or remove statuses you would like to associate with this type. Removing a status will also delete the workflow associated with it. + statuses_removal_dialog: + title: Remove statuses + heading: + one: Remove 1 status? + other: Remove %{count} statuses? + description: Removing these statuses will make them unavailable to this type and delete existing workflows. Are you sure you want to proceed? + confirm: Remove + leave_confirmation: + title: Save changes before continuing? + description: You are about to leave this page but you have unsaved changes. Would you like to save them before continuing? + ignore: Ignore changes + save: Save changes and continue + role_selector: + title: Select roles + label: 'Role: %{role}' + no_role: Select role + roles: + one: One role selected + other: "%{count} roles selected" + blankslate: + title: No status transitions configured + description: Add statuses to start configuring workflows for this role + info: + database_deprecation_html: 'Starting with OpenProject 16.0, PostgreSQL 16 is required to use OpenProject. Your installation will remain functional with your current database, but anticipate incompatability in future releases.
We have prepared [upgrade guides for all installation methods](upgrade_guide). You can perform the upgrade ahead of the next release at any time by following the guides. + + ' + authentication: + login_and_registration: Login and registration + announcements: + show_until: Show until + is_active: currently displayed + is_inactive: currently not displayed + antivirus_scan: + not_processed_yet_message: Downloading is blocked, as file was not scanned for viruses yet. Please try again later. + quarantined_message: A virus was detected in file '%{filename}'. It has been quarantined and is not available for download. + deleted_message: A virus was detected in file '%{filename}'. The file has been deleted. + deleted_by_admin: The quarantined file '%{filename}' has been deleted by an administrator. + overridden_by_admin: The quarantine for file '%{filename}' has been removed by %{user}. The file can now be acccessed. + quarantined_attachments: + container: Container + delete: Delete the quarantined file + title: Quarantined attachments + error_cannot_act_self: Cannot perform actions on your own uploaded files. + attribute_help_texts: + caption: This short version will be displayed as caption of the attribute. + note_public: Any text and images you add to this field are publicly visible to all logged in users. + text_overview: In this view, you can create custom help texts for attributes view. When defined, these texts can be shown by clicking the help icon next to its belonging attribute. + show_preview: Preview text + add_new: Add help text + edit_field_name: Edit help text for %{attribute_field_name} + background_jobs: + status: + error_requeue: 'Job experienced an error but is retrying. The error was: %{message}' + cancelled_due_to: 'Job was cancelled due to error: %{message}' + ldap_auth_sources: + ldap_error: 'LDAP-Error: %{error_message}' + ldap_auth_failed: Could not authenticate at the LDAP-Server. + sync_failed: 'Failed to synchronize from LDAP: %{message}.' + back_to_index: Click here to go back to the list of connection. + technical_warning: | + This LDAP form requires technical knowledge of your LDAP / Active Directory setup. + [Please visit our documentation for detailed instructions](docs_url). + attribute_texts: + name: Arbitrary name of the LDAP connection + host: LDAP host name or IP address + login_map: The attribute key in LDAP that is used to identify the unique user login. Usually, this will be `uid` or `samAccountName`. + generic_map: The attribute key in LDAP that is mapped to the OpenProject `%{attribute}` attribute + admin_map_html: 'Optional: The attribute key in LDAP that if present marks the OpenProject user an admin. Leave empty when in doubt.' + system_user_dn_html: | + Enter the DN of the system user used for read-only access. +
+ Example: uid=openproject,ou=system,dc=example,dc=com + system_user_password: Enter the bind password of the system user + base_dn: | + Enter the Base DN of the subtree in LDAP you want OpenProject to look for users and groups. + OpenProject will filter for provided usernames in this subtree only. + Example: ou=users,dc=example,dc=com + filter_string: | + Add an optional RFC4515 filter to apply to the results returned for users filtered in the LDAP. + This can be used to restrict the set of users that are found by OpenProject for authentication and group synchronization. + filter_string_concat: | + OpenProject will always filter for the login attribute provided by the user to identify the record. If you provide a filter here, + it will be concatenated with an AND. By default, a catch-all (objectClass=*) will be used as a filter. + onthefly_register: | + If you check this box, OpenProject will automatically create new users from their LDAP entries + when they first authenticate with OpenProject. + Leave this unchecked to only allow existing accounts in OpenProject to authenticate through LDAP! + connection_encryption: Connection encryption + encryption_details: LDAPS / STARTTLS options + system_account: System account + system_account_legend: | + OpenProject requires read-only access through a system account to lookup users and groups in your LDAP tree. + Please specify the bind credentials for that system user in the following section. + ldap_details: LDAP details + user_settings: Attribute mapping + user_settings_legend: | + The following fields are related to how users are created in OpenProject from LDAP entries and + what LDAP attributes are used to define the attributes of an OpenProject user (attribute mapping). + tls_mode: + plain: none + simple_tls: LDAPS + start_tls: STARTTLS + plain_description: Opens an unencrypted connection to the LDAP server. Not recommended for production. + simple_tls_description: Use LDAPS. Requires a separate port on the LDAP server. This mode is often deprecated, we recommend using STARTTLS whenever possible. + start_tls_description: Sends a STARTTLS command after connecting to the standard LDAP port. Recommended for encrypted connections. + section_more_info_link_html: 'This section concerns the connection security of this LDAP authentication source. For more information, visit the Net::LDAP documentation. + + ' + tls_options: + verify_peer: Verify SSL certificate + verify_peer_description_html: 'Enables strict SSL verification of the certificate trusted chain.
Warning: Unchecking this option disables SSL verification of the LDAP server certificate. This exposes your connection to Man in the Middle attacks. + + ' + tls_certificate_description: If the LDAP server certificate is not in the trust sources of this system, you can add it manually here. Enter a PEM X509 certifiate string. + forums: + show: + no_results_title_text: There are currently no posts for the forum. + colors: + index: + no_results_title_text: There are currently no colors. + no_results_content_text: Create a new color + label_new_color: New color + new: + label_new_color: New Color + edit: + label_edit_color: Edit Color + form: + label_new_color: New color + label_edit_color: Edit color + label_no_color: No color + label_properties: Properties + label_really_delete_color: 'Are you sure, you want to delete the following color? Types using this color will not be deleted. + + ' + custom_actions: + actions: + name: Actions + add: Add action + assigned_to: + executing_user_value: "(Assign to executing user)" + conditions: Conditions + plural: Custom actions + new: New custom action + edit: Edit custom action %{name} + execute: Execute %{name} + custom_fields: + admin: + custom_field_projects: + is_for_all_blank_slate: + heading: For all projects + description: This custom field is enabled in all projects since the "For all projects" option is checked. It cannot be deactivated for individual projects. + items: + actions: Item actions + blankslate: + root: + title: Your list of items is empty + description: Start by adding items to the custom field of type hierarchy. Each item can be used to create a hierarchy bellow it. To navigate and create sub-items inside a hierarchy click on the created item. + item: + title: This item doesn't have any hierarchy level below + description: Add items to this list to create sub-items inside another one + delete_dialog: + title: Delete custom field item + heading: Delete custom field item? + description: This action will irreversibly remove the item and all its sub-items. Any assigned values will be permanently deleted. If this field is required, removing items may cause existing work packages to become invalid. + placeholder: + label: Item label + short: Short name + weight: Weight + notice: + remember_items_and_projects: Remember to set items and projects in the respective tabs for this custom field. + hierarchy: + subitems: + zero: no sub-items + one: 1 sub-item + other: "%{count} sub-items" + role_assignment: + title: Role Assignment + description: You can automatically grant a certain project role to any user assigned to this project attribute, regardless of that user’s original role in that project. + warning: Depending on the role selected below, the user assigned to this project attribute might gain significantly more permissions than they previously had, including the ability to add new members and elevate their role. + role_field_label: Project Role + role_field_caption: This project role will automatically be granted to any user assigned to this project attribute + review_hint: 'There are %{user_count} who are already assigned to this project attribute in various projects. They might get additional permissions and be added to projects they did not previously have access to. + + ' + review_button: Review users and permissions + dialog: + title: Overview of users and permissions + change: Change + changes: + new_member: Will be added as a member + remove_member: Will be removed as a member + gain_and_lose_role: Will lose role ‘%{old_role}’ and gain role ‘%{new_role}’ + gain_role: Will gain role ‘%{new_role}’ + lose_role: Will lose role ‘%{old_role}’ + no_change: No changes + text_add_new_custom_field: 'To add new custom fields to a project you first need to create them before you can add them to this project. + + ' + is_enabled_globally: Is enabled globally + enabled_in_project: Enabled in project + contained_in_type: Contained in type + confirm_destroy_option: Deleting an option will delete all of its occurrences (e.g. in work packages). Are you sure you want to delete it? + reorder_alphabetical: Reorder values alphabetically + reorder_confirmation: 'Warning: The current order of available values as well as all unsaved values will be lost. Are you sure you want to continue?' + placeholder_version_select: Work package or project selection is required first + calculated_field_not_editable: Non-editable attribute. This value is calculated automatically. + no_role_assigment: No role assignment + instructions: + is_required: + all: Mark the custom field as required. This will make it mandatory to fill in the field when creating new resources. Existing resources will not require a value when being updated. + project: Required attributes need to be filled out by the user on project creation if the field is active ('For all projects' set or copying from a project/template in which the field is active). Existing projects will not require a value when being updated. + is_for_all: + all: Mark the custom field as available in all existing and new projects. + project: Mark the attribute as available in all existing and new projects. + multi_select: + all: Allows the user to assign multiple values to this custom field. + project: Allows the user to assign multiple values to this attribute. + searchable: + all: Include the field values when using the global search functionality. + project: Check to make this attribute available as a filter in project lists. + editable: + all: Allow the field to be editable by users themselves. + admin_only: + all: Check to make this custom field only visible to administrators. Users without admin rights will not be able to view or edit it. + project: Check to make this attribute only visible to administrators. Users without admin rights will not be able to view or edit it. + is_filter: + all: 'Allow the custom field to be used in a filter in work package views. Note that only with ''For all projects'' selected, the custom field will show up in global views. + + ' + formula: + project: Add numeric values or type / to search for an attribute or a mathematical operator. + regexp: + all: eg. ^[A-Z0-9]+$ + project: eg. ^[A-Z0-9]+$ + min_max: + all: 0 means no restriction + project: 0 means no restriction + has_comment: + project: Allows the user to add a comment related to the project attribute when selecting the value in the project overview. + tab: + no_results_title_text: There are currently no custom fields. + no_results_content_text: Create a new custom field + calculated_values: + error_dialog: + title: Error with Calculated value + errors: + unknown: An unknown error occurred. Please review the formula for this Calculated value. + mathematical: The mathematical formula leads to an error. Please review the project calculation attribute and try again. + missing_value: The attribute "%{custom_field_name}" is required by this Calculated value, but is empty. + disabled_value: The attribute "%{custom_field_name}" is required by this Calculated value, but is disabled for the project. + concatenation: + single: or + danger_dialog: + confirmation_live_message_checked: The button to proceed is now active. + confirmation_live_message_unchecked: The button to proceed is now inactive. You need to tick the checkbox to continue. + departments: + edit: Edit department + add_user: Add user + add_department: Add department + blankslate: + heading: Your organization has no departments + description: 'Start by adding departments or users to the organization. Each department can be used to create a hierarchy below it, to navigate and create sub-department inside a hierarchy click on the created item. + + ' + add_button: Add + detail_blankslate: + heading: This department doesn’t have any hierarchy level below + description: Add departments or users to create sub-items inside another one. + add_button: Add + add_department_form: + name_label: Department name + name_placeholder: Enter department name + move_user_dialog: + title: User already in a department + heading: Move user to this department? + description: "%{user} is currently a member of %{from_department}. Moving them will remove them from that department." + confirm: Move user + context_menu: + add_sub_department: Add sub-department + add_user: Add user + flash: + user_added: User was successfully added to the department. + user_removed: User was successfully removed from the department. + department_created: Department was successfully created. + errors: + move_user_failed: Failed to move user between departments. + pagination: + label: Pagination + prev: Previous + prev_page: Previous Page + next: Next + next_page: Next Page + page: Page %{number} + page_with_more: Page %{number}... + mcp_configurations: + server_url_component: + caption: The URL at which the OpenProject MCP server will be reachable. Required for setting up MCP clients. + label: Server URL + op_dry_validation: + or: or + errors: + unexpected_key: is not allowed. + array?: must be an array. + decimal?: must be a decimal. + defined: must not be defined. + eql?: must be equal to %{left}. + filled?: must be filled. + format?: is in invalid format. + greater_or_equal_zero: must be greater or equal to 0. + gteq?: must be greater than or equal to %{num}. + hash?: must be a hash. + included_in?: + arg: + default: 'must be one of: %{list}.' + range: 'must be one of: %{list_left} - %{list_right}.' + int?: must be an integer. + key?: is missing. + not_found: not found. + respond_to?: does not implement required method. + rules: + copy_workflow_from: + workflow_missing: has no own workflow. + custom_field: + format_not_supported: format '%{field_format}' is unsupported. + item: + root_item: cannot be a root item. + not_persisted: must be an already existing item. + label: + not_unique: must be unique within the same hierarchy level. + short: + not_unique: must be unique within the same hierarchy level. + parent: + not_descendant: must be a descendant of the hierarchy root. + str?: must be a string. + time?: must be a time. + type?: must be %{type}. + uri?: is not a valid URI. + rules: + copy_workflow_from: Type for workflow copy + enabled: Enabled + depth: Depth + item: Item + label: Label + weight: Weight + short: Short name + parent: Parent + blueprint: Pattern blueprint + global_search: + title: + all_projects: Search for "%{search_term}" in all projects + current_project: Search for "%{search_term}" in %{project_name} + project_and_subprojects: Search for "%{search_term}" in %{project_name} and all subprojects + placeholder: Search in %{app_title} + overwritten_tabs: + all: All + messages: Forum + wiki_pages: Wiki + groups: + edit: + synchronized_groups: Synchronized groups + index: + description: By grouping users together, you can add them as members to the same projects or assign the same global roles to them. + table_component: + blank_slate: + description: You can define named groups of users with specific permissions. + title: No groups set up yet + user_count: User count + users: + no_results_title_text: There are currently no users part of this group. + memberships: + no_results_title_text: There are currently no projects part of this group. + synchronized_groups: + blankslate: + action: Authentication settings + description: When this group is automatically synced with groups in external identity providers like OpenID, they will appear here. You can set this up in your Authentication settings. + title: No synchronized groups yet + incoming_mails: + ignore_filenames: 'Specify a list of names to ignore when processing attachments for incoming mails (e.g., signatures or icons). Enter one filename per line. + + ' + portfolios: + index: + search: + label: Portfolio name filter + placeholder: Search portfolios + sub_items_html: + one: "1 sub-item" + other: "%{count} sub-items" + lists: + active: Active portfolios + my: My portfolios + favorited: Favorite portfolios + archived: Archived portfolios + projects: + copy: + members: Project members + overviews: Project overview + queries: 'Work packages: saved views' + wiki_page_attachments: 'Wiki pages: attachments' + work_package_attachments: 'Work packages: attachments' + work_package_categories: 'Work packages: categories' + work_package_file_links: 'Work packages: file links' + work_package_shares: 'Work packages: shares' + create: + notification_email_subject: Your project '%{project_name}' has been created + complete_wizard_link: Complete the %{artefact_name} + delete: + scheduled: Deletion has been scheduled and is performed in the background. You will be notified of the result. + schedule_failed: 'Project cannot be deleted: %{errors}' + failed: Deletion of project '%{name}' has failed + failed_text: The request to delete project '%{name}' has failed. The project was left archived. + completed: Deletion of project '%{name}' completed + completed_text: The request to delete project '%{name}' has been completed. + completed_text_children: 'Additionally, the following subprojects have been deleted:' + index: + open_as_gantt: Open as Gantt view + no_results_title_text: There are currently no projects + no_results_content_text: Create a new project + search: + label: Project name filter + placeholder: Search by project name + lists: + active: Active projects + my: My projects + favorited: Favorite projects + archived: Archived projects + shared: Shared project lists + my_lists: My project lists + new: + placeholder: New project list + delete_modal: + title: Delete project list + heading: Delete this project list? + text: This action will not delete any project the list contains. Are you sure you want to delete this project list? + settings: + header_details: Basic details + header_status: Status + header_relations: Project relations + button_update_details: Update details + button_update_status_description: Update status description + button_update_parent_project: Update parent project + public_warning: 'This project is public. Anyone who has access to this instance will be able to view and interact with this project depending on their role and associated permissions. Sub-projects are not affected and have their own settings. + + ' + public_confirmation: + checkbox: I understand that this will make the previously private content public + title: Make this project public? + description: 'Anyone who has access to this instance will be able to view and interact with this project depending on their role and authentication settings. Sub-projects are not affected and have their own settings. + + ' + private_confirmation: + checkbox: I understand that this will make the previously public content private. + title: Make this project private? + description: 'The project will only be visible to project members depending on their role and associated permissions. Sub-projects are not affected and have their own settings. + + ' + change_identifier: Change identifier + change_identifier_dialog_title: Change project identifier + change_identifier_format_hint_semantic: Only uppercase letters (A–Z), numbers or underscores. Max 10 characters. Must start with a letter. + change_identifier_format_hint_legacy: Only lowercase letters (a–z), numbers, dashes or underscores. + change_identifier_warning: 'This will permanently change identifiers and URLs of all work packages in this project. The previous identifier and URLs will nevertheless continue to redirect properly. + + ' + subitems: + template_section: 'Select templates to be used when creating new subitems. + + ' + project_template_label: Template for projects + project_template_caption: Select a template project to be used as the default for new subitems of this type. + program_template_label: Template for programs + program_template_caption: Select a template program to be used as the default for new subitems of this type. + no_template: No predefined template + template: + menu_title: Template + title: Template settings + enable_failed: Failed to enable template mode. + members: + excluded_roles_label: Roles to exclude when template is applied + excluded_roles_caption: 'When creating a new project from this template, the roles selected above will be omitted. This allows you to select which members will be excluded based on their project roles. Users can then access the template for viewing purposes without being granted access to new projects created from it. + + ' + actions: + label_enable_all: Enable all + label_disable_all: Disable all + activities: + no_results_title_text: There are currently no activities available. + forums: + no_results_title_text: There are currently no forums for the project. + no_results_content_text: Create a new forum + categories: + no_results_title_text: There are currently no work package categories. + no_results_content_text: Create a new work package category + custom_fields: + no_results_title_text: There are currently no custom fields available. + life_cycle: + header: + title: Project life cycle + description_html: The active project phases define this project's life cycle and are defined in the administration settings. Enabled phases will be displayed in your project overview. + non_defined: No phases are currently defined. + section_header: Phases + step: + use_in_project: Use %{step} in this project + filter: + label: Search project phase + project_custom_fields: + header: + title: Project attributes + description_html: 'These project attributes will be displayed in your project overview page under their respective sections. You can enable or disable individual attributes. Project attributes and sections are defined in the administration settings by the administrator of the instance. ' + filter: + label: Search project attribute + actions: + label_enable_single: Active in this project, click to disable + label_disable_single: Inactive in this project, click to enable + remove_from_project: Remove from project + is_for_all_blank_slate: + heading: For all projects + description: This project attribute is enabled in all projects since the "For all projects" option is checked. It cannot be deactivated for individual projects. + enabled_via_assignee_when_submitted_html: This project attribute cannot be disabled since it is set as assignee when submitted for project initiation requests. + types: + no_results_title_text: There are currently no types available. + form: + enable_type_in_project: Enable type "%{type}" + versions: + no_results_title_text: There are currently no versions for the project. + no_results_content_text: Create a new version + work_packages_graph: Work packages graph + show_work_packages: Show work packages + storage: + no_results_title_text: There is no additional recorded disk space consumed by this project. + work_package_priorities: + new_label: New priority + creation_wizard: + errors: + no_work_package_type: Failed to enable project initiation request because it requires at least one active work package type and this project has none. Please add at least one work package type to this project. + no_status_when_submitted: Failed to enable project initiation request because work package type %{type} requires at least one status associated with it. Please enable at least one status workflow for this work package type. + export: + description_attachment_export: The generated artifact will be saved as a PDF attachment to the artifact work package. + description_file_link_export: The artifact work package will have a file link to a PDF stored in an external file storage. Requires a working file storage with automatically-managed project folders for this project. At the moment only Nextcloud file storages are supported. + description_file_storage_selection: Select which of the configured external file storages should be used. + external_file_storage: External file storage + label_artifact_export: Artifact export + label_attachment_export: Save as work package file attachment + label_file_link_export: Upload file to external file storage and add file link to work package + pdf_file_storage: PDF file storage + unavailable: unavailable + label_request_submission: Request submission + project_attributes_description: 'Select which project attributes should be included in the project initiation request. This list only includes [project attributes](project_attributes_url) enabled for for this project. + + ' + enabled_because_required_html: This project attribute cannot be disabled for this project initiation request since it is defined as required. This can be changed in the administration settings by the administrator of the instance. + status: + button_edit: Edit status + wizard: + sidebar_content_title: Content + sections: Sections + title: Project initiation request + no_help_text: This attribute has no help text defined. + success: Project attributes saved and artifact work package created successfully. + progress_label: "%{current} of %{total}" + create_artifact_work_package_error: Failed to create artifact work package + create_artifact_storage_error: Failed to store artifact in file storage + lists: + create: + success: The modified list has been saved as a new list + failure: 'The modified list cannot be saved: %{errors}' + update: + success: The modified list has been saved + failure: 'The modified list cannot be saved: %{errors}' + publish: + success: The list has been made public + failure: 'The list cannot be made public: %{errors}' + unpublish: + success: The list has been made private + failure: 'The list cannot be made private: %{errors}' + can_be_saved: 'List modified:' + can_be_saved_as: 'The modifications can only be saved in a new list:' + members: + index: + no_results_title_text: There are currently no members part of this project. + no_results_content_text: Add a member to the project + invite_by_mail: Send invite to %{mail} + send_invite_to: Send invite to + columns: + shared: Shared + filters: + all_shares: All shares + menu: + all: All + invited: Invited + locked: Locked + project_roles: Project roles + wp_shares: Work package shares + groups: Groups + delete_member_dialog: + title: Remove member + will_remove_the_users_role: This will remove the user’s role from this project. + will_remove_the_groups_role: This will remove the group role from this project. + however_work_packages_shared_with_user_html: + one: However, %{shared_work_packages_link} has also been shared with this user. + other: However, %{shared_work_packages_link} have also been shared with this user. + however_work_packages_shared_with_group_html: + one: However, %{shared_work_packages_link} has also been shared with this group. + other: However, %{shared_work_packages_link} have also been shared with this group. + remove_work_packages_shared_with_user_too: A user that has been removed as member can still access shared work packages. Would you like to remove the shares too? + remove_work_packages_shared_with_group_too: A group that has been removed as member can still access shared work packages. Would you like to remove the shares too? + will_not_affect_inherited_shares: "(This will not affect work packages shared with their group)." + can_remove_direct_but_not_shared_roles: You can remove this user as a direct project member but a group they are in is also a member of this project, so they will continue being a member via the group. + also_work_packages_shared_with_user_html: + one: Also, %{shared_work_packages_link} has been shared with this user. + other: Also, %{shared_work_packages_link} have been shared with this user. + remove_project_membership_or_work_package_shares_too: Do you want to remove just the user as a direct member (and keep the shares) or remove the work package shares too? + will_remove_all_user_access_priveleges: Deleting this member will remove all access privileges of the user to the project. The user will still exist as part of the instance. + will_remove_all_group_access_priveleges: Deleting this member will remove all access privileges of the group to the project. The group will still exist as part of the instance. + cannot_delete_inherited_membership: You cannot delete this member because they belong to a group that is itself a member of this project. + cannot_delete_inherited_membership_note_admin_html: You can either remove the group as a member of the project or this specific member from the group in the %{administration_settings_link}. + cannot_delete_inherited_membership_note_non_admin: You can either remove the group as a member of the project or contact your administrator to remove this specific member from the group. + delete_work_package_shares_dialog: + title: Revoke work package shares + shared_with_this_user_html: + one: "%{all_shared_work_packages_link} has been shared with this user." + other: "%{all_shared_work_packages_link} have been shared with this user." + shared_with_this_group_html: + one: "%{all_shared_work_packages_link} has been shared with this group." + other: "%{all_shared_work_packages_link} have been shared with this group." + shared_with_permission_html: + one: Only %{shared_work_packages_link} has been shared with %{shared_role_name} permissions. + other: Only %{shared_work_packages_link} have been shared with %{shared_role_name} permissions. + revoke_all_or_with_role: Would you like to revoke access to all shared work packages, or only those with %{shared_role_name} permissions? + will_not_affect_inherited_shares: "(This will not affect work packages shared with their group)." + cannot_remove_inherited: The work packages shares shared via groups cannot be removed. + cannot_remove_inherited_with_role: The work packages shares with role %{shared_role_name} are shared via groups and cannot be removed. + cannot_remove_inherited_note_admin_html: You can either revoke the share to the group or remove this specific member from the group in the %{administration_settings_link}. + cannot_remove_inherited_note_non_admin: You can either revoke the share to the group or contact your administrator to remove this specific member from the group. + will_revoke_directly_granted_access: This action will revoke their access to all of them, but the work packages shared with a group. + will_revoke_access_to_all: This action will revoke their access to all of them. + my: + access_token: + dialog: + token/api: + dialog_title: Create new API token + attention_text: Treat API tokens like passwords. Anyone with this token will have access to information from this instance, share it only with trusted users. + dialog_body: This token will allow third-party applications to communicate with your instance. To differentiate the new API token, please give it a name. + create_button: Create + name_label: Token name + created_dialog: + one_time_warning: This is the only time you will see this token. Make sure to copy it now. + token/api: + title: The API token has been generated + token/rss: + title: The RSS token has been generated + failed_to_reset_token: 'Failed to reset access token: %{error}' + failed_to_create_token: 'Failed to create access token: %{error}' + failed_to_revoke_token: 'Failed to revoke access token: %{error}' + notice_reset_token: 'A new %{type} token has been generated. Your access token is:' + token_value_warning: 'Note: This is the only time you will see this token, make sure to copy it now.' + no_results_title_text: There are currently no access tokens available. + notice_api_token_revoked: The API token has been deleted. To create a new token please use the button in the API section. + notice_rss_token_revoked: The RSS token has been deleted. To create a new token please use the link in the RSS section. + notice_ical_token_revoked: iCalendar token "%{token_name}" for calendar "%{calendar_name}" of project "%{project_name}" has been revoked. The iCalendar URL with this token is now invalid. + password_confirmation_dialog: + confirmation_required: You need to enter your account password to confirm this change. + title: Confirm your password to continue + news: + index: + no_results_title_text: There is currently no news to report. + no_results_content_text: Add a news item + roles: + edit: + default_for_new_projects_warning: 'This role is configured as the default role given to non-admin users who create a project. Do not remove the following permissions, otherwise project creators will be unable to complete the setup of their newly created projects:' + permissions: + section_check_all_label: Assign all %{module} permissions + section_uncheck_all_label: Unassign all %{module} permissions + report: + matrix_caption: Permissions matrix for %{module} module + matrix_checkbox_label: Assign %{permission} permission to %{role} role + matrix_check_all_label: Assign all %{module} permissions to all roles + matrix_uncheck_all_label: Unassign all %{module} permissions from all roles + matrix_check_uncheck_all_in_row_label_html: Toggle %{permission} permission for all roles + matrix_check_uncheck_all_in_col_label_html: Toggle all %{module} permissions for %{role} role + users: + force_password_change_hint: The user must set a new password on their next login. Automatically enabled when sending credentials via email. + send_information_hint: Emails the password in plain text. When checked, the user will be required to change their password on first login. + autologins: + prompt: Stay logged in for %{num_days} + sessions: + session_name: "%{browser_name} %{browser_version} on %{os_name}" + browser: Browser + expires: Expires + last_connection: Last connection + device: Device / OS + unknown_browser: unknown browser + unknown_os: unknown operating system + unknown: "(unknown)" + browser_session: "(Browser session)" + current: Current (this device) + title: Session management + instructions: You are logged in to your account through the following devices. Revoke sessions that you do not recognise or from devices you do not control. + may_not_delete_current: You cannot delete your current session. + deletion_warning: Are you sure you want to revoke this session? You will be logged out on this device. + groups: + member_in_these_groups: 'This user is currently a member of the following groups:' + no_results_title_text: This user is currently not a member in any group. + summary_with_more_html: Member of %{names} and %{count_link}. + more: "%{count} more" + summary_html: Member of %{names}. + memberships: + no_results_title_text: This user is currently not a member of a project. + open_profile: Open profile + invite_user_modal: + invite: Invite + title: + invite: Invite user + invite_to_project: Invite %{type} to %{project} + invite_principal_to_project: Invite %{principal} to %{project} + project: + label: Project + required: Please select a project + next_button: Next + no_results: No projects were found + no_invite_rights: You are not allowed to invite members to this project + type: + required: Please select the type to be invited + user: + title: Invite user to %{project_name} + description: Permissions based on the assigned role in the selected project + group: + title: Invite group to %{project_name} + description: Permissions based on the assigned role in the selected project + placeholder_user: + title: Add placeholder user to %{project_name} + title_no_ee: Placeholder user (Enterprise edition only add-on) + description: Has no access to the project and no emails are sent out. + already_member_message: Already a member of %{project} + principal: + no_results_user: No users were found + invite_user: 'Invite:' + no_results_placeholder: No placeholders were found + create_new_placeholder: 'Create new placeholder:' + no_results_group: No groups were found + invite_to_project: Invite to %{project_name} + required: + user: Please select a user + placeholder: Please select a placeholder + group: Please select a group + role: + label: Role in %{project} + no_roles_found: No roles were found + description: 'This is the role that the user will receive when they join your project. The role defines which actions they are allowed to take and which information they are allowed to see. [Learn more about roles and permissions.](docs_url) + + ' + required: Please select a role + message: + label: Invitation message + description: We will send an email to the user, to which you can add a personal message here. An explanation for the invitation could be useful, or perhaps a bit of information regarding the project to help them get started. + summary: + next_button: Send invitation + success_message: + user: The user can now log in to access %{project}. Meanwhile you can already plan with that user and assign work packages for instance. + placeholder_user: The placeholder can now be used in %{project}. Meanwhile you can already plan with that user and assign work packages for instance. + group: The group is now a part of %{project}. Meanwhile you can already plan with that group and assign work packages for instance. + working_hours: + current_schedule: + title: Current schedule + work_days: Work days + work_hours: Work hours + availability_factor: Availability factor + availability_subtitle: Dedicated to project work + effective_hours: Effective work hours + effective_subtitle: Per week + not_set: Not set + future: + title: Future schedules + description: Plan working schedule changes ahead of time. Once the date arrives your working schedules will be updated automatically. + add_button: Add future schedule + blank_title: No future schedules planned + blank_description: Create a future schedule to plan changes ahead of time + history: + title: Schedule history + description: View your past work schedules. + blank_title: No schedule history yet + blank_description: Past schedule changes will appear here + destroy: + confirm: Are you sure you want to delete this working schedule? + form: + title: Plan a future work schedule + title_current: Edit current work schedule + start_date: Start date + start_date_caption: Select the date from when the new work schedule will be effective. + work_days: Work days + working_hours_label: Working hours + hours_mode_label: Hours mode + same_hours_mode: Same hours per day + individual_hours_mode: Individual hours per day + work_hours: Work hours + hours_per_day: Hours per day + per_day: per day + per_week: per week + total_work_hours: Total work hours + availability_description: The availability factor represents the actual percentage of your working time dedicated to project tasks. This accounts for meetings, emails, administrative work, and other non-project activities. + availability_factor: Availability factor + availability_factor_caption: Define the percentage of your working time dedicated to project work. + total_available_hours: Total available work hours + title_availability_factor: Availability factor + title_days_and_hours: Days and hours + title_future_dates: Future dates + table: + mobile_title: Working schedules + start_date: Start date + work_days: Work days + work_hours: Work hours + availability_factor: Availability factor + effective_work_hours: Effective work hours + work_days_count: + one: 1 working day + other: "%{count} working days" + user_preferences: + disable_keyboard_shortcuts_caption: 'You can choose to disable default [keyboard shortcuts](docs_url) if you use a screen reader or want to avoid accidentally triggering an action with a shortcut. + + ' + page: + text: Text + placeholder_users: + right_to_manage_members_missing: 'You are not allowed to delete the placeholder user. You do not have the right to manage members for all projects that the placeholder user is a member of. + + ' + delete_tooltip: Delete placeholder user + deletion_info: + heading_html: Delete placeholder user %{name} + data_consequences: 'All occurrences of the placeholder user (e.g., as assignee, responsible or other user values) will be reassigned to an account called "Deleted user". As the data of every deleted account is reassigned to this account it will not be possible to distinguish the data the user created from the data of another deleted account. + + ' + irreversible: This action is irreversible + confirmation_html: Enter the placeholder user name %{name} to confirm the deletion. + priorities: + edit: + priority_color_text: | + Click to assign or change the color of this priority. + It can be used for highlighting work packages in the table. + admin: + default: + caption: Making this priority default will override the previous default priority. + reactions: + action_title: React + add_reaction: Add reaction + react_with: React with %{reaction} + and_user: and %{user} + and_others: + one: and 1 other + other: and %{count} others + reaction_by: "%{reaction} by" + reportings: + index: + no_results_title_text: There are currently no status reportings. + no_results_content_text: Add a status reporting + statuses: + edit: + status_color_text: | + Click to assign or change the color of this status. + It is shown in the status button and can be used for highlighting work packages in the table. + status_default_text: New work packages are by default set to this type. They cannot be read-only. + status_excluded_from_totals_text: |- + Check this option to exclude work packages with this status from totals of Work, + Remaining work, and % Complete in a hierarchy. + status_percent_complete_text_html: |- + In [status-based progress calculation mode](setting_url), the % Complete of a work + package is automatically set to this value when this status is selected. + Ignored in work-based mode. + status_readonly_html: | + Check this option to mark work packages with this status as read-only. + No attributes can be changed with the exception of the status. +
+ Note: Inherited values (e.g., from children or relations) will still apply. + index: + no_results_title_text: There are currently no work package statuses. + no_results_content_text: Add a new status + headers: + is_default: Default + is_closed: Closed + is_readonly: Read-only + excluded_from_totals: Excluded from totals + themes: + dark: Dark + light: Light + sync_with_os: Automatic (match OS color mode) + types: + index: + no_results_title_text: There are currently no types. + no_results_content_text: Create a new type + edit: + form_configuration: + tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' + projects: + tab: Projects + enable_all: Enable for all projects + select_projects: Select projects + select_projects_description: Select the projects in which you would like to use this type. + settings: + tab: Settings + type_color_text: The selected color distinguishes different types in Gantt charts or work packages tables. It is therefore recommended to use a strong color. + subject_configuration: + tab: Subject configuration + manually_editable_subjects: + label: Manually editable subjects + caption: Users can manually enter and edit work package subjects without restrictions. + automatically_generated_subjects: + label: Automatically generated subjects + caption: Define a pattern using referenced attributes and text to automatically generate work package subjects. Users will not be able to manually edit subjects. + token: + label_with_context: "%{attribute_context}: %{attribute_label}" + context: + work_package: Work package + parent: Parent + project: Project + pattern: + label: Subject pattern + caption: Create patterns by adding text, or type "/" to search for [supported attributes](attributes_url). + insert_as_text: 'No attributes found. Add as text: "%{word}"' + export_configuration: + tab: Generate PDF + intro: Select which templates from those that are available you would like to enable for this type. The template determines the design and attributes visible in the exported PDF of a work package using this type. The first template on the list is selected by default. + pdf_export_templates: + label: PDF Export templates + actions: + label_enable_all: Enable all + label_disable_all: Disable all + versions: + overview: + work_packages_in_archived_projects: The version is shared with archived projects which still have work packages assigned to this version. These are counted, but will not appear in the linked views. + no_results_title_text: There are currently no work packages assigned to this version. + wiki: + page_not_editable_index: The requested page does not (yet) exist. You have been redirected to the index of all wiki pages. + no_results_title_text: There are currently no wiki pages. + print_hint: This will print the content of this wiki page without any navigation bars. + index: + no_results_content_text: Add a new wiki page + workflows: + copies: + form: + source_role_id: + label: Source role + target_role_ids: + label: Target roles + target_type_ids: + label: Target types + mode: + from_role: + label: Copy to other roles + caption: Copy the current workflow to one or more roles inside the same work package type. If the selected role already has a workflow the current one will be overwritten. + from_type: + label: Copy to another type + caption: Copy the current workflow to another work packages type. If the selected type already has a workflow the current one will be overwritten. This affects all roles. + from_roles: + create: + notice: + one: Successfully copied workflow to '%{role_name}' role. + other: Successfully copied workflow to %{count} roles. + from_types: + create: + notice: + one: Successfully copied workflow to '%{type_name}' type. + other: Successfully copied workflow to %{count} types. + new: + title: Copy workflow of "%{source_type}" + form: + matrix_caption: Workflow matrix + matrix_caption_assignee: Workflow matrix for assignee + matrix_caption_author: Workflow matrix for author + matrix_checkbox_label: Allow transition from %{old_status} to %{new_status} + matrix_check_all_label: Allow all transitions + matrix_uncheck_all_label: Disallow all transitions + matrix_check_uncheck_all_in_row_label_html: Toggle transitions from %{old_status} to all new statuses + matrix_check_uncheck_all_in_col_label_html: Toggle transitions from all old statuses to %{new_status} + index: + type_filter: + label: Filter by type name… + page_headers: + index_component: + description: Configure status transitions for each work package type. + work_flows: + index: + no_results_title_text: There are currently no workflows. + work_packages: + delete_dialog: + title: Delete work package + heading: Permanently delete this work package? + description: Are you sure you want to delete the work package "%{name}"? + confirm_descendants_deletion: I acknowledge that ALL descendants of this work package will be recursively removed. + cross_project_warning: 'Work packages from the following projects will be deleted: %{projects}' + bulk_delete_dialog: + title: Delete %{count} work packages + heading: Permanently delete these %{count} work packages? + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' + confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. + cross_project_warning: 'These work packages span multiple projects: %{projects}' + children_label: 'The following children will also be deleted:' + datepicker_modal: + banner: + description: + automatic_mobile: Start date derived. + click_on_show_relations_to_open_gantt: Click on "%{button_name}" for Gantt overview. + manual_mobile: Ignoring relations. + manual_gap_between_predecessors: There is a gap between this and all predecessors. + manual_overlap_with_predecessors: Overlaps with at least one predecessor. + manual_with_children: This has child work packages but their start dates are ignored. + title: + automatic_mobile: Automatically scheduled. + automatic_with_children: The dates are determined by child work packages. + automatic_with_predecessor: The start date is set by a predecessor. + manual_mobile: Manually scheduled. + manually_scheduled: Manually scheduled. Dates not affected by relations. + blankslate: + title: No predecessors + description: To enable automatic scheduling, this work package needs to have at least one predecessor. It will then automatically be scheduled to start after the closest predecessor. + ignore_non_working_days: + title: Working days only + mode: + title: Scheduling mode + automatic: Automatic + manual: Manual + show_relations: Show relations + update_inputs_aria_live_message: Date picker updated. %{message} + tabs: + aria_label: Datepicker tabs + children: Children + dates: Dates + predecessors: Predecessors + successors: Successors + blankslate: + predecessors: + title: No predecessors + description: This work package does not have any predecessors. + successors: + title: No successors + description: This work package does not have any successors. + children: + title: No children + description: This work package does not have any children. + x_descendants: + one: One descendant work package + other: "%{count} work package descendants" + bulk: + copy_failed: The work packages could not be copied. + move_failed: The work packages could not be moved. + could_not_be_saved: 'The following work packages could not be saved:' + none_could_be_saved: None of the %{total} work packages could be updated. + x_out_of_y_could_be_saved: "%{failing} out of the %{total} work packages could not be updated while %{success} could." + selected_because_descendants: While %{selected} work packages were selected, in total %{total} work packages are affected which includes descendants. + descendant: descendant of selected + move: + no_common_statuses_exists: There is no status available for all selected work packages. Their status cannot be changed. + unsupported_for_multiple_projects: Bulk move/copy is not supported for work packages from multiple projects + current_type_not_available_in_target_project: 'The current type of the work package is not enabled in the target project. Please enable the type in the target project if you''d like it to remain unchanged. Otherwise, select an available type in the target project from the list. + + ' + bulk_current_type_not_available_in_target_project: 'The current types of the work packages aren''t enabled in the target project. Please enable the types in the target project if you''d like them to remain unchanged. Otherwise, select an available type in the target project from the list. + + ' + sharing: + missing_workflow_warning: + title: Workflow missing for work package sharing + message: No workflow is configured for the 'Work package editor' role. Without a workflow, the shared with user cannot alter the status of the work package. Workflows can be copied. Select a source type (e.g. 'Task') and source role (e.g. 'Member'). Then select the target types. To start with, you could select all the types as targets. Finally, select the 'Work package editor' role as the target and press 'Copy'. After having thus created the defaults, fine tune the workflows as you do for every other role. + link_message: Configure the workflows in the administration. + summary: + reports: + category: + no_results_title_text: There are currently no categories available. + assigned_to: + no_results_title_text: There are currently no members part of this project. + responsible: + no_results_title_text: There are currently no members part of this project. + author: + no_results_title_text: There are currently no members part of this project. + priority: + no_results_title_text: There are currently no priorities available. + type: + no_results_title_text: There are currently no types available. + version: + no_results_title_text: There are currently no versions available. + work_package_relations_tab: + index: + action_bar_title: Add relations to other work packages to create a link between them. + no_results_title_text: There are currently no relations available. + blankslate_heading: No relations + blankslate_description: This work package does not have any relations yet. + label_add_child_button: Child + label_add_x: Add %{x} + label_edit_x: Edit %{x} + label_add_description: Add description + lag: + subject: Lag + caption: |- + The minimum number of working days to keep in between the two work packages. + It can also be negative. + relations: + label_new_child_created: New work package created and added as a child + label_relates_singular: related to + label_relates_plural: related to + label_relates_to_singular: related to + label_relates_to_plural: related to + relates_description: Creates a visible link between the two work packages with no additional effect + relates_to_description: Creates a visible link between the two work packages with no additional effect + label_precedes_singular: successor (after) + label_precedes_plural: successors (after) + precedes_description: The related work package necessarily needs to start after this one finishes + label_follows_singular: predecessor (before) + label_follows_plural: predecessors (before) + follows_description: The related work package necessarily needs to finish before this one can start + label_child_singular: child + label_child_plural: children + new_child: Create new child + new_child_description: Creates a related work package as a sub-item of the current (parent) work package + child: Child + child_description: Makes the related work package a sub-item of the current (parent) work package + parent: Parent + parent_description: Makes the related work package a parent of the current (child) work package + label_closest: Closest + label_blocks_singular: blocks + label_blocks_plural: blocks + blocks_description: The related work package cannot be closed until this one is closed first + label_blocked_singular: blocked by + label_blocked_plural: blocked by + label_blocked_by_singular: blocked by + label_blocked__by_plural: blocked by + blocked_description: This work package cannot be closed until the related one is closed first + blocked_by_description: This work package cannot be closed until the related one is closed first + label_duplicates_singular: duplicates + label_duplicates_plural: duplicates + duplicates_description: This is a copy of the related work package + label_duplicated_singular: duplicated by + label_duplicated_plural: duplicated by + label_duplicated_by_singular: duplicated by + label_duplicated_by_plural: duplicated by + duplicated_by_description: The related work package is a copy of this + duplicated_description: The related work package is a copy of this + label_includes_singular: includes + label_includes_plural: includes + includes_description: Marks the related work package as including this one with no additional effect + label_partof_singular: part of + label_partof_plural: part of + label_part_of_singular: part of + label_part_of_plural: part of + partof_description: Marks the related work package as being part of this one with no additional effect + part_of_description: Marks the related work package as being part of this one with no additional effect + label_requires_singular: requires + label_requires_plural: requires + requires_description: Marks the related work package as a requirement to this one + label_required_singular: required by + label_required_plural: required by + required_description: Marks this work package as being a requirement to the related one + label_parent_singular: parent + label_parent_plural: parent + label_other_relations: Other relations + ghost_relation_title: Related work package + ghost_relation_description: This is not visible to you due to permissions. + label_invitation: Invitation + account: + delete: Delete account + delete_confirmation: Are you sure you want to delete the account? + deletion_pending: Account has been scheduled for deletion. Note that this process takes place in the background. It might take a few moments until the user is fully deleted. + deletion_info: + data_consequences: + other: All user-specific data will be deleted. The user's activity in shared views such as work packages and meetings will not be deleted but instead be associated with a generic 'Deleted user' that cannot be linked to the original account. + self: All user-specific data will be deleted. Your activity in shared views such as work packages and meetings will not be deleted but instead be associated with a generic 'Deleted user' that cannot be linked to your original account. + heading: Delete %{name}'s account? + login_consequences: + other: This account will immediately be removed from the system and the user will no longer be able to log in with their credentials. + self: Your account will immediately be removed from the system and you will no longer be able to log in using your credentials. + error_inactive_activation_by_mail: 'Your account has not yet been activated. To activate your account, click on the link that was emailed to you. + + ' + error_inactive_manual_activation: 'Your account has not yet been activated. Please wait for an administrator to activate your account. + + ' + error_self_registration_disabled: 'User registration is disabled on this system. Please ask an administrator to create an account for you. + + ' + error_self_registration_limited_provider: 'User registration is limited for the Single sign-on provider ''%{name}''. Please ask an administrator to activate the account for you or change the self registration limit for this provider. + + ' + login_with_auth_provider: or sign in with your existing account + signup_with_auth_provider: or sign up using + auth_source_login_html: Please login as %{login} to activate your account. + omniauth_login: Please login to activate your account. + actionview_instancetag_blank_option: Please select + activemodel: + attributes: + projects/copy_options: + dependencies: Dependencies + activerecord: + attributes: + work_package_semantic_alias: + identifier: Identifier + work_package: Work package + jira_import: + projects: Projects + import/jira: + name: Jira instance name + url: Jira instance URL + personal_access_token: Personal access token + import/jira_open_project_reference: + jira: Jira + jira_import: Jira Migrator + announcements: + show_until: Display until + attachment: + attachment_content: Attachment content + attachment_file_name: Attachment file name + content_type: Content-type + downloads: Downloads + file: File + filename: File + filesize: Size + attribute_help_text: + attribute_name: Attribute + help_text: Help text + caption: Caption + auth_provider: + scim_clients: SCIM clients + calculated_value_error: + error_code: Error code + customized_id: Customized ID + customized_type: Customized type + capability: + context: Context + changeset: + repository: Repository + comment: + commented: Commented + custom_action: + actions: Actions + custom_field: + allow_non_open_versions: Allow non-open versions + default_value: Default value + editable: Editable + field_format: Format + formula: Formula + is_filter: Used as a filter + is_for_all: For all projects + is_required: Required + max_length: Maximum length + min_length: Minimum length + content_right_to_left: Right-to-Left content + multi_value: Allow multi-select + possible_values: Possible values + regexp: Regular expression + searchable: Searchable + admin_only: Admin-only + has_comment: Add a comment text field + custom_value: + value: Value + design_color: + variable: Variable + doorkeeper/application: + uid: Client ID + secret: Client secret + owner: Owner + builtin: Builtin + enabled: Active + redirect_uri: Redirect URI + client_credentials_user_id: Client Credentials User ID + scopes: Scopes + confidential: Confidential + emoji_reaction: + reactable: Reacted on + enterprise_token: + starts_at: Valid since + subscriber: Subscriber + subscription: Subscription + plan: Plan + encoded_token: Enterprise support token + active_user_count_restriction: Active users + enterprise_trial: + company: Company + favorite: + favorited: Item + grids/grid: + page: Page + row_count: Number of rows + column_count: Number of columns + widgets: Widgets + journal: + notes: Notes + cause_type: Cause type + ldap_auth_source: + account: Account + attr_firstname: Firstname attribute + attr_lastname: Lastname attribute + attr_login: Username attribute + attr_mail: Email attribute + filter_string: Filter string + admin: Administrator + base_dn: Base DN + host: Host + onthefly: Automatic user creation + port: Port + tls_certificate_string: LDAP server SSL certificate + mcp_configuration: + enabled: Enabled + title: Title + description: Description + member: + roles: Roles + notification: + read_ian: Read in-app + resource: Resource + oauth_client: + client: Client ID + project: + active_value: + true: unarchived + false: archived + attribute_groups: Attribute Groups + description: Description + enabled_modules: Enabled modules + identifier: Identifier + latest_activity_at: Latest activity at + parent: Subproject of + project_creation_wizard_enabled: Project initiation request + public_value: + title: Visibility + true: public + false: private + queries: Queries + status_code: Status + status_explanation: Status description + status_codes: + not_started: Not started + on_track: On track + at_risk: At risk + off_track: Off track + finished: Finished + discontinued: Discontinued + project_creation_wizard_assignee_custom_field: Assignee when submitted + project_creation_wizard_notification_text: Notification text + project_creation_wizard_send_confirmation_email: Confirmation email + project_creation_wizard_status_when_submitted: Status when submitted + project_creation_wizard_work_package_comment: Work package comment + project_creation_wizard_work_package_type: Work package type + template: Template + templated: Template project + templated_value: + true: marked as template + false: unmarked as template + types: Types + versions: Versions + work_packages: Work Packages + workspace_type: Workspace type + project_custom_field: + custom_field_section: Section + subproject_template_assignment: + workspace_type: Workspace type + project/phase: + date_range: Date range + definition: Definition + duration: Duration + start_date: Start date + start_date_caption: Follows the previous phase. + finish_date: Finish date + project/phase_definition: + name: Name + color: Color + start_gate: Start phase gate + start_gate_name: Start phase gate name + finish_gate: Finish phase gate + finish_gate_name: Finish phase gate name + query: + sums: Sums + columns: Columns + column_names: Columns + relations_to_type_column: Relations to %{type} + relations_of_type_column: "%{type} relations" + child_work_packages: Child work packages + group_by: Group results by + sort_by: Sort results by + filters: Filters + timeline_labels: Timeline labels + timeline_visible: Show Gantt chart + timeline_zoom_level: Gantt chart zoom level + timestamps: Baseline timestamps + sort_criteria: Sort criteria + highlighted_attributes: Highlighted attributes + highlighting_mode: Highlight mode + display_representation: Display mode + show_hierarchies: Display mode + starred: Favorite + hidden: Hidden + manual_sorting: Manual sort order + ordered_work_packages: Work packages order + include_subprojects: Include subprojects + results: Results + relation: + lag: Lag + from: Related work package + to: Related work package + relation_type: Relation type + reminder: + remindable: Reminded object + remind_at: Remind at + remind_at_date: Date + remind_at_time: Time + reminder_notification: + notification: Notification + repository: + url: URL + role: + permissions: Permissions + scim_client: + authentication_method: Authentication method + jwt_sub: Subject claim + status: + is_closed: Work package closed + is_readonly: Work package read-only + excluded_from_totals: Exclude from calculation of totals in hierarchy + default_done_ratio: "% Complete" + token/named: + token_name: Token name + token/ical: + calendar: Calendar + ical_token_query_assignment: Query assignment + time_entry: + activity: Activity + hours: Hours + spent_on: Date + type: Type + ongoing: Ongoing + type: + description: Default text for description + attribute_groups: Form configuration + is_in_roadmap: Displayed in roadmap by default + is_default: Activated for new projects by default + is_milestone: Is milestone + color: Color + patterns: Patterns + remote_identity: + auth_source: Auth Source + integration: Integration + user: User + user: + admin: Administrator + auth_source: Authentication source + ldap_auth_source: LDAP connection + identity_url: Identity URL + current_password: Current password + force_password_change: Enforce password change on next login + language: Language + last_login_on: Last login + failed_login_count: Failed login attempts + first_name: First name + last_name: Last name + first_login: First login + new_password: New password + password_confirmation: Confirmation + consented_at: Consented at + group: + identity_url: Identity URL + parent: Parent group + organizational_unit: Organizational unit + group_users: Group users + group_detail: + parent: Parent group + organizational_unit: Organizational unit + user_preference: + header_look_and_feel: Look and feel + header_alerts: Alerts + button_update_look_and_feel: Update look and feel + button_update_alerts: Update alerts + button_update_user_information: Update profile + comments_sorting: Display work package activity sorted by + disable_keyboard_shortcuts: Disable keyboard shortcuts + dismissed_enterprise_banners: Hidden enterprise banners + impaired: Accessibility mode + auto_hide_popups: Automatically hide success banners + auto_hide_popups_caption: When enabled, the green success banners will automatically disappear after 5 seconds. + warn_on_leaving_unsaved: Warn me when leaving a work package with unsaved changes + increase_theme_contrast: Increase theme contrast + increase_contrast: Increase contrast + increase_contrast_caption: Enables high-contrast mode for the chosen colour mode. + force_light_theme_contrast: Force high-contrast when in Light mode + force_dark_theme_contrast: Force high-contrast when in Dark mode + force_light_theme_contrast_caption: Uses the high-contrast version of Light mode when automatic color mode is selected. + force_dark_theme_contrast_caption: Uses the high-contrast version of Dark mode when automatic color mode is selected. + theme: Color mode + time_zone: Time zone + mode_guideline: Some modes will overwrite custom theme colors for accessibility and legibility. Please select Light mode for full custom theme support. + daily_reminders: Daily reminders + workdays: Working days + users/invitation/form_model: + principal_type: Invitation type + id_or_email: Name or email address + user_non_working_time: + start_date: Start date + end_date: End date + user_working_hours: + valid_from: Valid from + monday: Monday + monday_hours: Monday hours + tuesday: Tuesday + tuesday_hours: Tuesday hours + wednesday: Wednesday + wednesday_hours: Wednesday hours + thursday: Thursday + thursday_hours: Thursday hours + friday: Friday + friday_hours: Friday hours + saturday: Saturday + saturday_hours: Saturday hours + sunday: Sunday + sunday_hours: Sunday hours + availability_factor: Availability factor + shared_hours: Work hours + days: Working days + version: + effective_date: Finish date + sharing: Sharing + wiki_content: + text: Text + wiki_page: + parent_title: Parent page + redirect_existing_links: Redirect existing links + text: Page content + work_package: + ancestor: Descendants of + begin_insertion: Begin of the insertion + begin_deletion: Begin of the deletion + children: Subelements + derived_done_ratio: Total % complete + derived_remaining_hours: Total remaining work + derived_remaining_time: Total remaining work + done_ratio: "% Complete" + duration: Duration + end_insertion: End of the insertion + end_deletion: End of the deletion + identifier: Identifier + ignore_non_working_days: Ignore non working days + include_non_working_days: + title: Working days + false: working days only + true: include non-working days + journal_internal: Internal Journal + notify: Notify + parent: Parent + parent_issue: Parent + parent_work_package: Parent + priority: Priority + progress: "% Complete" + readonly: Read only + remaining_hours: Remaining work + remaining_time: Remaining work + sequence_number: Sequence number + shared_with_users: Shared with + schedule_manually: Manual scheduling + spent_hours: Spent time + spent_time: Spent time + subproject: Subproject + time_entries: Log time + type: Type + version: Version + watcher: Watcher + ordered_persisted_query_entity: + persisted_query: Persisted query + entity: Entity + position: Position + persisted_query: + name: Name + views: Views + filters: Filters + orders: Orders + selects: Selects + persisted_view: + name: Name + query: Query + parent: Parent view + public: Public + user_card_view: + secondary_info: Secondary info + show_status_badge: Show status badge + show_email: Show email + tag_source: Tag source + tag_limit: Tag limit + card_size: Card size + columns_per_row: Columns per row + errors: + messages: + accepted: must be accepted. + after: must be after %{date}. + after_today: must be in the future. + after_or_equal_to: must be after or equal to %{date}. + before: must be before %{date}. + before_or_equal_to: must be before or equal to %{date}. + blank: can't be blank. + not_before_start_date: must not be before the start date. + overlapping_range: overlaps with an existing non-working day range. + blank_nested: needs to have the property '%{property}' set. + cannot_delete_mapping: is required. Cannot be deleted. + is_for_all_cannot_modify: is for all projects and can therefore not be modified. + cant_link_a_work_package_with_a_descendant: A work package cannot be linked to one of its subtasks. + circular_dependency: This relation would create a circular dependency. + confirmation: doesn't match %{attribute}. + could_not_be_copied: "%{dependency} could not be (fully) copied." + does_not_exist: does not exist. + user_already_in_department: User %{user_id} is already a member of department %{department_id}. + error_enterprise_only: "%{action} is only available in the OpenProject Enterprise edition." + error_unauthorized: may not be accessed. + error_readonly: was attempted to be written but is not writable. + error_conflict: Information has been updated by at least one other user in the meantime. + error_not_found: not found. + email: is not a valid email address. + empty: can't be empty. + enterprise_plan_required: requires at least the %{plan_name}. + even: must be even. + exclusion: is reserved. + feature_disabled: is not available. + feature_disabled_for_project: is disabled for this project. + file_too_large: is too large (maximum size is %{count} Bytes). + filter_does_not_exist: filter does not exist. + format: does not match the expected format '%{expected}'. + format_nested: does not match the expected format '%{expected}' at path '%{path}'. + greater_than: must be greater than %{count}. + greater_than_or_equal_to: must be greater than or equal to %{count}. + greater_than_or_equal_to_start_date: must be greater than or equal to the start date. + greater_than_start_date: must be greater than the start date. + inclusion: is not set to one of the allowed values. + inclusion_nested: is not set to one of the allowed values at path '%{path}'. + invalid: is invalid. + invalid_uri: must be a valid URI. + invalid_url: is not a valid URL. + invalid_url_scheme: 'is not a supported protocol (allowed: %{allowed_schemes}).' + less_than_or_equal_to: must be less than or equal to %{count}. + not_available: is not available due to a system configuration. + not_deletable: cannot be deleted. + not_editable: cannot be edited because it is already in effect. + not_current_user: is not the current user. + system_wide_non_working_day_exists: conflicts with an existing system-wide non-working day for this date. + not_found: not found. + not_a_date: is not a valid date. + not_a_datetime: is not a valid date time. + not_a_number: is not a number. + not_allowed: is invalid because of missing permissions. + host_not_allowed: is not an allowed host. + not_json: is not parseable as JSON. + not_json_object: is not a JSON object. + not_an_integer: is not an integer. + not_an_iso_date: 'is not a valid date. Required format: YYYY-MM-DD.' + not_same_project: doesn't belong to the same project. + datetime_must_be_in_future: must be in the future. + odd: must be odd. + regex_match_failed: does not match the regular expression %{expression}. + regex_invalid: could not be validated with the associated regular expression. + regex_list_invalid: Lines %{invalid_lines} could not be parsed as regular expression. + hexcode_invalid: is not a valid 6-digit hexadecimal color code. + smaller_than_or_equal_to_max_length: must be smaller than or equal to maximum length. + taken: has already been taken. + too_long: is too long (maximum is %{count} characters). + too_short: is too short (minimum is %{count} characters). + type_mismatch: is not of type '%{type}' + type_mismatch_nested: is not of type '%{type}' at path '%{path}' + unchangeable: cannot be changed. + unknown_property: is not a known property. + unknown_property_nested: has the unknown path '%{path}'. + unremovable: cannot be removed. + url_not_secure_context: 'is not providing a "Secure Context". Either use HTTPS or a loopback address, such as localhost. + + ' + wrong_length: is the wrong length (should be %{count} characters). + models: + group: + attributes: + parent_id: + circular_dependency: would create a circular group hierarchy. + organizational_unit_mismatch: must have the same organizational unit setting as the group. + ldap_auth_source: + attributes: + tls_certificate_string: + invalid_certificate: 'The provided SSL certificate is invalid: %{additional_message}' + format: "%{message}" + attachment: + attributes: + content_type: + blank: The content type of the file cannot be blank. + not_allowlisted: The file was rejected by an automatic filter. '%{value}' is not allowed for upload. + format: "%{message}" + capability: + context: + global: Global + query: + filters: + minimum: need to include at least one filter for principal, context or id with the '=' operator. + custom_field: + at_least_one_custom_option: At least one option needs to be available. + previous_custom_field_recalculation_unprocessed: The recalculation of previous changes for this custom field have not been applied yet, please try again in a few minutes. + referenced_in_other_fields_html: + one: "%{name} is used in project attribute calculation %{links}." + other: "%{name} is used in project attribute calculations: %{links}." + attributes: + formula: + blank: Formula can't be blank. + invalid: Formula is invalid. + invalid_characters: Only numeric values, mathematical operators and project attributes of type integer, float, calculated value and weighted list are allowed. + not_allowed_custom_fields_referenced: The attribute %{custom_fields} cannot be used because it leads to a circular reference; one attribute depends on the other. + format: "%{message}" + required: + cannot_be_true: cannot be set to true. + custom_fields_project: + attributes: + project_ids: + blank: Please select a project. + custom_actions: + only_one_allowed: "(%{name}) only one value is allowed." + empty: "(%{name}) value can't be empty." + inclusion: "(%{name}) value is not set to one of the allowed values." + not_logged_in: "(%{name}) value cannot be set because you are not logged in." + not_an_integer: "(%{name}) is not an integer." + smaller_than_or_equal_to: "(%{name}) must be smaller than or equal to %{count}." + greater_than_or_equal_to: "(%{name}) must be greater than or equal to %{count}." + format: "%{message}" + doorkeeper/application: + attributes: + redirect_uri: + fragment_present: cannot contain a fragment. + invalid_uri: must be a valid URI. + relative_uri: must be an absolute URI. + secured_uri: is not providing a "Secure Context". Either use HTTPS or a loopback address, such as localhost. + forbidden_uri: is forbidden by the server. + scopes: + not_match_configured: doesn't match available scopes. + enterprise_trial: + already_used: was already used to create a trial. + failed_to_create: Trial could not be created (%{status}) + general_consent: Please accept the terms and conditions. + enterprise_token: + only_one_trial: Only one trial token can be active. Please delete the previous trial token before adding another. + unreadable: can't be read. Are you sure it is a support token? + already_added: This token has already been added. + favorite: + already_favorited: has already been favorited. + grids/grid: + overlaps: overlap. + outside: is outside of the grid. + end_before_start: end value needs to be larger than the start value. + ical_token_query_assignment: + attributes: + name: + blank: is mandatory. Please select a name. + not_unique: is already in use. Please select another name. + meeting: + error_conflict: Unable to save because the meeting was updated by someone else in the meantime. Please reload the page. + message: + cannot_move_message_to_forum_of_different_project: A message cannot be moved to a forum of a different project. + notifications: + at_least_one_channel: At least one channel for sending notifications needs to be specified. + attributes: + read_ian: + read_on_creation: cannot be set to true on notification creation. + mail_reminder_sent: + set_on_creation: cannot be set to true on notification creation. + reason: + no_notification_reason: cannot be blank as IAN is chosen as a channel. + reason_mail_digest: + no_notification_reason: cannot be blank as mail digest is chosen as a channel. + non_working_day: + attributes: + date: + taken: A non-working day already exists for %{value}. + format: "%{message}" + parse_schema_filter_params_service: + attributes: + base: + unsupported_operator: The operator is not supported. + invalid_values: A value is invalid. + id_filter_required: An 'id' filter is required. + project: + archived_ancestor: The project has an archived ancestor. + foreign_wps_reference_version: Work packages in non descendant projects reference versions of the project or its descendants. + cannot_be_assigned_to_artifact_work_package: The chosen user is not allowed to be assigned to work packages. + attributes: + base: + archive_permission_missing_on_subprojects: You do not have the permissions required to archive all sub-projects. Please contact an administrator. + project_initiation_request_disabled: Project initiation request is disabled. It must be enabled to create the artifact work package. + types: + in_use_by_work_packages: 'still in use by work packages: %{types}' + identifier: + must_start_with_letter: must start with a letter + no_special_characters: may only contain uppercase letters, numbers, and underscores + enabled_modules: + dependency_missing: The module '%{dependency}' needs to be enabled as well since the module '%{module}' depends on it. + format: "%{message}" + project_custom_field_project_mapping: + attributes: + project_ids: + blank: Please select a project. + project/phase: + attributes: + start_date: + must_be_before_finish_date: must be before the finish date. + non_continuous_dates: can't be earlier than the previous phase's end date. + finish_date: + must_be_after_start_date: must be after the start date. + cannot_be_a_non_working_day: can't be a non-working day. + query: + attributes: + public: + error_unauthorized: "- The user has no permission to create public views." + group_by: + invalid: 'Can''t group by: %{value}' + format: "%{message}" + column_names: + invalid: 'Invalid query column: %{value}' + format: "%{message}" + sort_criteria: + invalid: 'Can''t sort by column: %{value}' + format: "%{message}" + timestamps: + invalid: 'Timestamps contain invalid values: %{values}' + forbidden: 'Timestamps contain forbidden values: %{values}' + format: "%{message}" + selects: + name_not_included: The 'Name' column needs to be included + nonexistent: The column '%{column}' does not exist. + format: "%{message}" + group_by_hierarchies_exclusive: is mutually exclusive with group by '%{group_by}'. You cannot activate both. + can_only_be_modified_by_owner: The query can only be modified by its owner. + need_permission_to_modify_public_query: You cannot modify a public query. + filters: + custom_fields: + inexistent: There is no custom field for the filter. + queries/filters/base: + attributes: + values: + inclusion: filter has invalid values. + format: "%{message}" + queries/principals/filters/internal_mentionable_on_work_package_filter: + attributes: + values: + single_value_requirement: must be a single work package + relation: + typed_dag: + circular_dependency: The relationship creates a circle of relationships. + attributes: + base: + error_not_deletable: This relation cannot be deleted because you do not have edit permissions for the selected work package. + error_not_editable: This relation cannot be edited because you do not have edit permissions for the selected work package. + to_id: + format: The selected work package %{message} + error_not_found: could not be found. + error_readonly: cannot be changed for existing relations. + error_not_manageable: cannot be added because you do not have edit permissions for the selected work package. + from_id: + format: The selected work package %{message} + error_not_found: could not be found. + error_readonly: cannot be changed for existing relations. + error_not_manageable: cannot be added because you do not have edit permissions for the selected work package. + repository: + not_available: SCM vendor is not available + not_whitelisted: is not allowed by the configuration. + invalid_url: is not a valid repository URL or path. + must_not_be_ssh: must not be an SSH url. + must_not_point_to_openproject_directory: must not point to an OpenProject-managed repository directory. + no_directory: is not a directory. + role: + attributes: + permissions: + dependency_missing: need to also include '%{dependency}' as '%{permission}' is selected. + setting: + attributes: + base: + working_days_are_missing: At least one day of the week must be defined as a working day. + previous_working_day_changes_unprocessed: The previous changes to the working days configuration have not been applied yet. + hours_per_day_are_missing: The number of hours per day must be defined. + durations_are_not_positive_numbers: The durations must be positive numbers. + hours_per_day_is_out_of_bounds: Hours per day can't be more than 24 + status: + attributes: + default_done_ratio: + inclusion: must be between 0 and 100. + readonly_default_exlusive: can not be activated for statuses that are marked default. + time_entry: + attributes: + hours: + day_limit: is too high as a maximum of 24 hours can be logged per date. + user_preference: + attributes: + pause_reminders: + invalid_range: can only be a valid date range. + daily_reminders: + full_hour: can only be configured to be delivered at a full hour. + notification_settings: + only_one_global_setting: There must only be one global notification setting. + email_alerts_global: The email notification settings can only be set globally. + format: "%{message}" + wrong_date: Wrong value for Start date, Due date, or Overdue. + watcher: + attributes: + user_id: + not_allowed_to_view: is not allowed to view this resource. + locked: is locked. + wiki_page: + error_conflict: The wiki page has been updated by someone else while you were editing it. + attributes: + slug: + undeducible: cannot be deduced from the title '%{title}'. + work_package: + is_not_a_valid_target_for_time_entries: 'Work package #%{id} is not a valid target for reassigning the time entries.' + attributes: + id: + format: "%{message}" + cannot_add_child_because_of_lack_of_permission: Cannot add child because you don't have permissions to edit the selected work package. + blank: ID can't be blank. + identifier: + semantic_identifier_incomplete: and sequence_number must both be set at the same time. + assigned_to: + format: "%{message}" + done_ratio: + does_not_match_work_and_remaining_work: does not match Work and Remaining work + cannot_be_set_when_work_is_zero: cannot be set when Work is 0h + must_be_set_when_remaining_work_is_set: required when Remaining work is set. + must_be_set_when_work_and_remaining_work_are_set: required when Work and Remaining work are set. + inclusion: must be between 0 and 100. + due_date: + not_start_date: is not on start date, although this is required for milestones. + cannot_be_null: can not be set to null as start date and duration are known. + duration: + larger_than_dates: is larger than the interval between the start and the finish date. + smaller_than_dates: is smaller than the interval between the start and the finish date. + not_available_for_milestones: is not available for milestone typed work packages. + cannot_be_null: can not be set to null as start date and finish date are known. + not_an_integer: is not a valid duration. + parent: + cannot_be_milestone: cannot be a milestone. + cannot_be_self_assigned: cannot be assigned to itself. + cannot_be_in_another_project: cannot be in another project. + not_a_valid_parent: is invalid. + schedule_manually: + cannot_be_automatically_scheduled: cannot be set to false (automatically scheduled) as it has no predecessors or children. + start_date: + violates_relationships: can only be set to %{soonest_start} or later so as not to violate the work package's relationships. + cannot_be_null: can not be set to null as finish date and duration are known. + status_id: + status_transition_invalid: is invalid because no valid transition exists from old to new status for the current user's roles. + status_invalid_in_type: is invalid because the current status does not exist in this type. + type: + cannot_be_milestone_due_to_children: cannot be a milestone because this work package has children. + priority_id: + only_active_priorities_allowed: needs to be active. + category: + only_same_project_categories_allowed: The category of a work package must be within the same project as the work package. + does_not_exist: The specified category does not exist. + estimated_hours: + not_a_number: is not a valid duration. + cant_be_inferior_to_remaining_work: cannot be lower than Remaining work. + must_be_set_when_remaining_work_and_percent_complete_are_set: required when Remaining work and % Complete are set. + remaining_hours: + not_a_number: is not a valid duration. + cant_exceed_work: cannot be higher than Work. + must_be_set_when_work_is_set: required when Work is set. + must_be_set_when_work_and_percent_complete_are_set: required when Work and % Complete are set. + must_be_set_to_zero_hours_when_work_is_set_and_percent_complete_is_100p: ">- must be 0h when Work is set and % Complete is 100%." + must_be_empty_when_work_is_empty_and_percent_complete_is_100p: must be empty when Work is empty and % Complete is 100%. + readonly_status: The work package is in a readonly status so its attributes cannot be changed. + type: + attributes: + attribute_groups: + attribute_unknown: Invalid work package attribute used. + attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' + duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. + query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' + group_without_name: Group name can't be blank. + patterns: + invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. + user: + attributes: + base: + user_limit_reached: User limit reached. No more accounts can be created on the current plan. + one_must_be_active: Admin User cannot be locked/removed. At least one admin must be active. + password_confirmation: + confirmation: Password confirmation does not match password. + format: "%{message}" + password: + requirements_not_met: 'Must include characters of the following types: %{rules}' + lowercase: lowercase (e.g. 'a') + uppercase: uppercase (e.g. 'A') + numeric: numeric (e.g. '1') + special: special (e.g. '%') + reused: + one: has been used before. Please choose one that is different from your last one. + other: has been used before. Please choose one that is different from your last %{count}. + match: + confirm: Confirm new password. + description: "'Password confirmation' should match the input in the 'New password' field." + status: + invalid_on_create: is not a valid status for new users. + user_working_hours: + attributes: + days: + no_working_day: At least one day needs to be configured as a working day. + member: + principal_blank: Please choose at least one user or group. + role_blank: need to be assigned. + attributes: + roles: + ungrantable: has an unassignable role. + more_than_one: has more than one role. + principal: + unassignable: cannot be assigned to a project. + version: + undeletable_archived_projects: The version cannot be deleted as it has work packages attached to it. + undeletable_work_packages_attached: The version cannot be deleted as it has work packages attached to it. + token/named: + attributes: + token_name: + blank: Please provide a token name + in_use: This token name is already in use, please select a different one + format: "%{message}" + jira: + invalid_protocol: Please provide a valid protocol (http or https) + template: + body: 'Please check the following fields:' + header: + one: 1 error prohibited this %{model} from being saved + other: "%{count} errors prohibited this %{model} from being saved" + models: + attachment: File + attribute_help_text: + one: Attribute help text + other: Attribute help texts + auth_provider: + one: Authentication provider + other: Authentication providers + category: Category + color: Color + comment: Comment + custom_action: Custom action + custom_field: Custom field + customized: Customized + doorkeeper/application: OAuth application + enterprise_token: + one: Enterprise token + other: Enterprise tokens + forum: Forum + global_role: Global role + group: Group + issue_priority: + one: Priority + other: Priorities + meeting_participant: Meeting participant + member: Member + news: News + notification: + one: Notification + other: Notifications + placeholder_user: Placeholder user + project: + one: Project + other: Projects + project_query: + one: Project list + other: Project lists + query: Custom query + reminder: Reminder + role: + one: Role + other: Roles + scim_client: + one: SCIM client + other: SCIM clients + jira: + one: Jira + other: Jira + status: Work package status + token/api: + one: Access token + other: Access tokens + token/rss: + one: RSS token + other: RSS tokens + type: + one: Type + other: Types + user: User + version: Version + workflow: Workflow + work_package: Work package + wiki: Wiki + wiki_page: Wiki page + errors: + header_invalid_fields: + one: 'There was a problem with the following field:' + other: 'There were problems with the following fields:' + header_additional_invalid_fields: + one: 'Additionally, there was a problem with the following field:' + other: 'Additionally, there were problems with the following fields:' + field_erroneous_label: |- + This field is invalid: %{full_errors} + Please enter a valid value. + messages: + must_be_template: must be template + unsupported_storage_type: is not a supported storage type. + storage_error: There was an error with the storage connection. + invalid_input: The input is invalid. + invalid_child_for_parent: is not allowed as a parent for this view type. + activity: + item: + created_by_on: created by %{user} on %{datetime} + created_by_on_time_entry: time logged by %{user} on %{datetime} + created_on: created on %{datetime} + created_on_time_entry: time logged on %{datetime} + updated_by_on: updated by %{user} on %{datetime} + updated_by_on_time_entry: logged time updated by %{user} on %{datetime} + updated_on: updated on %{datetime} + updated_on_time_entry: logged time updated on %{datetime} + deleted_on: deleted on %{datetime} + deleted_by_on: deleted by %{user} on %{datetime} + added_on: added on %{datetime} + added_by_on: added by %{user} on %{datetime} + removed_on: removed on %{datetime} + removed_by_on: removed by %{user} on %{datetime} + parent_without_of: Subproject + parent_no_longer: No longer subproject of + time_entry: + hour: + one: "%{count} hour" + other: "%{count} hours" + hour_html: + one: "%{count} hour" + other: "%{count} hours" + updated: changed from %{old_value} to %{value} + logged_for: Logged for + filter: + changeset: Changesets + message: Forums + news: News + project_details: Project details + subproject: Include subprojects + time_entry: Spent time + wiki_edit: Wiki + work_package: Work packages + project_phase: + activated: activated + added_date: set to %{date} + changed_date: changed from %{from} to %{to} + deactivated: deactivated + deleted_project_phase: Deleted project phase + phase_and_both_gates: "%{phase_message}. %{start_gate_message}, and %{finish_gate_message}" + phase_and_one_gate: "%{phase_message}. %{gate_message}" + removed_date: date deleted %{date} + attributes: + active: Active + assigned_to: Assignee + assignee: Assignee + attachments: Attachments + actor: Actor + action: Action + api_key: API key + author: Author + avatar: Avatar + base: 'General Error:' + body: Body + category: Category + comment: Comment + comments: Comment + content: Content + color: Color + creator: Creator + created_at: Created on + custom_field: Custom field + custom_options: Possible values + custom_values: Custom fields + date: Date + dates_interval: Date range + default_columns: Default columns + description: Description + derived_due_date: Derived finish date + derived_estimated_hours: Total work + derived_start_date: Derived start date + direction: Direction + display_sums: Display Sums + domain: Domain + due_date: Finish date + estimated_hours: Work + estimated_time: Work + email: Email + entity_type: Entity + expires_at: Expires on + firstname: First name + filter: Filter + group: Group + groups: Groups + hexcode: Hex code + id: ID + is_default: Default value + is_for_all: For all projects + public: Public + principal: User or group + issue: Work package + journal: Journal + journal_notes: Comment + lastname: Last name + login: Username + lock_version: Lock version + mail: Email + name: Name + note: Note + notes: Notes + number: Number + options: Options + operator: Operator + password: Password + priority: Priority + project: Project + project_ids: Project IDs + project_phase: Project phase + project_phase_definition: Project phase + reason: Reason + responsible: Accountable + required: Required + recipient: Recipient + role: Role + roles: Roles + search: Search + sprint: Sprint + start_date: Start date + status: Status + state: State + subject: Subject + slug: Slug + summary: Summary + template: Template + time_zone: Time zone + text: Text + title: Title + type: Type + typeahead: Autocomplete + uid: Unique identifier + updated_at: Updated on + updated_on: Updated on + uploader: Uploader + user: User + username: Username + unit: Unit + value: Value + values: Values + version: Version + visible: Visible + work_package: Work package + work_package_id: Work package + backup: + failed: Backup failed + label_backup_token: Backup token + label_create_token: Create backup token + label_delete_token: Delete backup token + label_reset_token: Reset backup token + label_token_users: The following users have active backup tokens + reset_token: + action_create: Create + action_reset: Reset + heading_reset: Reset backup token + heading_create: Create backup token + implications: 'Enabling backups will allow any user with the required permissions and this backup token to download a backup containing all data of this OpenProject installation. This includes the data of all other users. + + ' + info: 'You will need to generate a backup token to be able to create a backup. Each time you want to request a backup you will have to provide this token. You can delete the backup token to disable backups for this user. + + ' + verification_html: 'Enter %{word} to confirm you want to %{action} the backup token. + + ' + verification_word_reset: reset + verification_word_create: create + warning: 'When you create a new token you will only be allowed to request a backup after 24 hours. This is a safety measure. After that you can request a backup any time using that token. + + ' + text_token_deleted: Backup token deleted. Backups are now disabled. + error: + invalid_token: Invalid or missing backup token + token_cooldown: The backup token will be valid in %{hours} hours. + backup_pending: There is already a backup pending. + limit_reached: You can only do %{limit} backups per day. + button_actions: Actions + button_add: Add + button_add_comment: Add comment + button_add_item_above: Add item above + button_add_item_below: Add item below + button_add_sub_item: Add sub-item + button_add_member: Add member + button_add_watcher: Add watcher + button_annotate: Annotate + button_apply: Apply + button_apply_changes: Apply changes + button_archive: Archive + button_back: Back + button_cancel: Cancel + button_change: Change + button_change_parent_page: Change parent page + button_change_password: Change password + button_check_all: Check all + button_clear: Clear + button_click_to_reveal: Click to reveal + button_close: Close + button_collapse_all: Collapse all + button_confirm: Confirm + button_configure: Configure + button_continue: Continue + button_complete: Complete + button_copy: Copy + button_copy_to_clipboard: Copy to clipboard + button_copy_link_to_clipboard: Copy link to clipboard + button_create: Create + button_create_and_continue: Create and continue + button_decline: Decline + button_delete: Delete + button_delete_permanently: Delete permanently + button_delete_watcher: Delete watcher %{name} + button_download: Download + button_disable: Disable + button_duplicate: Duplicate + button_duplicate_and_follow: Duplicate and follow + button_edit: Edit + button_enable: Enable + button_edit_associated_wikipage: 'Edit associated wiki page: %{page_title}' + button_expand_all: Expand all + button_favorite: Add to favorites + button_filter: Filter + button_finish_setup: Finish setup + button_generate: Generate + button_list: List + button_lock: Lock + button_login: Sign in + button_move: Move + button_move_and_follow: Move and follow + button_print: Print + button_quote: Quote + button_remove: Remove + button_remove_permanently: Remove permanently + button_remove_reminder: Remove reminder + button_rename: Rename + button_replace: Replace + button_revoke: Revoke + button_reply: Reply + button_reset: Reset + button_rollback: Rollback to this version + button_save: Save + button_save_as: Save as + button_save_back: Save and back + button_select: Select + button_set_reminder: Set reminder + button_show: Show + button_sort: Sort + button_submit: Submit + button_test: Test + button_unarchive: Unarchive + button_uncheck_all: Uncheck all + button_unlock: Unlock + button_unfavorite: Remove from favorites + button_unwatch: Unwatch + button_update: Update + button_upgrade: Upgrade + button_buy_now: Buy now + button_upload: Upload + button_view: View + button_watch: Watch + button_manage_menu_entry: Configure menu item + button_add_menu_entry: Add menu item + button_configure_menu_entry: Configure menu item + button_delete_menu_entry: Delete menu item + button_view_shared_work_packages: View shared work packages + button_manage_roles: Manage roles + button_remove_member: Remove member + button_remove_member_and_shares: Remove member and shares + button_revoke_work_package_shares: Revoke work package shares + button_revoke_access: Revoke access + button_revoke_all: Revoke all + button_revoke_only: Revoke only %{shared_role_name} + button_publish: Make public + button_unpublish: Make private + consent: + checkbox_label: I have noted and do consent to the above. + failure_message: Consent failed, cannot proceed. + title: User Consent + decline_warning_message: You have declined to consent and have been logged out. + user_has_consented: The user gave their consent to your [configured consent information text](consent_settings). + not_yet_consented: The user has not yet given their consent to your [configured consent information text](consent_settings). They will be reminded the next time they log in. + contact_mail_instructions: Define the mail address that users can reach a data controller to perform data change or removal requests. + contact_your_administrator: Please contact your administrator if you want to have your account deleted. + contact_this_mail_address: Please contact %{mail_address} if you want to have your account deleted. + text_update_consent_time: Check this box to force users to consent again. Enable when you have changed the legal aspect of the consent information above. + update_consent_last_time: 'Last update of consent: %{update_time}' + copy_project: + title: Copy project "%{source_project_name}" + started: Started to copy project "%{source_project_name}" to "%{target_project_name}". You will be informed by mail as soon as "%{target_project_name}" is available. + failed: Cannot copy project %{source_project_name} + failed_internal: Copying failed due to an internal error. + succeeded: Created project %{target_project_name} + errors: Error + project_custom_fields: Custom fields on project + x_objects_of_this_type: + zero: No objects of this type + one: One object of this type + other: "%{count} objects of this type" + text: + failed: Could not copy project "%{source_project_name}" to project "%{target_project_name}". + succeeded: Copied project "%{source_project_name}" to "%{target_project_name}". + source_project_label: Project copied + copy_options: + dependencies_label: Copy from project + create_project: + attributes_heading: Fill in this mandatory information to work on your projects. + template_label: Use template + template_heading: Select a project template to work with the most common project management methods, or create a project from scratch. + copy_options: + dependencies_label: Copy from template + blank_template: + label: Blank project + description: Start from scratch. Manually add project attributes, members and modules. + blank_description: No description provided. + create_portfolio: + template_heading: Select a portfolio template to work with the most common project management methods, or create a portfolio from scratch. + blank_template: + label: Blank portfolio + description: Start from scratch. Manually add portfolio attributes, members and modules. + create_program: + template_heading: Select a program template to work with the most common project management methods, or create a program from scratch. + blank_template: + label: Blank program + description: Start from scratch. Manually add program attributes, members and modules. + create_wiki_page: Create new wiki page + create_wiki_page_button: Wiki page + date: + abbr_day_names: + - Sun + - Mon + - Tue + - Wed + - Thu + - Fri + - Sat + abbr_month_names: + - + - Jan + - Feb + - Mar + - Apr + - May + - Jun + - Jul + - Aug + - Sep + - Oct + - Nov + - Dec + abbr_week: Wk + day_names: + - Sunday + - Monday + - Tuesday + - Wednesday + - Thursday + - Friday + - Saturday + formats: + default: "%m/%d/%Y" + long: "%B %d, %Y" + short: "%b %d" + month_names: + - + - January + - February + - March + - April + - May + - June + - July + - August + - September + - October + - November + - December + order: + - :year + - :month + - :day + datetime: + distance_in_words: + about_x_hours: + one: about 1 hour + other: about %{count} hours + about_x_months: + one: about 1 month + other: about %{count} months + about_x_years: + one: about 1 year + other: about %{count} years + almost_x_years: + one: almost 1 year + other: almost %{count} years + half_a_minute: half a minute + less_than_x_minutes: + one: less than a minute + other: less than %{count} minutes + less_than_x_seconds: + one: less than 1 second + other: less than %{count} seconds + over_x_years: + one: over 1 year + other: over %{count} years + x_days: + one: 1 day + other: "%{count} days" + x_minutes: + one: 1 minute + other: "%{count} minutes" + x_minutes_abbreviated: + one: 1 min + other: "%{count} mins" + x_hours: + one: 1 hour + other: "%{count} hours" + x_hours_abbreviated: + one: 1 hr + other: "%{count} hrs" + x_weeks: + one: 1 week + other: "%{count} weeks" + x_months: + one: 1 month + other: "%{count} months" + x_years: + one: 1 year + other: "%{count} years" + x_seconds: + one: 1 second + other: "%{count} seconds" + x_seconds_abbreviated: + one: 1 s + other: "%{count} s" + units: + minute_abbreviated: + one: min + other: mins + hour: + one: hour + other: hours + day: + one: day + other: days + description_active: Active? + description_attachment_toggle: Show/Hide attachments + description_autocomplete: 'This field uses autocomplete. While typing the title of a work package you will receive a list of possible candidates. Choose one using the arrow up and arrow down key and select it with tab or enter. Alternatively you can enter the work package number directly. + + ' + description_available_columns: Available Columns + description_choose_project: Projects + description_compare_from: Compare from + description_compare_to: Compare to + description_current_position: 'You are here: ' + description_date_from: Enter start date + description_date_to: Enter end date + description_enter_number: Enter number + description_enter_text: Enter text + description_filter: Filter + description_filter_toggle: Show/Hide filter + description_category_reassign: Choose category + description_message_content: Message content + description_my_project: You are member + description_notes: Notes + description_parent_work_package: Parent work package of current + description_project_scope: Search scope + description_query_sort_criteria_attribute: Sort attribute + description_query_sort_criteria_direction: Sort direction + description_search: Searchfield + description_select_work_package: Select work package + description_selected_columns: Selected Columns + description_sub_work_package: Sub work package of current + description_toc_toggle: Show/Hide table of contents + description_wiki_subpages_reassign: Choose new parent page + direction: ltr + ee: + features: + baseline_comparison: Baseline Comparisons + board_view: Advanced Boards + calculated_values: Calculated values + capture_external_links: Capture External Links + internal_comments: Internal Comments + custom_actions: Custom Actions + custom_field_hierarchies: Hierarchies + customize_life_cycle: Customize Life Cycle + date_alerts: Date Alerts + define_custom_style: Custom theme and logo + edit_attribute_groups: Edit Attribute Groups + gantt_pdf_export: Gantt PDF Export + ldap_groups: LDAP users and group sync + mcp_server: Model Context Protocol (MCP) + meeting_templates: Reusable meeting templates + nextcloud_sso: Single Sign-On for Nextcloud Storage + one_drive_sharepoint_file_storage: OneDrive/SharePoint File Storage + placeholder_users: Placeholder Users + portfolio_management: Portfolio management + project_creation_wizard: Project initiation request + project_list_sharing: Project List Sharing + readonly_work_packages: Readonly Work Packages + scim_api: SCIM server API + sso_auth_providers: Single Sign-On + team_planner_view: Team Planner View + virus_scanning: Antivirus Scanning + weighted_item_lists: Weighted item lists + work_package_query_relation_columns: Work Package Query Relation Columns + work_package_sharing: Share work packages with external users + work_package_subject_generation: Work Package Subject Generation + upsell: + buy_now_button: Buy now + plans_title: Enterprise plans + title: Enterprise add-on + plan_title: Enterprise %{plan} add-on + plan_name: "%{plan} enterprise plan" + trial_text: This feature is included in your active Enterprise trial. + plan_text_html: Available starting with the %{plan_name}. + unlimited: Unlimited + already_have_token: 'Already have a token? Add it using the button below to upgrade to the booked Enterprise plan. + + ' + hide_banner: Hide this banner + homescreen_description: 'Enterprise plans extend the Community edition of OpenProject with additional [Enterprise add-ons](enterprise_url) and professional support, ideal for organizations running OpenProject in a mission-critical environment. + + ' + homescreen_subline: By upgrading, you will also be supporting an open source project. + baseline_comparison: + description: Highlight changes made to this list since any point in the past. + benefits: + description: What are the benefits of the Enterprise on-premises edition? + high_security: Security features + high_security_text: Single sign on (SAML, OpenID Connect, CAS), LDAP groups. + installation: Installation support + installation_text: Experienced software engineers guide you through the complete installation and setup process in your own infrastructure. + premium_features: Enterprise add-ons + premium_features_text: Agile boards, custom theme and logo, graphs, intelligent workflows with custom actions, full text search for work package attachments and multi-select custom fields. + professional_support: Professional support + professional_support_text: Get reliable, high-touch support from senior support engineers with expert knowledge about running OpenProject in business-critical environments. + work_package_subject_generation: + description: Create automatically generated subjects using referenced attributes and text. + customize_life_cycle: + description: Create and organize different project phases than the ones provided by PM2 project cycle planning. + capture_external_links: + description: Prevent social engineering attacks by capturing and warning about external links before users visit them. + work_package_query_relation_columns: + description: Need to see relations or child elements in the work package list? + edit_attribute_groups: + description: 'Customize form configuration with these additional add-ons:' + features: + groups: Add new attribute groups + rename: Rename attribute groups + related: Add a table of related work packages + readonly_work_packages: + description: Mark work packages as read-only for specific statuses. + custom_field_hierarchies: + description: Hierarchy custom fields allow organizing hierarchical structures in work packages and projects by making use of multi-level select lists. + date_alerts: + description: With date alerts, you will be notified of upcoming start or finish dates so that you never miss or forget an important deadline. + weighted_item_lists: + description: Weighted item lists allow you to create a list with underlying numeric values associated. + work_package_sharing: + description: Share work packages with users who are not members of the project. + project_list_sharing: + description: Share project lists with individual users. + calculated_values: + description: Calculated values allow you to create a mathematical formula based attribute using numeric values and other project attributes and custom fields. + define_custom_style: + title: Custom color theme and logo + more_info: 'Note: the used logo will be publicly accessible.' + description: Customize your OpenProject installation with your own logo and colors. + custom_actions: + title: Custom actions + description: Custom actions are one-click shortcuts to a set of pre-defined actions that you can make available on certain work packages based on status, role, type or project. + mcp_server: + description: Bring OpenProject into your AI workflows with a secure MCP server. + meeting_templates: + description: Define meeting templates with a set agenda structure and save time by reusing them when creating new meetings. + nextcloud_sso: + title: Single Sign-On for Nextcloud Storage + description: Enable seamless and secure authentication for your Nextcloud storage with Single Sign-On. Simplify access management and enhance user convenience. + scim_api: + title: SCIM clients + description: Automate user management in OpenProject by seamlessly integrating external identity services like Microsoft Entra or Keycloak through our SCIM Server API. Available starting with the Enterprise corporate plan. + sso_auth_providers: + title: Single Sign-On (SSO) + description: Enable users to log in via external SSO providers using SAML or OpenID Connect for seamless access and integration with existing identity systems. + virus_scanning: + description: Ensure uploaded files in OpenProject are scanned for viruses before being accessible by other users. + project_creation_wizard: + description: Generate a step-by-step wizard to help project managers fill out a project initiation request. + placeholder_users: + title: Placeholder users + description: 'Placeholder users are a way to assign work packages to users who are not part of your project. They can be useful in a range of scenarios; for example, if you need to track tasks for a resource that is not yet named or available, or if you don’t want to give that person access to OpenProject but still want to track tasks assigned to them. + + ' + internal_comments: + title: Internal comments + description: Internal comments allow an internal team to communicate amongst themselves privately. These are only visible to certain project roles and will never be visible publicly. + internal_comments_inline: + title: Write internal comments only a small group can see + description: " " + portfolio_management: + description: Align your projects to your strategic goals by organizing them into portfolios and programs. + teaser: + title: + one: One day left of %{trial_plan} trial token + other: "%{count} days left of %{trial_plan} trial token" + description_html: You have access to all %{trial_plan} features. + trial: + not_found: You have requested a trial token, but that request is no longer available. Please try again. + wait_for_confirmation: We sent you an email to confirm your address in order to retrieve a trial token. + already_retrieved: 'Your trial enterprise token was already retrieved. Please check your emails for the token being attached. Please reach out to our support team if you need a new one. + + ' + successfully_saved: Your trial enterprise token has been successfully retrieved. + token_sent: Trial token requested + request_again: Request again + resend_action: Resend confirmation email + welcome_title: Quick feature overview + welcome_description: Get a quick overview of project management and team collaboration with OpenProject Enterprise edition. + confirmation_info: 'We sent you an email on %{date} to %{email} with all the information to start the free trial of OpenProject Enterprise. Please check your inbox and click the confirmation link provided to start your 14-day free trial. + + ' + confirmation_subline: 'Please, check your inbox and follow the steps to start your 14-day free trial. + + ' + domain_caption: The token will be valid for your currently configured host name. + receive_newsletter: I want to receive the OpenProject [newsletter](newsletter_url). + consent: I agree with the [terms of service](tos_url) and the [privacy policy](privacy_url). + email_calendar_updates: + state: + disabled: Disabled. + enabled: Enabled. + button: + disabled: Enable + enabled: Disable + enumeration_activities: Time tracking activities + enumeration_work_package_priorities: Work package priorities + enumeration_reported_project_statuses: Reported status + enumeration_caption_order_changed: Order successfully changed. + enumeration_could_not_be_moved: Enumeration could not be moved. + enterprise_trials: + dialog_component: + title: Enterprise Trial + error_auth_source_sso_failed: Single Sign-On (SSO) for user '%{value}' failed + error_can_not_archive_project: 'This project cannot be archived: %{errors}' + error_can_not_delete_entry: Unable to delete entry + error_can_not_delete_custom_field: Unable to delete custom field + error_can_not_delete_in_use_archived_undisclosed: There are also work packages in archived projects. You need to ask an administrator to perform the deletion to see which projects are affected. + error_can_not_delete_in_use_archived_work_packages: 'There are also work packages in archived projects. You need to reactivate the following projects first, before you can change the attribute of the respective work packages: %{archived_projects_urls}' + error_can_not_delete_type: + explanation: This type contains work packages and cannot be deleted. You can see all affected work packages in this view. + error_can_not_delete_standard_type: Standard types cannot be deleted. + error_can_not_invite_user: Failed to send invitation to user. + error_can_not_remove_role: This role is in use and cannot be deleted. + error_can_not_reopen_work_package_on_closed_version: A work package assigned to a closed version cannot be reopened + error_can_not_find_all_resources: Could not find all related resources to this request. + error_can_not_unarchive_project: 'This project cannot be unarchived: %{errors}' + error_check_user_and_role: Please choose a user and a role. + error_code: Error %{code} + error_color_could_not_be_saved: Color could not be saved + error_cookie_missing: The OpenProject cookie is missing. Please ensure that cookies are enabled, as this application will not properly function without. + error_custom_option_not_found: Option does not exist. + error_enterprise_plan_needed: You need the %{plan} enterprise plan to perform this action. + error_enterprise_activation_user_limit: Your account could not be activated (user limit reached). Please contact your administrator to gain access. + error_enterprise_token_invalid_domain: The Enterprise edition is not active. Your Enterprise token's domain (%{actual}) does not match the system's host name (%{expected}). + error_failed_to_delete_entry: Failed to delete this entry. + error_in_dependent: 'Error attempting to alter dependent object: %{dependent_class} #%{related_id} - %{related_subject}: %{error}' + error_in_new_dependent: 'Error attempting to create dependent object: %{dependent_class} - %{related_subject}: %{error}' + error_invalid_selected_value: Invalid selected value. + error_journal_attribute_not_present: Journal does not contain attribute %{attribute}. + error_pdf_export_too_many_columns: Too many columns selected for the PDF export. Please reduce the number of columns. + error_pdf_date_range_too_long: The selected work package date range exceeds the allowable PDF export limit. Please condense the range to a maximum of %{years} years. + error_pdf_failed_to_export: 'The PDF export could not be saved: %{error}' + error_token_authenticity: Unable to verify Cross-Site Request Forgery token. Did you try to submit data on multiple browsers or tabs? Please close all tabs and try again. + error_reminder_not_found: The reminder was not found or was already notified about. + error_work_package_not_found_in_project: The work package was not found or does not belong to this project + error_work_package_id_not_found: The work package was not found. + error_must_be_project_member: must be project member + error_migrations_are_pending: Your OpenProject installation has pending database migrations. You have likely missed running the migrations on your last upgrade. Please check the upgrade guide to properly upgrade your installation. + error_migrations_visit_upgrade_guides: Please visit our upgrade guide documentation + error_no_default_work_package_status: No default work package status is defined. Please check your configuration (Go to "Administration -> Work package statuses"). + error_no_type_in_project: No type is associated to this project. Please check the Project settings. + error_omniauth_registration_timed_out: The registration via an external authentication provider timed out. Please try again. + error_omniauth_invalid_auth: The authentication information returned from the identity provider was invalid. Please contact your administrator for further help. + error_password_change_failed: An error occurred when trying to change the password. + error_scm_command_failed: 'An error occurred when trying to access the repository: %{value}' + error_scm_not_found: The entry or revision was not found in the repository. + error_type_could_not_be_saved: Type could not be saved + error_unable_delete_status: The work package status cannot be deleted since it is used by at least one work package. + error_unable_delete_default_status: Unable to delete the default work package status. Please select another default work package status before deleting the current one. + error_unable_to_connect: Unable to connect (%{value}) + error_unable_delete_wiki: Unable to delete the wiki page. + error_unable_update_wiki: Unable to update the wiki page. + error_workflow_copy_source: Please select a source type or role + error_workflow_copy_target: Please select target type(s) and role(s) + error_menu_item_not_created: Menu item could not be added + error_menu_item_not_saved: Menu item could not be saved + error_wiki_root_menu_item_conflict: 'Can''t rename "%{old_name}" to "%{new_name}" due to a conflict in the resulting menu item with the existing menu item "%{existing_caption}" (%{existing_identifier}). + + ' + error_external_authentication_failed_message: 'An error occurred during external authentication: %{message}' + error_attribute_not_highlightable: 'Attribute(s) not highlightable: %{attributes}' + events: + changeset: Changeset edited + message: Message edited + news: News + project_details: Project details edited + project: Project edited + projects: Project edited + reply: Replied + time_entry: Timelog edited + wiki_page: Wiki page edited + work_package_closed: Work Package closed + work_package_edit: Work Package edited + work_package_note: Work Package note added + title: + project_html: 'Project: %{name}' + subproject_html: 'Subproject: %{name}' + export: + dialog: + title: Export + submit: Export + save_export_settings: + label: Save settings + format: + label: File format + options: + csv: + label: CSV + pdf: + label: PDF + xls: + label: XLS + columns: + input_label_report: Add columns to attribute table + input_caption_report: By default all attributes added as columns in the work package list are selected. Long text fields are not available in the attribute table, but can be displayed below it. + input_caption_table: By default all attributes added as columns in the work package list are selected. Long text fields are not available in table based exports. + input_caption_required: It is not possible to export the view without any column. Please add at least one column. + pdf: + export_type: + label: PDF export type + options: + table: + label: Table + caption: Export the work packages list in a table with the desired columns. + report: + label: Report + caption: Export the work package on a detailed report of all work packages in the list. + gantt: + label: Gantt chart + caption: Export the work packages list in a Gantt diagram view. + include_images: + label: Include images + caption: Exclude images to reduce the size of the PDF export. + gantt_zoom_levels: + label: Zoom levels + caption: Select what is the zoom level for dates displayed in the chart. + options: + days: Days + weeks: Weeks + months: Months + quarters: Quarters + column_width: + label: Table column width + options: + narrow: Narrow + medium: Medium + wide: Wide + very_wide: Very wide + paper_size: + label: Paper size + caption: Depending on the chart size more than one page might be exported. + long_text_fields: + input_caption: By default all long text fields are selected. + input_label: Add long text fields + input_placeholder: Search for long text fields + drag_area_label: Manage long text fields + xls: + include_relations: + label: Include relations + caption: This option will create a duplicate of each work package for every relation this has with another work package. + include_descriptions: + label: Include descriptions + caption: This option will add a description column in raw format. + your_work_packages_export: Work packages are being exported + your_projects_export: Projects are being exported + succeeded: Export completed + failed: 'An error has occurred while trying to export the work packages: %{message}' + demo: + heading: Demo PDF + footer: Generated by OpenProject + button_text: Generate Demo PDF + errors: + embedded_table_with_too_many_columns: This embedded work package table could not fit on the page, please reduce the number of columns. + format: + atom: Atom + csv: CSV + pdf: PDF + pdf_overview_table: PDF Table + pdf_report_with_images: PDF Report with images + pdf_report: PDF Report + pdf_gantt: PDF Gantt + image: + omitted: Image not exported. + macro: + error: Macro error, %{message} + attribute_not_found: 'attribute not found: %{attribute}' + model_not_found: 'invalid attribute model: %{model}' + resource_not_found: 'resource not found: %{resource}' + nested_rich_text_unsupported: Nested rich text embedding currently not supported in export + units: + hours: h + days: d + pdf_generator: + page_nr_footer: Page %{page} of %{total} + template_attributes: + label: Attributes and description + caption: All the attributes present in the current form configuration using the default template. + template_contract: + label: Contract + caption: Work package details formatted to the standard German contract form. + dialog: + title: Generate PDF + submit: Download + templates: + label: Template + none_enabled: No template has been enabled for this work package type + footer_center: + label: Footer text + caption: This text will appear on every page at the center of the footer. + footer_right: + label: Footer text + caption: This text will appear on every page at the right of the footer. + hyphenation: + label: Hyphenation + caption: Break line between words for better layout and justification. + hyphenation_language: + label: Language and hyphenation + page_orientation: + label: Page orientation + caption: Select the orientation of the pages in the PDF document. + options: + portrait: Portrait + landscape: Landscape + extraction: + available: + pdftotext: Pdftotext available (optional) + unrtf: Unrtf available (optional) + catdoc: Catdoc available (optional) + xls2csv: Xls2csv available (optional) + catppt: Catppt available (optional) + tesseract: Tesseract available (optional) + filterable_tree_view: + filter_mode: + all: All + label: Filter mode + selected: Selected + include_sub_items: Include sub-items + no_results_text: No results + toggle_switch: + label_on: 'On' + label_off: 'Off' + general_csv_decimal_separator: "." + general_csv_encoding: UTF-8 + general_csv_separator: "," + general_first_day_of_week: '7' + general_pdf_encoding: ISO-8859-1 + general_text_no: 'no' + general_text_yes: 'yes' + general_text_No: 'No' + general_text_Yes: 'Yes' + general_text_true: 'true' + general_text_false: 'false' + gui_validation_error: 1 error + gui_validation_error_plural: "%{count} errors" + health_reports: + report_component: + checks: + failures: + one: "%{count} check failed" + other: "%{count} checks failed" + success: All checks passed + warnings: + one: "%{count} check returned a warning" + other: "%{count} checks returned a warning" + summary: + failure: Some checks failed and the system does not work as expected. + success: All connections and systems are working as expected. + warning: Some checks returned a warning. This can lead to unexpected behaviour. + result_component: + status: + failed: Failed + passed: Passed + skipped: Skipped + warning: Warning + homescreen: + additional: + projects: Newest visible projects in this instance. + no_visible_projects: There are no visible projects in this instance. + users: Newest registered users in this instance. + blocks: + community: OpenProject community + upsell: + title: Upgrade to Enterprise edition + new_features: + header: Read about new features and product updates. + learn_about: Learn more about all new features + missing: There are no highlighted features yet. + '17_4': + new_features_title: 'The release contains various new features and improvements, such as: + + ' + new_features_list: + line_0: Jira Migrator with support for basic custom fields. + line_1: Backlog buckets for structuring and prioritizing work packages during backlog refinement. + line_2: Easier drag and drop and improved move options in the Backlogs module. + line_3: Sprint Start and Complete buttons in the sprint header. + line_4: Copy workflow settings between roles. + line_5: "'My Meetings' widget on the Home and Project Overview pages." + links: + upgrade_enterprise_edition: Upgrade to Enterprise edition + postgres_migration: Migrating your installation to PostgreSQL + user_guides: User guides + faq: FAQ + impressum: Legal notice + glossary: Glossary + shortcuts: Shortcuts + blog: OpenProject blog + forums: Community forum + security_alerts: Security alerts + newsletter: Newsletter + image_conversion: + imagemagick: Imagemagick + journals: + changes_retracted: The changes were retracted. + caused_changes: + budget_deleted: Budget has been deleted + dates_changed: Dates changed + default_attribute_written: Read-only attributes written + import: Imported + progress_mode_changed_to_status_based: Progress calculation updated + status_changed: Status '%{status_name}' + system_update: 'OpenProject system update:' + work_package_duplicate_closed: 'Duplicate work package updated:' + total_percent_complete_mode_changed_to_work_weighted_average: Calculation of % Complete totals now weighted by Work. + total_percent_complete_mode_changed_to_simple_average: Calculation of % Complete totals now based on a simple average of only % Complete values. + cause_descriptions: + import: + header: changes by %{author} + field_changed: "%{field} changed from %{old_value} to %{new_value}" + field_set: "%{field} set to %{value}" + field_removed: "%{field} removed" + field_updated: "%{field} updated" + deleted_with_diff: "%{field} deleted (%{link})" + changed_with_diff: "%{field} changed (%{link})" + set_with_diff: "%{field} set (%{link})" + work_package_predecessor_changed_times: by changes to predecessor %{link} + work_package_parent_changed_times: by changes to parent %{link} + work_package_children_changed_times: by changes to child %{link} + work_package_related_changed_times: by changes to related %{link} + work_package_duplicate_closed: The status was automatically updated by the duplicated work package %{link} + unaccessable_work_package_changed: by changes to a related work package + budget_deleted: Budget has been deleted + working_days_changed: + changed: by changes to working days (%{changes}) + days: + working: "%{day} is now working" + non_working: "%{day} is now non-working" + dates: + working: "%{date} is now working" + non_working: "%{date} is now non-working" + progress_mode_changed_to_status_based: Progress calculation mode set to status-based + status_excluded_from_totals_set_to_false_message: now included in hierarchy totals + status_excluded_from_totals_set_to_true_message: now excluded from hierarchy totals + status_percent_complete_changed: "% Complete changed from %{old_value}% to %{new_value}%" + system_update: + file_links_journal: 'From now on, activity related to file links (files stored in external storages) will appear here in the Activity tab. The following represent activity concerning links that already existed: + + ' + progress_calculation_adjusted_from_disabled_mode: Progress calculation automatically set to work-based mode and adjusted with version update. + progress_calculation_adjusted: Progress calculation automatically adjusted with version update. + scheduling_mode_adjusted: Scheduling mode automatically adjusted with version update. + totals_removed_from_childless_work_packages: Work and progress totals automatically removed for non-parent work packages with version update. This is a maintenance task and can be safely ignored. + sprint_migration: Version '%{version_name}' has been copied as a sprint. + total_percent_complete_mode_changed_to_work_weighted_average: Child work packages without Work are ignored. + total_percent_complete_mode_changed_to_simple_average: Work values of child work packages are ignored. + links: + configuration_guide: Configuration guide + get_in_touch: You have questions? Get in touch with us. + instructions_after_registration_link: You can sign in as soon as your account has been activated by clicking [here](signin_url). + instructions_after_logout_link: You can sign in again by clicking [here](signin_url). + instructions_after_error_link: You can try to sign in again by clicking [here](signin_url). If the error persists, ask your admin for help. + menus: + admin: + ai: Artificial Intelligence (AI) + aggregation: Aggregation + api_and_webhooks: API and webhooks + mail_notification: Email notifications + mails_and_notifications: Emails and notifications + mcp_configurations: Model Context Protocol (MCP) + quick_add: + label: Add… + my_account: + notifications_and_email: + title: Notification and email + tabs: + notifications: Notification settings + email_reminders: Email reminders + access_tokens: + description: Provider tokens are issued by OpenProject, allowing other applications to access it. Client tokens are issued by other applications, allowing OpenProject to access them. + no_results: + title: No access tokens to display + description: All of them have been disabled. They can be re-enabled in the administration menu. + access_tokens: Access tokens + headers: + action: Action + expiration: Expires + indefinite_expiration: Never + simple_revoke_confirmation: Are you sure you want to revoke this token? + tabs: + client: + title: Client tokens + provider: + title: Provider tokens + token/api: + blank_description: There is no API token yet. You can create one using the button below. + blank_title: No API token + title: API + table_title: API tokens + text_hint: API tokens allow third-party applications to communicate with this OpenProject instance via REST APIs. + static_token_name: API token + disabled_text: API tokens are not enabled by the administrator. Please contact your administrator to use this feature. + add_button: API token + ical: + blank_description: To add an iCalendar token, subscribe to a new or existing calendar from within the Calendar module of a project. You must have the necessary permissions. + blank_title: No iCalendar token + title: iCalendar + table_title: iCalendar tokens + text_hint_link: iCalendar tokens allow users to [subscribe to OpenProject calendars](docs_url) and view up-to-date work package information from external clients. + disabled_text: iCalendar subscriptions are not enabled by the administrator. Please contact your administrator to use this feature. + oauth_application: + active_tokens: Active tokens + blank_description: There is no third-party application access configured and active for you. + blank_title: No OAuth application token + last_refreshed_at: Last refreshed at + title: OAuth + table_title: OAuth application tokens + text_hint: OAuth application tokens allow third-party applications to connect with this OpenProject instance. + oauth_client: + blank_description: There are no OAuth client tokens yet. + blank_title: No OAuth client tokens + failed: An error occurred and the token couldn't be removed. Please try again later. + integration_type: Integration type + table_title: OAuth client tokens + text_hint: OAuth client tokens allow this OpenProject instance to connect with external applications, such as file storages. + title: OAuth + remove_token: Do you really want to remove this token? You will need to login again on %{integration}. + removed: OAuth client token successfully removed + unknown_integration: Unknown + token/rss: + add_button: RSS token + blank_description: There is no RSS token yet. You can create one using the button below. + blank_title: No RSS token + title: RSS + table_title: RSS tokens + text_hint: RSS tokens allow users to keep up with the latest changes in this OpenProject instance via an external RSS reader. + static_token_name: RSS token + disabled_text: RSS tokens are not enabled by the administrator. Please contact your administrator to use this feature. + storages: + unknown_storage: Unknown storage + email_reminders: + immediate_reminders: + title: Send me an email reminder + mentioned: Notify me when I am mentioned + personal_reminder: Notify me for personal reminders + daily_reminders: + title: Send me daily email reminders for unread notifications + description: You will receive these reminders only for unread notifications and only at hours you specify. Until you configure a time zone for your account, the times will be interpreted to be in UTC. + enabled: Enable daily email reminders + add_time: Add time + remove_time: Remove time + time_slot_label: Reminder time (UTC) + workdays: + title: Receive email reminders on these days + submit_button: Update reminder days + pause_reminders: + title: Pause email notifications + enabled: Temporarily pause daily email reminders + date_range: Pause period + email_alerts: + title: Email alerts for other items that are not work packages + description: Notifications today are limited to work packages. You can choose to continue receiving email alerts for these events until they are included in notifications. + news_added: News added + news_commented: Comment on a news item + document_added: Document added + forum_messages: Forum message posted + wiki_page_added: Wiki page added + wiki_page_updated: Wiki page updated + membership_added: Membership added + membership_updated: Membership updated + submit_button: Update alerts + notifications: + participating: + title: Participating + description: Notifications for all activities in work packages you are involved in (assignee, accountable or watcher). + submit_button: Update preferences + mentioned: Mentioned + watched: Watching + assignee: Assignee + responsible: Accountable + shared: Shared with me + date_alerts: + title: Date alerts + description: Automatic notifications when important dates are approaching for open work packages you are involved in (assignee, accountable or watcher). + submit_button: Update date alerts + start_date: Start date + due_date: Finish date + overdue: Overdue + times: + same_day: On the same day + one_day_before: 1 day before + three_days_before: 3 days before + seven_days_before: 7 days before + one_day_after: 1 day after + three_days_after: 3 days after + seven_days_after: 7 days after + non_participating: + title: Non-participating + description: Additional notifications for activities in all projects. + submit_button: Update preferences + work_package_created: New work packages + work_package_commented: All new comments + work_package_processed: All status changes + work_package_prioritized: All priority changes + work_package_scheduled: All date changes + project_specific_settings: + title: Project-specific notification settings + description: These project-specific settings override default settings above. + add_button: Add project-specific notifications + dialog_title: Add project-specific notifications + list_header: Projects with specific notifications + notifications: + reasons: + assigned: Assignee + dateAlert: Date alert + mentioned: Mentioned + responsible: Accountable + shared: Shared + watched: Watcher + reminder: Reminder + facets: + unread: Unread + unread_title: Show unread + all: All + all_title: Show all + menu: + by_project: Unread by project + by_reason: Reason + inbox: Inbox + send_notifications: Send notifications for this action + work_packages: + subject: + created: The work package was created. + assigned: You have been assigned to %{work_package} + subscribed: You subscribed to %{work_package} + mentioned: You have been mentioned in %{work_package} + responsible: You have become accountable for %{work_package} + watched: You are watching %{work_package} + query: + invalid_filter: Invalid notification filter + label_accessibility: Accessibility + label_account: Account + label_actions: Actions + label_active: Active + label_activate_user: Activate user + label_active_in_new_projects: Active in new projects + label_activity: Activity + label_add_edit_translations: Add and edit translations + label_add_another_file: Add another file + label_add_columns: Add selected columns + label_add_note: Add a note + label_add_projects: Add projects + label_add_related_work_packages: Add related work packages + label_add_subtask: Add subtask + label_added: added + label_added_by: Added by %{author} + label_added_by_on: Added by %{author} on %{date} + label_added_time_by: Added by %{author} %{age} ago + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_administration: Administration + label_interface_colors: Interface colors + label_interface_colors_description: 'These colors control how the application looks. If you modify them the theme will automatically be changed to Custom theme, but we can’t assure the compliance of the accessibility contrast minimums (WCAG 2.1). ' + label_age: Age + label_ago: days ago + label_all: all + label_all_uppercase: All + label_all_time: all time + label_all_words: All words + label_all_open_wps: All open + label_always_visible: Always displayed + label_announcement: Announcement + label_angular: AngularJS + label_app_modules: "%{app_title} modules" + label_api_access_key: API access key + label_api_access_key_created_on: API access key created %{value} ago + label_api_access_key_type: API + label_auto_option: "(auto)" + label_ical_access_key_type: iCalendar + label_ical_access_key_description: iCalendar token "%{token_name}" for "%{calendar_name}" in "%{project_name}" + label_ical_access_key_not_present: iCalendar token(s) not present. + label_ical_access_key_generation_hint: Automatically generated when subscribing to a calendar. + label_ical_access_key_latest: latest + label_ical_access_key_revoke: Revoke + label_integrations: Integrations + label_add_column: Add column + label_applied_status: Applied status + label_archive_project: Archive project + label_ascending: Ascending + label_assigned_to_me_work_packages: Work packages assigned to me + label_associated_revisions: Associated revisions + label_attachment_plural: Attachments + label_attribute: Attribute + label_attribute_plural: Attributes + label_ldap_auth_source_new: New LDAP connection + label_ldap_auth_source: LDAP connection + label_ldap_auth_source_plural: LDAP connections + label_attribute_expand_text: The complete text for '%{attribute}' + label_authentication: Authentication + label_available_custom_fields_projects: Available custom fields projects + label_available_global_roles: Available global roles + label_available_project_attributes: Available project attributes + label_available_project_forums: Available forums + label_available_project_repositories: Available repositories + label_available_project_versions: Available versions + label_available_project_work_package_categories: Available work package categories + label_available_project_work_package_types: Available work package types + label_available_projects: Available projects + label_api_doc: API documentation + label_backup: Backup + label_backup_code: Backup code + label_basic_details: Basic details + label_between: between + label_blocked_by: blocked by + label_blocks: blocks + label_blog: Blog + label_forums_locked: Locked + label_forum_new: New forum + label_forum_plural: Forums + label_forum_sticky: Sticky + label_boolean: Boolean + label_board_plural: Boards + label_branch: Branch + label_browse: Browse + label_builtin: Built-in + label_bulk_edit_selected_work_packages: Bulk edit selected work packages + label_bundled: "(Bundled)" + label_calendar: Calendar + label_calendars_and_dates: Calendars and dates + label_calendar_show: Show Calendar + label_category: Category + label_completed: Completed + label_committed_at_html: "%{committed_revision_link} at %{date}" + label_committed_link: committed revision %{revision_identifier} + label_consent_settings: User Consent + label_wiki_menu_item: Wiki menu item + label_select_main_menu_item: Select new main menu item + label_required_disk_storage: Required disk storage + label_send_invitation: Send invitation + label_calculated_value: Calculated value + label_change_parent: Change parent + label_change_plural: Changes + label_change_properties: Change properties + label_change_status: Change status + label_change_status_of_user: 'Change status of #{username}' + label_change_view_all: View all changes + label_changes_details: Details of all changes + label_changeset: Changeset + label_changeset_id: Changeset ID + label_changeset_plural: Changesets + label_checked: checked + label_check_uncheck_all_in_column: Check/Uncheck all in column + label_check_uncheck_all_in_row: Check/Uncheck all in row + label_child_element: Child element + label_choices: Choices + label_close_versions: Close completed versions + label_closed_work_packages: closed + label_collapse: Collapse + label_collapsed_click_to_show: Collapsed. Click to show + label_configuration: configuration + label_comment_add: Add a comment + label_comment_added: Comment added + label_comment_delete: Delete comments + label_comment_plural: Comments + label_commits_per_author: Commits per author + label_commits_per_month: Commits per month + label_confirmation: Confirmation + label_contains: contains + label_starts_with: starts with + label_content: Content + label_color_plural: Colors + label_copied: copied + label_copy_same_as_target: Same as target + label_copy_source: Source + label_copy_target: Target + label_copy_workflow_from: Copy workflow from + label_copy_workflow_from_type: Copy to another type + label_copy_workflow_from_role: Copy to other roles + label_copy_project: Copy project + label_core_version: Core version + label_core_build: Core build + label_created_by: Created by %{user} + label_current_status: Current status + label_current_version: Current version + label_custom_field_add_no_type: Add this field to a work package type + label_custom_field_new: New custom field + label_custom_field_plural: Custom fields + label_custom_field_default_type: Empty type + label_custom_style: Design + label_custom_style_description: Choose how OpenProject looks to you with themes, select your default colors to use in the app and how exports look like. + label_dashboard: Dashboard + label_database_version: PostgreSQL version + label_date: Date + label_dates: Dates + label_date_and_time: Date and time + label_date_format: Date format + label_date_from: From + label_date_from_to: From %{start} to %{end} + label_date_to: To + label_day_plural: days + label_default: Default + label_delete_user: Delete user + label_delete_project: Delete project + label_delete: Delete + label_deleted: deleted + label_deleted_custom_field: "(deleted custom field)" + label_deleted_custom_item: "(deleted item)" + label_deleted_custom_option: "(deleted option)" + label_empty_element: "(empty)" + label_go_back: Go back one menu level + label_go_forward: Open %{module} sub-menu + label_missing_or_hidden_custom_option: "(missing value or lacking permissions to access)" + label_descending: Descending + label_details: Details + label_defaults: Defaults + label_development_roadmap: Development roadmap + label_diff: diff + label_diff_inline: inline + label_diff_side_by_side: side by side + label_digital_accessibility: Digital accessibility (DE) + label_disabled: disabled + label_disabled_uppercase: Disabled + label_display: Display + label_display_per_page: 'Per page: %{value}' + label_display_used_statuses_only: Only display statuses that are used by this type + label_download: "%{count} Download" + label_download_plural: "%{count} Downloads" + label_downloads_abbr: D/L + label_duplicated_by: duplicated by + label_duplicate: duplicate + label_duplicates: duplicates + label_edit: Edit + label_edit_x: 'Edit: %{x}' + label_view_x: 'View: %{x}' + label_enable_multi_select: Toggle multiselect + label_enabled_project_custom_fields: Enabled custom fields + label_enabled_project_modules: Enabled modules + label_enabled_project_activities: Enabled time tracking activities + label_end_to_end: end to end + label_end_to_start: end to start + label_enumeration_new: New enumeration value + label_enumeration_value: Enumeration value + label_enumerations: Enumerations + label_enterprise: Enterprise + label_enterprise_active_users: "%{current}/%{limit} booked active users" + label_enterprise_edition: Enterprise edition + label_enterprise_support: Enterprise support + label_environment: Environment + label_estimates_and_progress: Estimates and progress + label_equals: is + label_equals_with_descendants: is any with descendants + label_everywhere: everywhere + label_example: Example + label_experimental: Experimental + label_i_am_member: I am member + label_ifc_viewer: Ifc Viewer + label_ifc_model_plural: Ifc Models + label_import: Import + label_export_to: 'Also available in:' + label_expand: Expand + label_expanded_click_to_collapse: Expanded. Click to collapse + label_f_hour: "%{value} hour" + label_f_hour_plural: "%{value} hours" + label_favorite: Favorite + label_feed_plural: Feeds + label_feeds_access_key: RSS access key + label_feeds_access_key_created_on: RSS access key created %{value} ago + label_feeds_access_key_type: RSS + label_file_plural: Files + label_filter: Filters + label_filter_add: Add filter + label_filter_by: Filter by + label_filter_any_name_attribute: Name attributes + label_filter_plural: Filters + label_filters_toggle: Show/hide filters + label_float: Float + label_folder: Folder + label_follows: follows + label_form_configuration: Form configuration + label_formula: Formula + label_gantt_chart: Gantt chart + label_gantt_chart_plural: Gantt charts + label_general: General + label_generate_key: Generate a key + label_global_modules: Global modules + label_global_roles: Global roles + label_git_path: Path to .git directory + label_greater_or_equal: ">=" + label_group_by: Group by + label_group_new: New group + label_group: Group + label_group_named: Group %{name} + label_group_plural: Groups + label_help: Help + label_here: here + label_hide: Hide + label_history: History + label_hierarchy: Hierarchy + label_hierarchy_leaf: Hierarchy leaf + label_home: Home + label_subject_or_id: Subject or ID + label_calendar_subscriptions: Calendar subscriptions + label_identifier: Identifier + label_project_identifier: Project identifier + label_in: in + label_in_less_than: in less than + label_in_more_than: in more than + label_inactive: Inactive + label_incoming_emails: Incoming emails + label_includes: includes + label_incomplete: Incomplete + label_include_sub_projects: Include sub-projects + label_index_by_date: Index by date + label_index_by_title: Index by title + label_information: Information + label_information_plural: Information + label_installation_guides: Installation guides + label_integer: Integer + label_interface: Interface + label_internal: Internal + label_introduction_video: Getting started video + label_invite_user: Invite user + label_item: Item + label_item_plural: Items + label_weighted_item_list: Weighted item list + label_share: Share + label_share_project_list: Share project list + label_share_work_package: Share work package + label_show_all_registered_users: Show all registered users + label_show_less: Show less + label_show_more: Show more + label_journal: Journal + label_journal_diff: Description Comparison + label_language: Language + label_languages: Languages + label_external_links: External links + label_locale: Language and region + label_jump_to_a_project: Jump to a project... + label_jira_import: Jira Migrator + label_keyword_plural: Keywords + label_language_based: Based on user's language + label_last_activity: Last activity + label_last_change_on: Last change on + label_last_changes: last %{count} changes + label_last_login: Last login + label_last_month: last month + label_last_n_days: last %{count} days + label_last_week: last week + label_latest_revision: Latest revision + label_latest_revision_plural: Latest revisions + label_ldap_authentication: LDAP authentication + label_learn_more: Learn more + label_less_or_equal: "<=" + label_less_than_ago: less than days ago + label_link_url: Link (URL) + label_list: List + label_loading: Loading... + label_locked: locked + label_lock_user: Lock user + label_logged_as: Logged in as + label_login: Sign in + label_custom_comment: "%{name} comment" + label_custom_logo: Custom logo desktop + label_custom_logo_mobile: Custom logo mobile + label_custom_export_logo: Custom export logo + label_custom_export_cover: Custom export cover background + label_custom_export_footer: Custom export footer image + label_custom_export_font_regular: Regular + label_custom_export_font_bold: Bold + label_custom_export_font_italic: Italic + label_custom_export_font_bold_italic: Bold Italic + label_custom_export_cover_overlay: Custom export cover background overlay + label_custom_export_cover_text_color: Text color + label_custom_pdf_export_settings: Custom PDF export settings + label_custom_favicon: Custom favicon + label_custom_touch_icon: Custom touch icon + label_departments: Organization + label_departments_description_html: 'Define your company’s structure by creating departments and sub-departments in a hierarchical way. This allows you to reflect reporting lines and maintain a clear, structured overview of your organization within OpenProject. You can also import an existing organization structure through [LDAP group synchronisation](ldap_docs_article). + + ' + label_logout: Sign out + label_mapping_for: 'Mapping for: %{attribute}' + label_main_menu: Side Menu + label_manage: Manage + label_manage_groups: Manage groups + label_managed_repositories_vendor: Managed %{vendor} repositories + label_mathematical_operators: Mathematical operators + label_max_size: Maximum size + label_me: me + label_member_new: New member + label_member_all_admin: "(All roles due to admin status)" + label_member_plural: Members + label_membership_plural: Memberships + label_membership_added: Member added + label_membership_updated: Member updated + label_menu: Menu + label_menu_badge: + pre_alpha: pre-alpha + alpha: alpha + beta: beta + label_menu_item_name: Name of menu item + label_message: Message + label_message_last: Last message + label_message_new: New message + label_message_plural: Messages + label_message_posted: Message added + label_min_max_length: Min - Max length + label_minute_plural: minutes + label_missing_api_access_key: Missing API access key + label_missing_feeds_access_key: Missing RSS access key + label_modification: "%{count} change" + label_modified: modified + label_module_plural: Modules + label_modules: Modules + label_months_from: months from + label_more: More + label_more_information: More information + label_more_than_ago: more than days ago + label_move_column_left: Move column left + label_move_column_right: Move column right + label_move_work_package: Move work package + label_my_account: Account settings + label_my_activity: My activity + label_my_account_data: My account data + label_my_avatar: My avatar + label_my_queries: My custom queries + label_name: Name + label_never: Never + label_new: New + label_new_features: New features + label_new_statuses_allowed: New statuses allowed + label_news_singular: News + label_news_added: News added + label_news_comment_added: Comment added to a news + label_news_latest: Latest news + label_news_new: Add news + label_news_edit: Edit news + label_news_plural: News + label_news_view_all: View all news + label_next: Next + label_next_week: Next week + label_next_year: Next year + label_no_change_option: "(No change)" + label_no_data: No data to display + label_no_due_date: no finish date + label_no_start_date: no start date + label_no_parent_page: No parent page + label_no_parent_group: "(No parent group)" + label_notification_center_plural: Notifications + label_nothing_display: Nothing to display + label_nobody: nobody + label_not_configured: Not configured + label_not_found: not found + label_none: none + label_none_parentheses: "(none)" + label_not_contains: doesn't contain + label_not_equals: is not + label_life_cycle_step_plural: Project life cycle + label_on: 'on' + label_operator_all: is not empty + label_operator_none: is empty + label_operator_equals_or: is (OR) + label_operator_equals_all: is (AND) + label_operator_shared_with_user_any: any + label_open: open + label_closed: closed + label_open_menu: Open menu + label_open_work_packages: open + label_open_work_packages_plural: open + label_openproject_website: OpenProject website + label_optional_description: Description + label_options: Options + label_other: Other + label_overall_activity: Overall activity + label_overview: Overview + label_page_title: Page title + label_parent_group_caption: 'Setting a parent group will make this group a subgroup of the selected parent group. This will also inherit all memberships, including permissions of the parent group. + + ' + label_part_of: part of + label_password_lost: Forgot your password? + label_password_rule_lowercase: Lowercase + label_password_rule_numeric: Numeric Characters + label_password_rule_special: Special Characters + label_password_rule_uppercase: Uppercase + label_password_requirement_lowercase: Must contain at least one lowercase character. + label_password_requirement_numeric: Must contain at least one numeric character. + label_password_requirement_special: Must contain at least one special character. + label_password_requirement_uppercase: Must contain at least one uppercase character. + label_path_encoding: Path encoding + label_per_page: 'Per page:' + label_people: People + label_permissions: Permissions + label_permissions_report: Permissions report + label_personalize_page: Personalize this page + label_placeholder_user: Placeholder user + label_placeholder_user_new: New placeholder user + label_placeholder_user_plural: Placeholder users + label_planning: Planning + label_please_login: Please log in + label_plugins: Plugins + label_portfolio_plural: Portfolios + label_modules_and_plugins: Modules and Plugins + label_precedes: precedes + label_preferences: Preferences + label_preview: Preview + label_preview_not_available: Preview not available + label_previous: Previous + label_previous_week: Previous week + label_previous_year: Previous year + label_principal_invite_via_email: " or invite new users via email" + label_principal_search: Add existing users or groups + label_privacy_policy: Data privacy and security policy + label_product_version: Product version + label_profile: Profile + label_percent_complete: "% Complete" + label_progress_tracking: Progress tracking + label_project: Project + label_project_activity: Project activity + label_project_attribute_plural: Project attributes + label_project_attribute_manage_link: Manage project attributes + label_project_count: Total number of projects + label_project_copy_notifications: Send email notifications during the project copy + label_project_initiation_export_pdf: Export PDF for %{project_creation_name} + label_project_latest: Latest projects + label_project_default_type: Allow empty type + label_project_hierarchy: Project hierarchy + label_project_mappings: Projects + label_project_new: New project + label_project_plural: Projects + label_project_list_plural: Project lists + label_project_life_cycle: Project life cycle + label_project_attributes_plural: Project attributes + label_project_custom_field_plural: Project attributes + label_project_settings: Project settings + label_project_attributes_settings: Project attributes settings + label_project_storage_plural: File storages + label_project_storage_project_folder: 'File storages: Project folders' + label_projects_disk_usage_information: "%{count} projects using %{used_disk_space} disk space" + label_project_view_all: View all projects + label_project_show_details: Show project details + label_project_hide_details: Hide project details + label_portfolio: Portfolio + label_portfolio_new: New portfolio + label_program: Program + label_program_new: New program + label_public_projects: Public projects + label_query_new: New query + label_query_plural: Custom queries + label_read: Read... + label_read_documentation: Read documentation + label_register: Create a new account + label_register_with_developer: Register as developer + label_registered_on: Registered on + label_related_work_packages: Related work packages + label_relates: related to + label_relates_to: related to + label_relation: Relation + label_relation_actions: Relation actions + label_relation_delete: Delete relation + label_relation_edit: Edit relation + label_relation_new: New relation + label_release_notes: Release notes + label_remaining_work: Remaining work + label_remove_column: Remove column + label_remove_columns: Remove selected columns + label_renamed: renamed + label_reply_plural: Replies + label_report: Report + label_report_bug: Report a bug + label_report_plural: Reports + label_reported_work_packages: Reported work packages + label_reporting: Reporting + label_reporting_plural: Reportings + label_repository: Repository + label_repository_remove: Remove repository + label_repository_root: Repository root + label_repository_plural: Repositories + label_request_submission: Request submission + label_required: required + label_requires: requires + label_result_plural: Results + label_revision: Revision + label_revision_id: Revision %{value} + label_revision_plural: Revisions + label_roadmap: Roadmap + label_roadmap_edit: Edit roadmap %{name} + label_roadmap_due_in: Due in %{value} + label_roadmap_no_work_packages: No work packages for this version + label_roadmap_overdue: "%{value} late" + label_role_and_permissions: Roles and permissions + label_role_new: New role + label_role_grantable: Grantable role + label_role_plural: Roles + label_role_missing_permissions: "%{role} (missing required permissions)" + label_role_search: Assign role to new members + label_scm: SCM + label_scroll_left: Scroll left + label_scroll_right: Scroll right + label_search: Search + label_search_by_name: Search by name + label_send_information: Send new credentials to the user + label_send_test_email: Send a test email + label_session: Session + label_setting_plural: Settings + label_system_settings: System settings + label_show_completed_versions: Show completed versions + label_columns: Columns + label_sort: Sort + label_sort_ascending: Sort ascending + label_sort_by: Sort by %{value} + label_sorted_by: sorted by %{value} + label_sort_descending: Sort descending + label_sort_higher: Move up + label_sort_highest: Move to top + label_sort_lower: Move down + label_sort_lowest: Move to bottom + label_spent_time: Spent time + label_start_to_end: start to end + label_start_to_start: start to start + label_statistics: Statistics + label_status: Status + label_status_plural: Statuses + label_storage_free_space: Remaining disk space + label_storage_used_space: Used disk space + label_storage_group: Storage filesystem %{identifier} + label_storage_for: Encompasses storage for + label_string: Text + label_subproject: Subproject + label_subproject_new: New subproject + label_subproject_plural: Subprojects + label_subitems: Subitems + label_subtask_plural: Subtasks + label_summary: Summary + label_system: System + label_system_storage: Storage information + label_table_of_contents: Table of contents + label_tag: Tag + label_team_planner: Team Planner + label_template: Template + label_templates: Templates + label_text: Long text + label_this_month: this month + label_this_week: this week + label_this_year: this year + label_time: Time + label_time_entry_plural: Spent time + label_time_entry_activity_plural: Spent time activities + label_title: Title + label_projects_menu: Projects + label_today: today + label_today_capitalized: Today + label_token_version: Token Version + label_today_as_start_date: Select today as start date. + label_today_as_due_date: Select today as finish date. + label_today_as_date: Select today as date. + label_top_menu: Top Menu + label_topic_plural: Topics + label_total: Total + label_type_new: New type + label_type_plural: Types + label_ui: User Interface + label_updated_time: Updated %{value} ago + label_updated_time_at: "%{author} %{age}" + label_updated_time_by: Updated by %{author} %{age} ago + label_upgrade_guides: Upgrade guides + label_used_by: Used by + label_used_by_types: Used by types + label_used_in_projects: Used in projects + label_user: User + label_user_and_permission: Users and permissions + label_user_named: User %{name} + label_user_activity_html: "%{value}'s activity" + label_user_anonymous: Anonymous + label_user_menu: User menu + label_user_new: New user + label_user_plural: Users + label_user_search: Search for user + label_user_settings: User settings + label_users_settings: Users settings + label_value_x: 'Value: %{x}' + label_version_new: New version + label_version_edit: Edit version + label_version_plural: Versions + label_version_sharing_descendants: With subprojects + label_version_sharing_hierarchy: With project hierarchy + label_version_sharing_none: Not shared + label_version_sharing_system: With all projects + label_version_sharing_tree: With project tree + label_videos: Videos + label_view_all_revisions: View all revisions + label_view_diff: View differences + label_view_revisions: View revisions + label_watched_work_packages: Watched work packages + label_what_is_this: What is this? + label_week: Week + label_widget: Widget + label_widget_new: New widget + label_wiki_content_added: Wiki page added + label_wiki_content_updated: Wiki page updated + label_wiki_toc: Table of Contents + label_wiki_toc_empty: Table of Contents is empty as no headings are present. + label_wiki_dont_show_menu_item: Do not show this wikipage in project navigation + label_wiki_edit: Wiki edit + label_wiki_edit_plural: Wiki edits + label_wiki_page_attachments: Wiki page attachments + label_wiki_page_id: Wiki page ID + label_wiki_navigation: Wiki navigation + label_wiki_page: Wiki page + label_wiki_page_plural: Wiki pages + label_wiki_show_index_page_link: Show submenu item 'Table of Contents' + label_wiki_show_menu_item: Show as menu item in project navigation + label_wiki_show_new_page_link: Show submenu item 'Create new child page' + label_wiki_start: Start page + label_work: Work + label_work_package: Work package + label_work_package_attachments: Work package attachments + label_work_package_category_new: New category + label_work_package_category_plural: Work package categories + label_work_package_comments: Work package comments + label_work_package_hierarchy: Work package hierarchy + label_work_package_new: New work package + label_work_package_edit: Edit work package %{name} + label_work_package_plural: Work packages + label_work_package_status: Work package status + label_work_package_status_new: New status + label_work_package_status_plural: Work package statuses + label_work_package_types: Work package types + label_work_package_tracking: Work package tracking + label_work_package_view_all: View all work packages + label_workflow: Workflow + label_workflow_copy: Copy workflow + label_workflow_plural: Workflows + label_workflow_summary: Summary + label_working_days_and_hours: Working days and hours + label_x_closed_work_packages_abbr: + one: 1 closed + other: "%{count} closed" + zero: 0 closed + label_x_comments: + one: 1 comment + other: "%{count} comments" + zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items + label_x_open_work_packages_abbr: + one: 1 open + other: "%{count} open" + zero: 0 open + label_x_work_packages: + one: 1 work package + other: "%{count} work packages" + zero: No work packages + label_x_projects: + one: 1 project + other: "%{count} projects" + zero: no projects + label_x_files: + one: 1 file + other: "%{count} files" + zero: no files + label_x_days: + one: 1 day + other: "%{count} days" + label_x_working_days: + one: 1 working day + other: "%{count} working days" + label_x_working_days_time_off: + one: 'Time off: 1 working day' + other: 'Time off: %{count} working days' + label_yesterday: yesterday + label_zen_mode: Zen mode + label_role_type: Type + label_member_role: Project role + label_global_role: Global role + label_not_changeable: "(not changeable)" + label_global: Global + label_seeded_from_env_warning: This record has been created through a setting environment variable. It is not editable through UI. + label_schedule_and_availability: Schedule and availability + label_working_hours: Work schedule + label_non_working_days: Availability calendar + label_non_working_days_with_count: Non-working days (%{count}) + label_non_working_days_summary: Summary + button_add_non_working_time: Time off + button_edit_non_working_time: Edit time off + label_continued_from_previous_year: continued from previous year + label_continues_into_next_year: continues into next year + label_end_date: Finish date + label_working_days: Working days + label_non_working_times_with_count: "%{year} time off (%{count})" + label_non_working_times_summary: "%{year} summary" + label_total_user_non_working_times: Personal non-working days + label_total_global_non_working_days: Global non-working days + label_total_days_off: Total days off + macro_execution_error: Error executing the macro %{macro_name} + macro_unavailable: Macro %{macro_name} cannot be displayed. + macros: + placeholder: "[Placeholder] Macro %{macro_name}" + errors: + missing_or_invalid_parameter: Missing or invalid macro parameter. + legacy_warning: + timeline: This legacy timeline macro has been removed and is no longer available. You can replace the functionality with an embedded table macro. + include_wiki_page: + removed: The macro does no longer exist. + wiki_child_pages: + errors: + page_not_found: Cannot find the wiki page '%{name}'. + create_work_package_link: + errors: + no_project_context: Calling create_work_package_link macro from outside project context. + invalid_type: No type found with name '%{type}' in project '%{project}'. + link_name: New work package + link_name_type: New %{type_name} + mail: + actions: Actions + digests: + including_mention_singular: including a mention + including_mention_plural: including %{number_mentioned} mentions + unread_notification_singular: 1 unread notification + unread_notification_plural: "%{number_unread} unread notifications" + you_have: You have + logo_alt_text: Logo + mention: + subject: "%{user_name} mentioned you in %{id} - %{subject}" + notification: + center: To notification center + see_in_center: See comment in notification center + settings: Change email settings + salutation: Hello %{user}, + salutation_full_name: Full name + work_packages: + created_at: 'Created at %{timestamp} by %{user} ' + login_to_see_all: Log in to see all notifications. + mentioned: You have been mentioned in a comment + mentioned_by: "%{user} mentioned you in a comment" + more_to_see: + one: There is 1 more work package with notifications. + other: There are %{count} more work packages with notifications. + open_in_browser: Open in browser + reason: + watched: Watched + assigned: Assigned + responsible: Accountable + mentioned: Mentioned + shared: Shared + subscribed: all + prefix: 'Received because of the notification setting: %{reason}' + date_alert_start_date: Date alert + date_alert_due_date: Date alert + reminder: Reminder + see_all: See all + updated_at: Updated at %{timestamp} by %{user} + reminder_notifications: + subject: 'Reminder: %{note}' + heading: You have a new reminder + note: 'Note: “%{note}”' + sharing: + work_packages: + allowed_actions_html: 'You have the following permissions on this work package: %{allowed_actions}. This can change depending on your project role and permissions.' + create_account: To access this work package, you will need to create and activate an account on %{instance}. + open_work_package: Open work package + subject: Work package %{id} was shared with you + enterprise_text: Share work packages with users who are not members of the project. + summary: + user: "%{user} shared a work package with you with %{role_rights} rights" + group: "%{user} shared a work package with the group %{group} you are a member of" + storages: + health: + plaintext: + storage: Storage + healthy: + summary: Good news! The status of your storage, %{storage_name}, is currently displaying as "Healthy". + error-solved-on: Solved On + recommendation: We will continue monitoring the system to ensure it remains in good health. In case of any discrepancies, we will notify you. + details: For more details or to make any necessary amendments, you can visit your storage configuration + unhealthy: + summary: The status of your storage, %{storage_name}, is currently displaying as "Error". We've detected an issue that might require your attention. + error-details: Error Details + error-message: Error Message + error-occurred-on: Occurred On + recommendation: We recommend heading over to the storage configuration page to address this issue + unsubscribe: If you would no longer like to receive these notifications, you can unsubscribe at any time. To unsubscribe, please follow the instructions on this page + email_notification_settings: Storage email notification settings + see_storage_settings: See storage settings + healthy: + subject: Storage "%{name}" is now healthy! + solved_at: solved at + summary: The problem with your %{storage_name} storage integration is now solved + unhealthy: + subject: Storage "%{name}" is unhealthy! + since: since + summary: There is a problem with your %{storage_name} storage integration + troubleshooting: + text: For more information, check file storages + link_text: troubleshooting documentation + mail_body_account_activation_request: 'A new user (%{value}) has registered. The account is pending your approval:' + mail_body_account_information: Your account information + mail_body_account_information_external: You can use your %{value} account to log in. + mail_body_backup_ready: 'Your requested backup is ready. You can download it here:' + mail_body_backup_token_reset_admin_info: The backup token for user '%{user}' has been reset. + mail_body_backup_token_reset_user_info: Your backup token has been reset. + mail_body_backup_token_info: The previous token is no longer valid. + mail_body_backup_waiting_period: The new token will be enabled in %{hours} hours. + mail_body_backup_token_warning: If this wasn't you, login to OpenProject immediately and reset it again. + mail_body_incoming_email_error: The email you sent to OpenProject could not be processed. + mail_body_incoming_email_error_in_reply_to: At %{received_at} %{from_email} wrote + mail_body_incoming_email_error_logs: Logs + mail_body_lost_password: 'To change your password, click on the following link:' + mail_password_change_not_possible: + title: Password change not possible + body: Your account at %{app_title} is connected to an external authentication provider (%{name}). + subtext: Passwords for external account cannot be changed in the application. Please use the lost password functionality of your authentication provider. + mail_body_register: 'Welcome to %{app_title}. Please activate your account by clicking on this link:' + mail_body_register_header_title: Project member invitation email + mail_body_register_user: 'Dear %{name}, ' + mail_body_register_links_html: | + Please feel free to browse our youtube channel (%{youtube_link}) where we provide a webinar (%{webinar_link}) + and “Get started” videos (%{get_started_link}) to make your first steps in OpenProject as easy as possible. +
+ If you have any further questions, consult our documentation (%{documentation_link}) or contact your administrator. + mail_body_register_closing: Your OpenProject team + mail_body_register_ending: Stay connected! Kind regards, + mail_body_reminder: "%{count} work package(s) that are assigned to you are due in the next %{days} days:" + mail_body_group_reminder: '%{count} work package(s) that are assigned to group "%{group}" are due in the next %{days} days:' + mail_body_wiki_page_added: The '%{id}' wiki page has been added by %{author}. + mail_body_wiki_page_updated: The '%{id}' wiki page has been updated by %{author}. + mail_subject_account_activation_request: "%{value} account activation request" + mail_subject_backup_ready: Your backup is ready + mail_subject_backup_token_reset: Backup token reset + mail_subject_incoming_email_error: An email you sent to OpenProject could not be processed + mail_subject_lost_password: Your %{value} password + mail_subject_register: Your %{value} account activation + mail_subject_wiki_content_added: "'%{id}' wiki page has been added" + mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" + mail_member_added_project: + subject: "%{project} - You have been added as a member" + body: + added_by: + without_message: "%{user} added you as a member to the project '%{project}'." + with_message: "%{user} added you as a member to the project '%{project}' writing:" + roles: 'You have the following roles:' + mail_member_updated_project: + subject: "%{project} - Your roles have been updated" + body: + updated_by: + without_message: "%{user} updated the roles you have in the project '%{project}'." + with_message: "%{user} updated the roles you have in the project '%{project}' writing:" + roles: 'You now have the following roles:' + mail_member_updated_global: + subject: Your global permissions have been updated + body: + updated_by: + without_message: "%{user} updated the roles you have globally." + with_message: "%{user} updated the roles you have globally writing:" + roles: 'You now have the following roles:' + mail_user_activation_limit_reached: + subject: User activation limit reached + message_html: | + A new user (%{email}) tried to create an account on an OpenProject environment that you manage (%{host}). + The user cannot activate their account since the user limit has been reached. + steps: + label: 'To allow the user to sign in you can either: ' + a: Upgrade your payment plan ([here](upgrade_url)) + b: Lock or delete an existing user ([here](users_url)) + more_actions: More functions + noscript_description: You need to activate JavaScript in order to use OpenProject! + noscript_heading: JavaScript disabled + noscript_learn_more: Learn more + notice_accessibility_mode: The accessibility mode can be enabled in your [account settings](url). + notice_account_activated: Your account has been activated. You can now log in. + notice_account_already_activated: The account has already been activated. + notice_account_invalid_token: Invalid activation token + notice_account_invalid_credentials: Invalid user or password + notice_account_invalid_credentials_or_blocked: Invalid user or password or the account is blocked due to multiple failed login attempts. If so, it will be unblocked automatically in a short time. + notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. + notice_account_new_password_forced: A new password is required. + notice_account_password_expired: Your password expired after %{days} days. Please set a new one. + notice_account_password_updated: Password was successfully updated. + notice_account_pending: Your account was created and is now pending administrator approval. + notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you. + notice_account_unknown_email: Unknown user. + notice_account_update_failed: Account setting could not be saved. Please have a look at your account page. + notice_account_updated: Account was successfully updated. + notice_account_other_session_expired: All other sessions tied to your account have been invalidated. + notice_account_wrong_password: Wrong password + notice_account_registered_and_logged_in: Welcome, your account has been activated. You are logged in now. + notice_activation_failed: The account could not be activated. + notice_auth_stage_verification_error: Could not verify stage '%{stage}'. + notice_auth_stage_wrong_stage: Expected to finish authentication stage '%{expected}', but '%{actual}' returned. + notice_auth_stage_error: Authentication stage '%{stage}' failed. + notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password. + notice_custom_options_deleted: Option '%{option_value}' and its %{num_deleted} occurrences were deleted. + notice_email_error: An error occurred while sending mail (%{value}) + notice_email_sent: An email was sent to %{value} + notice_failed_to_save_work_packages: 'Failed to save %{count} work package(s) on %{total} selected: %{ids}.' + notice_failed_to_save_members: 'Failed to save member(s): %{errors}.' + notice_deletion_scheduled: The deletion has been scheduled and is performed asynchronously. + notice_file_not_found: The page you were trying to access doesn't exist or has been removed. + notice_forced_logout: You have been automatically logged out after %{ttl_time} minutes of inactivity. + notice_internal_server_error: An error occurred on the page you were trying to access. If you continue to experience problems please contact your %{app_title} administrator for assistance. + notice_locking_conflict: Information has been updated by at least one other user in the meantime. + notice_locking_conflict_additional_information: The update(s) came from %{users}. + notice_locking_conflict_reload_page: Please reload the page, review the changes and reapply your updates. + notice_locking_conflict_warning: This page has been updated by someone else. To not lose your edits, copy them locally and reload to view the updated version. + notice_locking_conflict_danger: Could not save your changes because of conflicting modifications. To not lose your edits, copy them locally and reload to view the updated version. + notice_locking_conflict_action_button: Discard changes and reload + notice_member_added: Added %{name} to the project. + notice_members_added: Added %{number} users to the project. + notice_member_removed: Removed %{user} from project. + notice_member_deleted: "%{user} has been removed from the project and deleted." + notice_no_principals_found: No results found. + notice_bad_request: Bad Request. + notice_not_authorized: You are not authorized to access this page. + notice_not_authorized_archived_project: The project you're trying to access has been archived. + notice_requires_enterprise_token: Enterprise token missing or doesn't allow access to this page. + notice_password_confirmation_failed: The entered password is not correct. + notice_principals_found_multiple: "There are %{number} results found. \n Tab to focus the first result." + notice_principals_found_single: "There is one result. \n Tab to focus it." + notice_parent_item_not_found: Parent item not found. + notice_project_not_deleted: The project wasn't deleted. + notice_project_not_found: Project not found. + notice_smtp_address_unsafe_env_hint: SMTP address %{address} is not safe. Please add it to the whitelist using the %{env_name} environment variable. + notice_successful_connection: Successful connection. + notice_successful_create: Successful creation. + notice_successful_delete: Successful deletion. + notice_successful_cancel: Successful cancellation. + notice_successful_update: Successful update. + notice_successful_move: Successful move from %{from} to %{to}. + notice_unsuccessful_create: Creation failed. + notice_unsuccessful_create_with_reason: 'Creation failed: %{reason}' + notice_unsuccessful_update: Update failed. + notice_unsuccessful_update_with_reason: 'Update failed: %{reason}' + notice_successful_update_custom_fields_added_to_project: | + Successful update. The custom fields of the activated types are automatically activated + on the work package form. See more. + notice_to_many_principals_to_display: |- + There are too many results. + Narrow down the search by typing in the name of the new member (or group). + notice_user_missing_authentication_method: User has yet to choose a password or another way to sign in. + notice_user_invitation_resent: An invitation has been sent to %{email}. + present_access_key_value: 'Your %{key_name} is: %{value}' + notice_automatic_set_of_standard_type: Set standard type automatically. + notice_logged_out: You have been logged out. + notice_wont_delete_auth_source: The LDAP connection cannot be deleted as long as there are still users using it. + notice_project_cannot_update_custom_fields: 'You cannot update the project''s available custom fields. The project is invalid: %{errors}' + notice_attachment_migration_wiki_page: 'This page was generated automatically during the update of OpenProject. It contains all attachments previously associated with the %{container_type} "%{container_name}". + + ' + number: + format: + delimiter: "," + precision: 3 + separator: "." + human: + format: + delimiter: '' + precision: 1 + storage_units: + format: "%n %u" + units: + byte: + one: Byte + other: Bytes + gb: GB + kb: kB + mb: MB + tb: TB + onboarding: + heading_getting_started: Get an overview + text_getting_started_description: Get a quick overview of project management and team collaboration with OpenProject. You can restart this video from the help menu. + welcome: Welcome to %{app_title} + select_language: Please select your language + open_project: + common: + work_package_card_component: + menu: + label_actions: Work package actions + permission_add_work_package_comments: Add comments + permission_add_work_packages: Add work packages + permission_add_messages: Post messages + permission_add_project: Create projects + permission_add_portfolios: Create portfolios + permission_add_programs: Create programs + permission_add_work_package_attachments: Add attachments + permission_add_work_package_attachments_explanation: Allows adding attachments without Edit work packages permission + permission_add_internal_comments: Write internal comments + permission_archive_project: Archive project + permission_create_user: Create users + permission_manage_user: Edit users + permission_manage_placeholder_user: Create, edit, and delete placeholder users + permission_add_subprojects: Create subprojects + permission_add_work_package_watchers: Add watchers + permission_assign_versions: Assign versions + permission_browse_repository: Read-only access to repository (browse and checkout) + permission_change_work_package_status: Change work package status + permission_change_work_package_status_explanation: Allows changing status without Edit work packages permission + permission_comment_news: Comment news + permission_commit_access: Read/write access to repository (commit) + permission_copy_projects: Copy projects + permission_copy_projects_explanation: In template projects, this permission has a secondary function, it allows the creation of new projects derived from the template. + permission_copy_work_packages: Duplicate work packages + permission_create_backup: Create backups + permission_delete_work_package_watchers: Delete watchers + permission_delete_work_packages: Delete work packages + permission_delete_messages: Delete messages + permission_delete_own_messages: Delete own messages + permission_delete_reportings: Delete reportings + permission_delete_timelines: Delete timelines + permission_edit_work_package_comments: Moderate comments + permission_edit_work_package_comments_explanation: 'Caution: Users with this permission are able to edit anyone''s comment.' + permission_edit_work_packages: Edit work packages + permission_edit_messages: Edit messages + permission_edit_own_internal_comments: Edit own internal comments + permission_edit_own_work_package_comments: Edit own comments + permission_edit_own_messages: Edit own messages + permission_edit_own_time_entries: Edit own time logs + permission_edit_others_internal_comments: Moderate internal comments + permission_edit_others_internal_comments_explanation: 'Caution: Users with this permission are able to edit other users'' internal comments.' + permission_edit_project: Edit project + permission_edit_project_attributes: Edit project attributes + permission_edit_project_phases: Edit project phases + permission_edit_reportings: Edit reportings + permission_edit_time_entries: Edit time logs for other users + permission_edit_timelines: Edit timelines + permission_edit_wiki_pages: Edit wiki pages + permission_export_work_packages: Export work packages + permission_export_projects: Export projects + permission_invite_members_by_email: Invite members by email + permission_invite_members_by_email_explanation: 'Allows users to invite new members by email. Invited users will receive an email with a link to set their password and activate their account. Depends on the permission to manage members + + ' + permission_log_own_time: Log own time + permission_log_time: Log time for other users + permission_manage_forums: Manage forums + permission_manage_categories: Manage work package categories + permission_manage_dashboards: Manage dashboards + permission_manage_work_package_relations: Manage work package relations + permission_manage_members: Manage members + permission_manage_news: Manage news + permission_manage_project_activities: Manage project activities + permission_manage_public_queries: Manage public views + permission_manage_repository: Manage repository + permission_manage_subtasks: Manage work package hierarchies + permission_manage_versions: Manage versions + permission_manage_wiki: Manage wiki + permission_manage_own_working_times: Manage own working times + permission_manage_own_working_times_explanation: 'Allows users to manage their own working times, and personal non-working days. + + ' + permission_manage_working_times: Manage working times for all users + permission_manage_working_times_explanation: 'Allows users to manage working times for all users, including personal non-working days. + + ' + permission_move_work_packages: Move work packages + permission_save_queries: Save views + permission_search_project: Search project + permission_select_custom_fields: Select custom fields + permission_select_project_custom_fields: Select project attributes + permission_select_project_phases: Select project phases + permission_select_project_phases_explanation: Activate/Deactivate the phases in a project. Enables the user to select the life cycle appropriate for the project as inactive phases will not be visible in the project overview page nor the project list. + permission_select_project_modules: Select project modules + permission_share_work_packages: Share work packages + permission_manage_types: Select types + permission_manage_own_reminders: Create own reminders + permission_view_all_principals: View all users and groups + permission_view_all_principals_explanation: 'Allows users to see all users and groups in the system, even if they are not members of any joined projects or groups. + + ' + permission_view_project: View projects + permission_view_changesets: View repository revisions in OpenProject + permission_view_internal_comments: View internal comments + permission_view_commit_author_statistics: View commit author statistics + permission_view_dashboards: View dashboards + permission_view_work_package_watchers: View watchers list + permission_view_work_packages: View work packages + permission_view_messages: View messages + permission_view_news: View news + permission_view_members: View members + permission_view_reportings: View reportings + permission_view_shared_work_packages: View work package shares + permission_view_time_entries: View spent time + permission_view_timelines: View timelines + permission_view_user_email: View users' mail addresses + permission_view_wiki_edits: View wiki history + permission_view_wiki_pages: View wiki + permission_work_package_assigned: Become assignee/responsible + permission_work_package_assigned_explanation: Work packages can be assigned to users and groups in possession of this role in the respective project + permission_view_project_activity: View project activity + permission_view_project_attributes: View project attributes + permission_view_project_phases: View project phases + permission_save_bcf_queries: Save BCF queries + permission_manage_public_bcf_queries: Manage public BCF queries + permission_edit_attribute_help_texts: Edit attribute help texts + permission_manage_public_project_queries: Manage public project lists + permission_view_project_query: View project query + permission_edit_project_query: Edit project query + placeholders: + default: "-" + templated_hint: Automatically generated through type %{type} + portfolio: + count: + zero: 0 portfolios + one: 1 portfolio + other: "%{count} portfolios" + program: + count: + zero: 0 programs + one: 1 program + other: "%{count} programs" + project: + archive: + title: Archive project + are_you_sure: Are you sure you want to archive the project '%{name}'? + archived: Archived + count: + zero: 0 projects + one: 1 project + other: "%{count} projects" + destroy: + title: Delete project + heading: Permanently delete this project? + confirmation_message_for_subprojects_html: + zero: 'You are about to permanently delete all data relating to project %{name}. + + ' + one: 'You are about to permanently delete all data relating to project %{name} and this subproject: + + ' + other: 'You are about to permanently delete all data relating to project %{name} and these subprojects: + + ' + filters: + project_phase: 'Project phase: %{phase}' + project_phase_any: 'Project phase: Any' + project_phase_gate: 'Project phase gate: %{gate}' + identifier: + warning_one: Members of the project will have to relocate the project's repositories. + warning_two: Existing links to the project will no longer work. + title: Change the project's identifier + not_available: Project N/A + template: + copying_title: Applying template + copying: 'Your project is being created from the selected template project. You will be notified by mail as soon as the project is available. + + ' + use_template: Use template + make_template: Set as template + remove_from_templates: Remove from templates + project_module_activity: Activity + project_module_forums: Forums + project_module_work_package_tracking: Work packages + project_module_news: News + project_module_repository: Repository + project_module_wiki: Wiki + permission_header_for_project_module_work_package_tracking: Work packages and Gantt charts + query: + attribute_and_direction: "%{attribute} (%{direction})" + query_fields: + active_or_archived: Active or archived + assigned_to_role: Assignee's role + assignee_or_group: Assignee or belonging group + member_of_group: Assignee's group + name_or_identifier: Name or identifier + only_subproject_id: Only subproject + shared_with_user: Shared with users + shared_with_me: Shared with me + subproject_id: Including subproject + repositories: + at_identifier: at %{identifier} + atom_revision_feed: Atom revision feed + autofetch_information: |- + Check this if you want repositories to be updated automatically when accessing the repository module page. + This encompasses the retrieval of commits from the repository and refreshing the required disk storage. + checkout: + access: + readwrite: Read + Write + read: Read-only + none: No checkout access, you may only view the repository through this application. + access_permission: Your permissions on this repository + url: Checkout URL + base_url_text: |- + The base URL to use for generating checkout URLs (e.g., https://myserver.example.org/repos/). + Note: The base URL is only used for rewriting checkout URLs in managed repositories. Other repositories are not altered. + default_instructions: + git: |- + The data contained in this repository can be downloaded to your computer with Git. + Please consult the documentation of Git if you need more information on the checkout procedure and available clients. + subversion: |- + The data contained in this repository can be downloaded to your computer with Subversion. + Please consult the documentation of Subversion if you need more information on the checkout procedure and available clients. + enable_instructions_text: Displays checkout instructions defined below on all repository-related pages. + instructions: Checkout instructions + show_instructions: Display checkout instructions + text_instructions: This text is displayed alongside the checkout URL for guidance on how to check out the repository. + not_available: Checkout instructions are not defined for this repository. Ask your administrator to enable them for this repository in the system settings. + create_managed_delay: 'Please note: The repository is managed, it is created asynchronously on the disk and will be available shortly.' + create_successful: The repository has been registered. + delete_sucessful: The repository has been deleted. + destroy: + confirmation: If you continue, this will permanently delete the managed repository. + info: Deleting the repository is an irreversible action. + info_not_managed: 'Note: This will NOT delete the contents of this repository, as it is not managed by OpenProject.' + managed_path_note: 'The following directory will be erased: %{path}' + repository_verification_html: Enter the project's identifier %{identifier} to verify the deletion of its repository. + subtitle: Do you really want to delete the %{repository_type} of the project %{project_name}? + subtitle_not_managed_html: Do you really want to remove the linked %{repository_type} %{url} from the project %{project_name}? + title_html: Delete the %{repository_type} + title_not_managed: Remove the linked %{repository_type}? + errors: + build_failed: Unable to create the repository with the selected configuration. %{reason} + managed_delete: Unable to delete the managed repository. + managed_delete_local: 'Unable to delete the local repository on filesystem at ''%{path}'': %{error_message}' + empty_repository: The repository exists, but is empty. It does not contain any revisions yet. + exists_on_filesystem: The repository directory already exists in the filesystem. + filesystem_access_failed: 'An error occurred while accessing the repository in the filesystem: %{message}' + not_manageable: This repository vendor cannot be managed by OpenProject. + path_permission_failed: 'An error occurred trying to create the following path: %{path}. Please ensure that OpenProject may write to that folder.' + unauthorized: You're not authorized to access the repository or the credentials are invalid. + unavailable: The repository is unavailable. + exception_title: 'Cannot access the repository: %{message}' + disabled_or_unknown_type: The selected type %{type} is disabled or no longer available for the SCM vendor %{vendor}. + disabled_or_unknown_vendor: The SCM vendor %{vendor} is disabled or no longer available. + remote_call_failed: 'Calling the managed remote failed with message ''%{message}'' (Code: %{code})' + remote_invalid_response: Received an invalid response from the managed remote. + remote_save_failed: Could not save the repository with the parameters retrieved from the remote. + git: + instructions: + managed_url: This is the URL of the managed (local) Git repository. + path: Specify the path to your local Git repository ( e.g., %{example_path} ). You can also use remote repositories which are cloned to a local copy by using a value starting with http(s):// or file://. + path_encoding: 'Override Git path encoding (Default: UTF-8)' + local_title: Link existing local Git repository + local_url: Local URL + local_introduction: If you have an existing local Git repository, you can link it with OpenProject to access it from within the application. + managed_introduction: Let OpenProject create and integrate a local Git repository automatically. + managed_title: Git repository integrated into OpenProject + managed_url: Managed URL + path: Path to Git repository + path_encoding: Path encoding + go_to_revision: Go to revision + managed_remote: Managed repositories for this vendor are handled remotely. + managed_remote_note: Information on the URL and path of this repository is not available prior to its creation. + managed_url: Managed URL + settings: + automatic_managed_repos_disabled: Disable automatic creation + automatic_managed_repos: Automatic creation of managed repositories + automatic_managed_repos_text: By setting a vendor here, newly created projects will automatically receive a managed repository of this vendor. + scm_vendor: Source control management system + scm_type: Repository type + scm_types: + local: Link existing local repository + existing: Link existing repository + managed: Create new repository in OpenProject + storage: + not_available: Disk storage consumption is not available for this repository. + update_timeout: |- + Keep the last required disk space information for a repository for N minutes. + As counting the required disk space of a repository may be costly, increase this value to reduce performance impact. + oauth_application_details_html: 'The client secret value will not be accessible again after you close this window. Please copy these values into the Nextcloud OpenProject Integration settings:' + oauth_application_details_link_text: Go to settings page + setup_documentation_details: 'If you need help configuring a new file storage please check the documentation: ' + setup_documentation_details_link_text: File storages setup + show_warning_details: To use this file storage remember to activate the module and the specific storage in the project settings of each desired project. + subversion: + existing_title: Existing Subversion repository + existing_introduction: If you have an existing Subversion repository, you can link it with OpenProject to access it from within the application. + existing_url: Existing URL + instructions: + managed_url: This is the URL of the managed (local) Subversion repository. + url: |- + Enter the repository URL. This may either target a local repository (starting with %{local_proto} ), or a remote repository. + The following URL schemes are supported: + managed_title: Subversion repository integrated into OpenProject + managed_introduction: Let OpenProject create and integrate a local Subversion repository automatically. + managed_url: Managed URL + password: Repository Password + username: Repository username + truncated: Sorry, we had to truncate this directory to %{limit} files. %{truncated} entries were omitted from the list. + named_repository: "%{vendor_name} repository" + update_settings_successful: The settings have been successfully saved. + url: URL to repository + warnings: + cannot_annotate: This file cannot be annotated. + scheduling: + manual: set to Manual + automatic: set to Automatic + search_input_placeholder: Search ... + setting_allowed_link_protocols: Allowed link protocols + setting_allowed_link_protocols_text_html: Allow these protocols to be rendered as links in work package descriptions, long text fields and comments. For example, %{tel_code} or %{element_code}. Enter one protocol per line.
Protocols %{http_code}, %{https_code}, and %{mailto_code} are always allowed. + setting_capture_external_links: Capture external links + setting_capture_external_links_text: 'When enabled, all external links in formatted text will redirect through a warning page before leaving the application. This helps protect users from potentially malicious external websites. + + ' + setting_capture_external_links_require_login: Require users to be logged in + setting_capture_external_links_require_login_text: 'When enabled, users wanting to click on external links need to be logged in before being able to continue. + + ' + setting_after_first_login_redirect_url: First login redirect + setting_after_first_login_redirect_url_text_html: 'Set a path to redirect users after their first login. If empty, redirects to the home page for the onboarding tour.
Example: /my/page + + ' + setting_after_login_default_redirect_url: After login redirect + setting_after_login_default_redirect_url_example_html: 'Set a default path to redirect users after login, if no back link was provided. Redirects to home page if not set.
Example: %{example_code} + + ' + setting_apiv3_cors_title: Cross-Origin Resource Sharing (CORS) + setting_apiv3_cors_enabled: Enable CORS + setting_apiv3_cors_origins: API V3 Cross-Origin Resource Sharing (CORS) allowed origins + setting_apiv3_cors_origins_instructions_html: 'If CORS is enabled, these are the origins that are allowed to access OpenProject API.
Please check the [Documentation on the Origin header](docs_url) on how to specify the expected values. + + ' + setting_apiv3_write_readonly_attributes: Write access to read-only attributes + setting_apiv3_write_readonly_attributes_instructions: 'If enabled, the API will allow administrators to write static read-only attributes during creation, such as createdAt and author. + + ' + setting_apiv3_write_readonly_attributes_warning: 'This setting has a use-case for e.g., importing data, but allows administrators to impersonate the creation of items as other users. All creation requests are being logged however with the true author. + + ' + setting_apiv3_write_readonly_attributes_additional_html: 'For more information on attributes and supported resources, please see the [API documentation](api_documentation_link). + + ' + setting_apiv3_max_page_size: Maximum API page size + setting_apiv3_max_page_size_instructions: 'Set the maximum page size the API will respond with. It will not be possible to perform API requests that return more values on a single page. + + ' + setting_apiv3_max_page_size_warning: 'Please only change this value if you are sure why you need it. Setting to a high value will result in significant performance impacts, while a value lower than the per page options will cause errors in paginated views. + + ' + setting_apiv3_docs: Documentation + setting_apiv3_docs_enabled: Enable docs page + setting_apiv3_docs_enabled_instructions_html: 'If the docs page is enabled you can get an interactive view of the APIv3 documentation under %{link}. + + ' + setting_apiv3_docs_enabled_instructions_warning: 'Please be aware that enabling the API docs on a production system may expose sensitive information or result in accidental loss of data when not being careful. We recommend to only enable this setting for development purposes. + + ' + setting_attachment_whitelist: Attachment upload whitelist + setting_email_delivery_method: Email delivery method + setting_emails_salutation: Address user in emails with + setting_oauth_allow_remapping_of_existing_users: Allow remapping of existing users + setting_sendmail_location: Location of the sendmail executable + setting_sendmail_arguments: Arguments for sendmail + setting_smtp_enable_starttls_auto: Automatically use STARTTLS if available + setting_smtp_ssl: Use SSL connection + setting_smtp_address: SMTP server + setting_smtp_port: SMTP port + setting_smtp_authentication: SMTP authentication + setting_smtp_user_name: SMTP username + setting_smtp_password: SMTP password + setting_smtp_domain: SMTP HELO domain + setting_activity_days_default: Days displayed on project activity + setting_api_tokens_enabled: Enable API tokens + setting_api_tokens_enabled_caption: 'Decide whether users can create personal API tokens in their account settings. These tokens can be used to access the different APIs of OpenProject, such as APIv3 and MCP. + + ' + setting_app_subtitle: Application subtitle + setting_app_title: Application title + setting_organization_name: Organization name + setting_attachment_max_size: Attachment max. size + setting_show_work_package_attachments: Show attachments in the files tab by default + setting_antivirus_scan_mode: Scan mode + setting_antivirus_scan_action: Infected file action + setting_autofetch_changesets: Autofetch repository changes + setting_autologin: Autologin + setting_available_languages: Available languages + setting_bcc_recipients: Blind carbon copy recipients (bcc) + setting_brute_force_block_after_failed_logins: Block user after this number of failed login attempts + setting_brute_force_block_minutes: Time the user is blocked for + setting_cache_formatted_text: Cache formatted text + setting_use_wysiwyg_description: Select to enable CKEditor5 WYSIWYG editor for all users by default. CKEditor has limited functionality for GFM Markdown. + setting_column_options: Default work package lists columns + setting_commit_fix_keywords: Fixing keywords + setting_commit_logs_encoding: Commit messages encoding + setting_commit_logtime_activity_id: Activity for logged time + setting_commit_logtime_enabled: Enable time logging + setting_commit_ref_keywords: Referencing keywords + setting_consent_time: Consent time + setting_consent_info: Consent information text + setting_consent_required: Consent required + setting_consent_decline_mail: Consent contact mail address + setting_cross_project_work_package_relations: Allow cross-project work package relations + setting_first_week_of_year: First week in year contains + setting_date_format: Date + setting_default_language: Default language + setting_default_projects_modules: Default enabled modules for new projects + setting_default_projects_public: New projects are public by default + setting_disable_password_login: Disable password authentication + setting_diff_max_lines_displayed: Max number of diff lines displayed + setting_omniauth_direct_login_provider: Direct login SSO provider + setting_display_subprojects_work_packages: Display subprojects work packages on main projects by default + setting_duration_format: Duration format + setting_duration_format_hours_only: Hours only + setting_duration_format_days_and_hours: Days and hours + setting_duration_format_instructions: This defines how Work, Remaining work, and Time spent durations are displayed. + setting_emails_footer: Emails footer + setting_emails_header: Emails header + setting_email_login: Use email as login + setting_enabled_scm: Enabled SCM + setting_enabled_projects_columns: Columns in a projects list displayed by default + setting_feeds_enabled: Enable Feeds + setting_ical_enabled: Enable iCalendar subscriptions + setting_feeds_limit: Feed content limit + setting_file_max_size_displayed: Max size of text files displayed inline + setting_host_name: Host name + setting_collaborative_editing_hocuspocus_url: Hocuspocus server URL + setting_collaborative_editing_hocuspocus_secret: Hocuspocus server secret + setting_hours_per_day: Hours per day + setting_hours_per_day_explanation: This defines what is considered a "day" when displaying duration in days and hours (for example, if a day is 8 hours, 32 hours would be 4 days). + setting_invitation_expiration_days: Activation email expires after + setting_invitation_expiration_days_caption: Number of days after which the activation email expires. + setting_work_package_done_ratio: Progress calculation mode + setting_work_package_done_ratio_field: Work-based + setting_work_package_done_ratio_field_caption_html: "% Complete can be freely set to any value. If you optionally enter a value for Work, Remaining work will automatically be derived." + setting_work_package_done_ratio_status: Status-based + setting_work_package_done_ratio_status_caption_html: Each status has a % Complete value associated with it. Changing status will change % Complete. + setting_work_package_done_ratio_explanation_html: 'In work-based mode, % Complete can be freely set to any value. If you optionally enter a value for Work, Remaining work will automatically be derived. In status-based mode, each status has a % Complete value associated with it. Changing status will change % Complete. + + ' + setting_work_package_properties: Work package properties + setting_work_package_startdate_is_adddate: Use current date as start date for new work packages + setting_work_packages_projects_export_limit: Work packages / Projects export limit + setting_journal_aggregation_time_minutes: Aggregation period + setting_log_requesting_user: Log user login, name, and mail address for all requests + setting_login_required: Authentication required + setting_login_required_caption: When checked, all requests to the application have to be authenticated. + setting_lost_password: Enable password reset + setting_lost_password_caption: When checked, allow users to reset their own passwords. + setting_mail_from: Emission email address + setting_mail_handler_api_key: API key + setting_mail_handler_body_delimiters: Truncate emails after one of these lines + setting_mail_handler_body_delimiter_regex: Truncate emails matching this regex + setting_mail_handler_ignore_filenames: Ignored mail attachments + setting_new_project_user_role_id: Role given to a non-admin user who creates a project + setting_new_project_user_role_id_caption: 'Only roles that include the permissions to edit project attributes and to manage members are listed, so that the creator can complete the project setup. + + ' + setting_new_project_send_confirmation_email: Send notification to author when creating a new project + setting_new_project_notification_text: Notification text + setting_password_active_rules: Password requirements + setting_password_count_former_banned: Number of most recently used passwords banned for reuse + setting_password_days_valid: Number of days, after which to enforce a password change + setting_password_min_length: Minimum length + setting_per_page_options: Objects per page options + setting_percent_complete_on_status_closed: "% Complete when status is closed" + setting_percent_complete_on_status_closed_no_change: No change + setting_percent_complete_on_status_closed_no_change_caption_html: The value of % Complete will not change even when a work package is closed. + setting_percent_complete_on_status_closed_set_100p: Automatically set to 100% + setting_percent_complete_on_status_closed_set_100p_caption: A closed work package is considered complete. + setting_plain_text_mail: Plain text mail (no HTML) + setting_protocol: Protocol + setting_project_gantt_query: Project portfolio Gantt view + setting_project_gantt_query_text: You can modify the query that is used to display Gantt chart from the project overview page. + setting_security_badge_displayed: Display security badge + setting_registration_footer: Registration footer + setting_registration_footer_caption: This text is displayed in the footer of the registration page. Use the HTML editor to format the text for each selected language. + setting_repositories_automatic_managed_vendor: Automatic repository vendor type + setting_repositories_encodings: Repositories encodings + setting_repository_storage_cache_minutes: Repository disk size cache + setting_repository_checkout_display: Show checkout instructions + setting_repository_checkout_base_url: Checkout base URL + setting_repository_checkout_text: Checkout instruction text + setting_repository_log_display_limit: Maximum number of revisions displayed on file log + setting_repository_truncate_at: Maximum number of files displayed in the repository browser + setting_self_registration: Self-registration + setting_self_registration_caption: 'Choose the self-registration mechanism for users. Be careful with the setting you choose, as some options allow users to activate their own accounts to this instance. + + ' + setting_self_registration_warning: 'The user will be able to activate their own accounts. Please note that this will give them access to all public projects and their content. Please make sure that no sensitive or private data is exposed in public projects. + + ' + setting_self_registration_disabled: Disabled + setting_self_registration_disabled_caption: 'No accounts can be registered on their own. Only administrators and users with the global permission to create new users are able to create new accounts. + + ' + setting_self_registration_activation_by_email: Account activation by email + setting_self_registration_activation_by_email_caption: 'Users can register on their own and activate their account after confirming their email address. Administrators have no moderation control over the activation process. + + ' + setting_self_registration_automatic_activation: Automatic account activation + setting_self_registration_automatic_activation_caption: 'Users can register on their own. Their accounts are immediately active without further action. Administrators have no moderation control over the activation process. + + ' + setting_self_registration_manual_activation: Manual account activation + setting_self_registration_manual_activation_caption: 'Users can register on their own. Their accounts are in a pending state until an administrator or user with the global permission to create or manage users activates them. + + ' + setting_session_ttl: Session expiration time after inactivity + setting_session_ttl_hint: Value below 5 works like disabled + setting_session_ttl_enabled: Session expires + setting_start_of_week: Week starts on + setting_sys_api_enabled: Enable repository management web service + setting_sys_api_description: The repository management web service provides integration and user authorization for accessing repositories. + setting_time_format: Time + setting_total_percent_complete_mode: Calculation of % Complete hierarchy totals + setting_total_percent_complete_mode_work_weighted_average: Weighted by work + setting_total_percent_complete_mode_work_weighted_average_caption_html: The total % Complete will be weighted against the Work of each work package in the hierarchy. Work packages without Work will be ignored. + setting_total_percent_complete_mode_simple_average: Simple average + setting_total_percent_complete_mode_simple_average_caption_html: "Work is ignored and the total % Complete will be a simple average of % Complete values of work packages in the hierarchy." + setting_accessibility_mode_for_anonymous: Enable accessibility mode for anonymous users + setting_user_format: Users name format + setting_user_default_timezone: Users default time zone + setting_users_deletable_by_admins: User accounts deletable by admins + setting_users_deletable_by_self: Users allowed to delete their accounts + setting_welcome_text: Welcome block text + setting_welcome_title: Welcome block title + setting_welcome_on_homescreen: Display welcome block on homescreen + setting_work_packages_identifier_classic: Instance-wide numerical sequence (default) + setting_work_packages_identifier_classic_caption: 'Every work package gets a sequential number starting with 1 and incremented with every new one. The numbers are unique within this instance so they remain the same even if work packages are moved between projects. + + ' + setting_work_packages_identifier_semantic: Project-based semantic identifiers + setting_work_packages_identifier_semantic_caption: 'Every project has a unique identifier that is prefixed to the work package ID. If a work package moved to another project, a new identifier is generated but the old one continues to function. + + ' + setting_work_package_list_default_highlighting_mode: Default highlighting mode + setting_work_package_list_default_highlighted_attributes: Default inline highlighted attributes + setting_working_days: Working days + settings: + errors: + not_writable: The setting is not writable and can only be changed by a sysadmin. + failed_to_update: 'Setting ''%{name}'' could not be updated: %{message}' + authentication: + login: Login + registration: Registration + sso: Single Sign-On (SSO) + omniauth_direct_login_hint_html: 'If this option is active, login requests will redirect to the configured omniauth provider. The login dropdown and sign-in page will be disabled.
Note: Unless you also disable password logins, with this option enabled, users can still log in internally by visiting the %{internal_path} login page. + + ' + remapping_existing_users_hint: 'If enabled, allows any configured identity provider to login existing users based on their username, even if the user never signed in through that provider before. This can be useful when migrating the OpenProject instance to a new SSO provider, but is not recommended when using a provider that is not trusted by all users of your instance. + + ' + attachments: + whitelist_text_html: 'Define a list of valid file extensions and/or mime types for uploaded files.
Enter file extensions (e.g., %{ext_example}) or mime types (e.g., %{mime_example}).
Leave empty to allow any file type to be uploaded. Multiple values allowed (one line for each value). + + ' + show_work_package_attachments: 'Deactivating this option will hide the attachments list on the work packages files tab for new projects. The files attached in the description of a work package will still be uploaded in the internal attachments storage. + + ' + antivirus: + title: Virus scanning + clamav_ping_failed: Failed to connect the the ClamAV daemon. Double-check the configuration and try again. + remaining_quarantined_files_html: 'Virus scanning has been disbled. %{file_count} remain in quarantine. To review quarantined files, please visit this link: %{link} + + ' + remaining_scan_complete_html: 'Remaining files have been scanned. There are %{file_count} in quarantine. You are being redirected to the quarantine page. Use this page to delete or override quarantined files. + + ' + remaining_rescanned_files: 'Virus scanning has been enabled successfully. There are %{file_count} that were uploaded previously and still need to be scanned. This process has been scheduled in the background. The files will remain accessible during the scan. + + ' + actions: + delete: Delete the file + quarantine: Quarantine the file + instructions_html: 'Select the action to perform for files on which a virus has been detected:
  • %{quarantine_option}: Quarantine the file, preventing users from accessing it. Administrators can review and delete quarantined files in the administration.
  • %{delete_option}: Delete the file immediately.
+ + ' + modes: + clamav_socket_html: Enter the socket to the clamd daemon, e.g., %{example} + clamav_host_html: Enter the hostname and port to the clamd daemon separated by colon. e.g., %{example} + description_html: 'Select the mode in which the antivirus scanner integration should operate.
  • %{disabled_option}: Uploaded files are not scanned for viruses.
  • %{socket_option}: You have set up ClamAV on the same server as OpenProject and the scan daemon clamd is running in the background
  • %{host_option}: You are streaming files to an external virus scanning host.
+ + ' + brute_force_prevention: Automated user blocking + date_format: + first_date_of_week_and_year_set: 'If either options "%{day_of_week_setting_name}" or "%{first_week_setting_name}" are set, the other has to be set as well to avoid inconsistencies in the frontend. + + ' + first_week_of_year_text_html: 'Select the date of January that is contained in the first week of the year. This value together with first day of the week determines the total number of weeks in a year. For more information, please see our documentation on this topic. + + ' + experimental: + save_confirmation: Caution! Risk of data loss! Only activate experimental features if you do not mind breaking your OpenProject installation and losing all of its data. + warning_toast: Feature flags are settings that activate features that are still under development. They shall only be used for testing purposes. They shall never be activated on OpenProject installations holding important data. These features will very likely corrupt your data. Use them at your own risk. + feature_flags: Feature flags + general: General + highlighting: + mode_long: + inline: Highlight attribute(s) inline + none: No highlighting + status: Entire row by Status + type: Entire row by Type + priority: Entire row by Priority + icalendar: + enable_subscriptions_text_html: Allows users with the necessary permissions to subscribe to OpenProject calendars and access work package information via an external calendar client. Note: Please read about iCalendar subscriptions to understand potential security risks before enabling this. + language_name_being_default: "%{language_name} (default)" + notifications: + events_explanation: Governs for which event an email is sent out. Work packages are excluded from this list as the notifications for them can be configured specifically for every user. + delay_minutes_explanation: Email sending can be delayed to allow users with configured in app notification to confirm the notification within the application before a mail is sent out. Users who read a notification within the application will not receive an email for the already read notification. + other: Other + passwords: Passwords + project_attributes: + heading: Project attributes + label_for_all_projects: All projects + label_new_attribute: Project attribute + label_new_section: Section + label_edit_section: Edit title + label_section_actions: Section actions + heading_description: These project attributes appear in the overview page of each project. You can add new attributes, group them into sections and re-order them as you please. These attributes can be enabled or disabled but not re-ordered at a project level. + label_project_custom_field_actions: Project attribute actions + label_no_project_custom_fields: No project attributes defined in this section + edit: + description: Changes to this project attribute will be reflected in all projects where it is enabled. Required attributes cannot be disabled on a per-project basis. + new: + heading: New attribute + description: Changes to this project attribute will be reflected in all projects where it is enabled. Required attributes cannot be disabled on a per-project basis. + sections: + display_representation: + overview: + label: 'Project attribute shown in:' + main_area: + label: Main area + description: Add all the project attributes as individual widgets in the main section of the project overview. + side_panel: + label: Side panel + description: Add all the project attributes in a section inside the right side panel in the project overview. + project_initiation_request: + header_description: 'OpenProject can generate a step-by-step wizard to help project managers fill out a project initiation request. You can choose which project attributes should be included and create a PDF artifact as a result. + + ' + status: + submitted: "%{wizard_name} has been submitted" + submitted_description: Click the button below to go to the work package for the submission process. + submitted_button: Open submission request + not_completed: "%{wizard_name} not yet completed" + not_completed_description: Provide the necessary information by filling the attributes and get the project started. + wizard_status_button: + project_initiation_request: Open project initiation request + project_creation_wizard: Open project creation wizard + project_mandate: Open project mandate + blankslate: + title: Initiation request not enabled + description: OpenProject can generate a step-by-step wizard to help project managers fill out a project initiation request. You can choose which project attributes should be included and what to do with the output document. Enable it here to start configuring the wizard. + disable_dialog: + title: Disable project initiation request + heading: Disable this project initiation request? + confirmation_message: The initiation request wizard will no longer be available to new projects based on this template. Project managers and project owners will need to manually configure and fill out the relevant information in the Project overview. + checkbox_message: I understand that this action is not reversible + name: + artifact_name: Artifact name + artifact_name_caption: Choose the name for this artifact that your project management framework recommends. + options: + project_initiation_request: Project initiation request + project_creation_wizard: Project creation wizard + project_mandate: Project mandate + submission: + description_template: "**This work package was automatically created upon completion of the %{wizard_name} workflow.** A PDF artifact containing all submitted information has been generated and attached to this work package for reference and audit purposes. If you need to update or re-run the initiation steps, you can reopen the wizard at any time by using the link below:\n" + description: When a user submits a project initiation request, a new work package will be created with the request artifact attached as a PDF file. The settings below define the type, status and assignee for this new work package. + work_package_type: Work package type + work_package_type_caption: The work package type that should be used to store the completed artifact. + status_when_submitted: Status when submitted + status_when_submitted_caption: The status the generated work package will transition to once the request is submitted. + send_confirmation_email: Send confirmation email to the user who submitted the project initiation request + assignee: Assignee when submitted + assignee_caption_html: The user or group assigned to this project attribute will also become the assignee of the new work package. This list includes active project attributes of type User only. + confirmation_email_text: Confirmation email text + confirmation_email_default: |- + Hello, + + You submitted a project initiation request for **%{project_name}**. It is now awaiting review. + Click the link below to access the work package with your request. + work_package_comment: Work package comment + work_package_comment_caption: The assignee selected above will automatically be @mentioned in the comment. + work_package_comment_default: A project initiation request for **%{project_name}** was submitted and is awaiting review. + project_phase_definitions: + heading: Project life cycle + heading_description: Project life cycle defines the project phases that can be used for your project planning and will appear in the overview page of each project. These attributes can be enabled or disabled but not re-ordered at a project level. + label_add: Add + label_add_description: Add project phase definition + filter: + label: Search project phase + section_header: Phases + non_defined: No phases are currently defined. + phase_gates: Phase gates + new: + description: Changes to this project phase will be reflected in all projects where it is enabled. + heading: New phase + both_gate: Start and finish gate + no_gate: No gate + start_gate: Start gate + start_gate_caption: Add a gate with the start date of the phase + finish_gate: Finish gate + finish_gate_caption: Add a gate with the end date of the phase + projects: + missing_dependencies: Project module %{module} was checked which depends on %{dependencies}. You need to check these dependencies as well. + section_new_projects: Settings for new projects + section_project_overview: Settings for project lists + session: Session + user: + default_preferences: Default preferences + display_format: Display format + deletion: Deletion + working_days: + section_work_week: Work week + section_holidays_and_closures: Holidays and closures + work_packages: + work_package_identifier: Work package identifier + not_allowed_text: You do not have the necessary permissions to view this page. + activities: + enable_internal_comments: Enable internal comments + helper_text_html: 'Internal comments allow an internal team to communicate amongst themselves privately. These are only visible to selected roles that have the necessary permissions and will not be visible publicly. [Click here to learn more](docs_url) + + ' + text_formatting: + markdown: Markdown + plain: Plain text + status_active: active + status_archived: archived + status_blocked: blocked + status_invited: invited + status_locked: locked + status_registered: registered + status_deleted: deleted + support: + array: + sentence_connector: and + skip_last_comma: 'false' + text_accessibility_hint: The accessibility mode is designed for users who are blind, motorically handicaped or have a bad eyesight. For the latter focused elements are specially highlighted. Please notice, that the Backlogs module is not available in this mode. + text_access_token_hint: Access tokens allow you to grant external applications access to resources in OpenProject. + text_analyze: 'Further analyze: %{subject}' + text_are_you_sure: Are you sure? + open_link_in_a_new_tab: Open link in a new tab + text_are_you_sure_continue: Are you sure you want to continue? + text_are_you_sure_with_children: Delete work package and all child work packages? + text_are_you_sure_with_project_custom_fields: Deleting this attribute will also delete its values in all projects. Are you sure you want to do this? + text_are_you_sure_with_project_life_cycle_step: Deleting this phase will also delete its usages in all projects. Are you sure you want to do this? + text_assign_to_project: Assign to the project + text_form_configuration: 'You can customize which fields will be displayed in work package forms. You can freely group the fields to reflect the needs for your domain. + + ' + text_form_configuration_required_attribute: Attribute is marked required and thus always shown + text_caracters_maximum: "%{count} characters maximum." + text_caracters_minimum: Must be at least %{count} characters long. + text_comma_separated: Multiple values allowed (comma separated). + text_comment_wiki_page: 'Comment to wiki page: %{page}' + text_custom_field_possible_values_info: One line for each value + text_custom_field_hint_activate_per_project: 'When using custom fields: Keep in mind that custom fields need to be activated per project, too. + + ' + text_custom_field_hint_activate_per_project_and_type: 'Custom fields need to be activated per work package type and per project. + + ' + text_project_custom_field_html: 'The Enterprise edition will add these additional add-ons for projects'' custom fields:
  • Add custom fields for projects to your Project list to create a project portfolio view
+ + ' + text_custom_logo_instructions: 'The logo automatically scales to fit the header. For best results, upload a white logo on a transparent 130×47px image. You can add as much spacing inside that image as you like. + + ' + text_custom_logo_mobile_instructions: 'The logo automatically scales to fit the header. For best results, upload a white logo on a transparent 130×33px image. You can add as much spacing inside that image as you like. + + ' + text_custom_export_logo_instructions: 'This is the logo that appears in your PDF exports. It needs to be a PNG or JPEG image file. A black or colored logo on transparent or white background is recommended. + + ' + text_custom_export_cover_instructions: 'This is the image that appears in the background of a cover page in your PDF exports. It needs to be an about 800px width by 500px height sized PNG or JPEG image file. + + ' + text_custom_export_footer_instructions: 'PDF exports will include a graphical element positioned to the left of the footer. This image must be a PNG or JPEG file with approximately 200 pixels in width. + + ' + label_custom_export_font_instructions: 'Upload and manage custom TrueType (.ttf) fonts used in your PDF exports. For best results, use matching files from the same font family. If no font is provided, the default NotoSans font will be used. + + ' + label_custom_export_images_instructions: 'Upload and manage custom image files used in your PDF exports. + + ' + text_custom_export_font_regular_instructions: 'This is the font file for regular text. It needs to be in TTF format and is required. + + ' + text_custom_export_font_bold_instructions: 'This is the font file for bold text. It needs to be in TTF format. + + ' + text_custom_export_font_italic_instructions: 'This is the font file for italic text. It needs to be in TTF format. + + ' + text_custom_export_font_bold_italic_instructions: 'This is the font file for bold and italic text. It needs to be in TTF format. + + ' + text_custom_favicon_instructions: 'This is the tiny icon that appears in your browser window/tab next to the page''s title. It needs to be a squared 32 by 32 pixels sized PNG image file with a transparent background. + + ' + text_custom_touch_icon_instructions: 'This is the icon that appears in your mobile or tablet when you place a bookmark on your homescreen. It needs to be a squared 180 by 180 pixels sized PNG image file. Please make sure the image''s background is not transparent otherwise it will look bad on iOS. + + ' + text_database_allows_tsv: Database allows TSVector (optional) + text_default_administrator_account_changed: Default administrator account changed + text_default_encoding: 'Default: UTF-8' + text_destroy: Delete + text_destroy_with_associated: 'There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:' + text_destroy_what_to_do: What do you want to do? + text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." + text_email_delivery_not_configured: |- + Email delivery is not configured, and notifications are disabled. + Configure your SMTP server to enable them. + text_enumeration_category_reassign_to: 'Reassign them to this value:' + text_enumeration_destroy_question: "%{count} objects are assigned to this value." + text_file_repository_writable: Attachments directory writable + text_git_repo_example: a bare and local repository (e.g. /gitrepo, c:\gitrepo) + text_hint_date_format: Enter a date in the form of YYYY-MM-DD. Other formats may be changed to an unwanted date. + text_hint_disable_with_0: 'Note: Disable with 0' + text_hours_between: Between %{min} and %{max} hours. + text_work_package_added: Work package %{id} has been reported by %{author}. + text_work_package_category_destroy_assignments: Remove category assignments + text_work_package_category_destroy_question: Some work packages (%{count}) are assigned to this category. What do you want to do? + text_work_package_category_reassign_to: Reassign work packages to this category + text_work_package_updated: Work package %{id} has been updated by %{author}. + text_work_package_watcher_added: You have been added as a watcher to Work package %{id} by %{watcher_changer}. + text_work_package_watcher_removed: You have been removed from watchers of Work package %{id} by %{watcher_changer}. + text_work_packages_destroy_confirmation: Are you sure you want to delete the selected work package(s)? + text_work_packages_ref_in_commit_messages: Referencing and fixing work packages in commit messages + text_journal_added: "%{label} %{value} added" + text_journal_attachment_added: "%{label} %{value} added as attachment" + text_journal_attachment_deleted: "%{label} %{old} removed as attachment" + text_journal_changed_plain: "%{label} changed from %{old} %{linebreak}to %{new}" + text_journal_changed_no_detail: "%{label} updated" + text_journal_changed_with_diff: "%{label} changed (%{link})" + text_journal_deleted: "%{label} deleted (%{old})" + text_journal_deleted_subproject: "%{label} %{old}" + text_journal_deleted_with_diff: "%{label} deleted (%{link})" + text_journal_file_link_added: "%{label} link to %{value} (%{storage}) added" + text_journal_file_link_deleted: "%{label} link to %{old} (%{storage}) removed" + text_journal_of: "%{label} %{value}" + text_journal_set_to: "%{label} set to %{value}" + text_journal_set_with_diff: "%{label} set (%{link})" + text_journal_label_value: "%{label} %{value}" + text_latest_note: 'The latest comment is: %{note}' + text_length_between: Length between %{min} and %{max} characters. + text_line_separated: Multiple values allowed (one line for each value). + text_load_default_configuration: Load the default configuration + text_no_roles_defined: There are no roles defined. + text_no_access_tokens_configurable: There are no access tokens which can be configured. + text_no_configuration_data: |- + Roles, types, work package statuses and workflow have not been configured yet. + It is highly recommended to load the default configuration. You will be able to modify it once loaded. + text_no_notes: There are no comments available for this work package. + text_notice_too_many_values_are_inperformant: 'Note: Displaying more than 100 items per page can increase the page load time.' + text_notice_security_badge_displayed_html: 'Note: if enabled, this will display a badge with your installation status in the %{information_panel_label} administration panel, and on the home page. It is displayed to administrators only.
The badge will check your current OpenProject version against the official OpenProject release database to alert you of any updates or known vulnerabilities. For more information on what the check provides, what data is needed to provide available updates, and how to disable this check, please visit the configuration documentation. + + ' + text_own_membership_delete_confirmation: |- + You are about to remove some or all of your permissions and may no longer be able to edit this project after that. + Are you sure you want to continue? + text_permanent_delete_confirmation_checkbox_label: I understand that this deletion cannot be reversed + text_permanent_remove_confirmation_checkbox_label: I understand that this removal cannot be reversed + text_plugin_assets_writable: Plugin assets directory writable + text_powered_by: Powered by %{link} + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed, must start with a lower case letter. + text_project_identifier_description: The project identifier is prepended to all work package IDs. If the identifier is "PROJ" for example, the work package identifier will be "PROJ-12" or "PROJ-246". + text_project_identifier_url_description: The project identifier is included in the URL of the project. + text_project_identifier_handle_format: Must start with a letter and contain only uppercase letters, numbers, and underscores (max 10 characters). + text_project_identifier_format: Must start with a lowercase letter. Only lowercase letters (a-z), numbers, dashes and underscores are allowed. + text_reassign: 'Reassign to work package:' + text_regexp_multiline: The regex is applied in a multi-line mode. e.g., ^---\s+ + text_rename_wiki_page: Rename wiki page + text_repository_usernames_mapping: |- + Select or update the OpenProject user mapped to each username found in the repository log. + Users with the same OpenProject and repository username or email are automatically mapped. + text_status_changed_by_changeset: Applied in changeset %{value}. + text_table_difference_description: In this table the single %{entries} are shown. You can view the difference between any two entries by first selecting the according checkboxes in the table. When clicking on the button below the table the differences are shown. + text_time_logged_by_changeset: Applied in changeset %{value}. + text_tip_work_package_begin_day: work package beginning this day + text_tip_work_package_begin_end_day: work package beginning and ending this day + text_tip_work_package_end_day: work package ending this day + text_type_no_workflow: No workflow defined for this type + text_unallowed_characters: Unallowed characters + text_user_invited: The user has been invited and is pending registration. + text_user_wrote: "%{value} wrote:" + text_wrote: wrote + text_warn_on_leaving_unsaved: The work package contains unsaved text that will be lost if you leave this page. + text_what_did_you_change_click_to_add_comment: What did you change? Click to add comment + text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content? + text_wiki_page_destroy_children: Delete child pages and all their descendants + text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? + text_wiki_page_nullify_children: Keep child pages as root pages + text_wiki_page_reassign_children: Reassign child pages to this parent page + text_workflow_edit: Select a role and a type to edit the workflow + text_zoom_in: Zoom in + text_zoom_out: Zoom out + text_setup_mail_configuration: Configure your email provider + help_texts: + views: + project: "%{plural} are always attached to a project. You can only select projects here where the %{plural} module is active. After creating a %{singular} you can add work packages from other projects to it.\n" + public: Publish this view, allowing other users to access your view. Users with the 'Manage public views' permission can modify or remove public query. This does not affect the visibility of work package results in that view and depending on their permissions, users may see different results. + favoured: Mark this view as favourite and add to the saved views sidebar on the left. + time: + am: am + formats: + default: "%m/%d/%Y %I:%M %p" + long: "%B %d, %Y %H:%M" + short: "%d %b %H:%M" + time: "%I:%M %p" + pm: pm + timeframe: + show: Show timeframe + end: to + start: from + title_remove_and_delete_user: Remove the invited user from the project and delete him/her. + title_enterprise_upgrade: Upgrade to unlock more users. + tooltip_user_default_timezone: 'The default time zone for new users. Can be changed in a user''s settings. + + ' + tooltip_resend_invitation: 'Sends another invitation email with a fresh token in case the old one expired or the user did not get the original email. Can also be used for active users to choose a new authentication method. When used with active users their status will be changed to ''invited''. + + ' + tooltip: + setting_email_login: 'If enabled a user will be unable to chose a login during registration. Instead their given email address will serve as the login. An administrator may still change the login separately. + + ' + queries: + apply_filter: Apply preconfigured filter + configure_view: + heading: Configure view + columns: + input_label: Add columns + input_placeholder: Select a column + drag_area_label: Manage and reorder columns + sort_by: + automatic: + heading: Automatic + description: Order the %{plural} by one or more sorting criteria. You will lose the previous sorting. + top_menu: + additional_resources: Additional resources + getting_started: Getting started + help_and_support: Help and support + total_progress: Total progress + user: + all: all + active: active + activate: Activate + activate_and_reset_failed_logins: Activate and reset failed logins + authentication_provider: Authentication Provider + identity_url_text: The internal unique identifier provided by the authentication provider. + authentication_settings_disabled_due_to_external_authentication: 'This user authenticates via an external authentication provider, so there is no password in OpenProject to be changed. + + ' + authorization_rejected: You are not allowed to sign in. + assign_random_password: Assign random password (sent to user via email) + blocked: locked temporarily + blocked_num_failed_logins: + one: locked temporarily (one failed login attempt) + other: locked temporarily (%{count} failed login attempts) + confirm_status_change: You are about to change the status of '%{name}'. Are you sure you want to continue? + deleted: Deleted user + error_status_change_self: You cannot change your own user status. + error_admin_change_on_non_admin: Only administrators can change the status of administrator users. + error_status_change_failed: 'Changing the user status failed due to the following errors: %{errors}' + invite: Invite user via email + invited: invited + lock: Lock permanently + locked: locked permanently + no_login: This user authenticates through login by password. Since it is disabled, they cannot log in. + password_change_unsupported: Change of password is not supported. + registered: registered + reset_failed_logins: Reset failed logins + status_user_and_brute_force: "%{user} and %{brute_force}" + status_change: Status change + text_change_disabled_for_provider_login: The name and email is set by your login provider and can thus not be changed. + unlock: Unlock + unlock_and_reset_failed_logins: Unlock and reset failed logins + error_cannot_delete_user: User cannot be deleted + version_status_closed: closed + version_status_locked: locked + version_status_open: open + note: Note + note_password_login_disabled_link: Password login has been disabled through a [configuration setting](configuration_url). + warning: Warning + warning_attachments_not_saved: "%{count} file(s) could not be saved." + warning_imminent_user_limit_html: 'You invited more users than are supported by your current plan. Invited users may not be able to join your OpenProject environment. Please [upgrade your plan](upgrade_url) or block existing users in order to allow invited and registered users to join. + + ' + warning_registration_token_expired: | + The activation email has expired. We sent you a new one to %{email}. + Please click the link inside of it to activate your account. + warning_user_limit_reached: 'Adding additional users will exceed the current limit. Please contact an administrator to increase the user limit to ensure external users are able to access this instance. + + ' + warning_user_limit_reached_admin_html: 'Adding additional users will exceed the current limit. Please [upgrade your plan](upgrade_url) to be able to ensure external users are able to access this instance. + + ' + warning_user_limit_reached_instructions: 'You reached your user limit (%{current}/%{max} active users). Please contact sales@openproject.com to upgrade your Enterprise edition plan and add additional users. + + ' + warning_protocol_mismatch_html: '' + warning_bar: + https_mismatch: + title: HTTPS mode setup mismatch + text_html: 'Your application is running with HTTPS mode set to %{set_protocol}, but the request is an %{actual_protocol} request. This will result in errors! You will need to set the following configuration value: %{setting_value}. Please see the installation documentation on how to set this configuration. + + ' + hostname_mismatch: + title: Hostname setting mismatch + text_html: 'Your application is running with its host name setting set to %{set_hostname}, but the request is a %{actual_hostname} hostname. This will result in errors! Go to System settings and change the "Host name" setting to correct this. + + ' + menu_item: Menu item + menu_item_setting: Visibility + wiki_menu_item_for: Menu item for wikipage "%{title}" + wiki_menu_item_setting: Visibility + wiki_menu_item_new_main_item_explanation: 'You are deleting the only main wiki menu item. You now have to choose a wiki page for which a new main item will be generated. To delete the wiki the wiki module can be deactivated by project administrators. + + ' + wiki_menu_item_delete_not_permitted: The wiki menu item of the only wiki page cannot be deleted. + work_package: + updated_automatically_by_child_changes: '_Updated automatically by changing values within child work package %{child}_ + + ' + destroy: + info: Deleting the work package is an irreversible action. + title: Delete the work package + progress: + label_note: 'Note:' + modal: + work_based_help_text: Each field is automatically calculated from the two others when possible. + status_based_help_text: "% Complete is set by work package status." + migration_warning_text: In work-based progress calculation mode, % Complete cannot be manually set and is tied to Work. The existing value has been kept but cannot be edited. Please input Work first. + derivation_hints: + done_ratio: + cleared_because_remaining_work_is_empty: Cleared because Remaining work is empty. + cleared_because_work_is_0h: Cleared because Work is 0h. + derived: Derived from Work and Remaining work. + estimated_hours: + cleared_because_remaining_work_is_empty: Cleared because Remaining work is empty. + derived: Derived from Remaining work and % Complete. + same_as_remaining_work: Set to same value as Remaining work. + remaining_hours: + cleared_because_work_is_empty: Cleared because Work is empty. + cleared_because_percent_complete_is_empty: Cleared because % Complete is empty. + decreased_by_delta_like_work: Decreased by %{delta}, matching the reduction in Work. + derived: Derived from Work and % Complete. + increased_by_delta_like_work: Increased by %{delta}, matching the increase in Work. + same_as_work: Set to same value as Work. + permissions: + comment: Comment + comment_verb: comment + comment_description: Can view and comment this work package. + edit: Edit + edit_verb: edit + edit_description: Can view, comment and edit this work package. + view: View + view_verb: view + view_description: Can view this work package. + reminders: + label_remind_at: Date + note_placeholder: Why are you setting this reminder? + create_success_message_html: Reminder set successfully. You will receive a notification for this work package %{reminder_time}. + success_update_message: Reminder updated successfully. + success_deletion_message: Reminder deleted successfully. + sharing: + count: + zero: 0 users + one: 1 user + other: "%{count} users" + filter: + project_member: Project member + not_project_member: Not project member + project_group: Project group + not_project_group: Not project group + user: User + group: Group + role: Role + type: Type + denied: You don't have permissions to share %{entities}. + label_search: Search for users to invite + label_search_placeholder: Search by user or email address + label_toggle_all: Toggle all shares + remove: Remove + share: Share + text_empty_search_description: There are no users with the current filter criteria. + text_empty_search_header: We couldn't find any matching results. + text_empty_state_description: The %{entity} has not been shared with anyone yet. + text_empty_state_header: Not shared + text_user_limit_reached: Adding additional users will exceed the current limit. Please contact an administrator to increase the user limit to ensure external users are able to access this %{entity}. + text_user_limit_reached_admins: Adding additional users will exceed the current limit. Please upgrade your plan to be able to add more users. + warning_user_limit_reached: 'Adding additional users will exceed the current limit. Please contact an administrator to increase the user limit to ensure external users are able to access this %{entity}. + + ' + warning_user_limit_reached_admin_html: 'Adding additional users will exceed the current limit. Please [upgrade your plan](upgrade_url) to be able to ensure external users are able to access this %{entity}. + + ' + warning_no_selected_user: Please select users to share this %{entity} with + warning_locked_user: The user %{user} is locked and cannot be shared with + user_details: + locked: Locked user + invited: 'Invite sent. ' + resend_invite: Resend. + invite_resent: Invite has been resent + not_project_member: Not a project member + project_group: Group members might have additional privileges (as project members) + not_project_group: Group (shared with all members) + additional_privileges_project: Might have additional privileges (as project member) + additional_privileges_group: Might have additional privileges (as group member) + additional_privileges_project_or_group: Might have additional privileges (as project or group member) + project_queries: + publishing_denied: You do not have permission to make project lists public. + access_warning: Users will only see the projects they have access to. Sharing project lists does not impact individual project permissions. + user_details: + owner: List owner + can_view_because_public: Can already view because list is shared with everyone + can_manage_public_lists: Can edit due to global permissions + public_flag: + label: Share with everyone at %{instance_name} + caption: Everyone can view this project list. Those with global edit permissions can modify it. + blank_state: + public: + header: Shared with everyone + description: Everyone can view this project list. You can also add individual users with extra permissions. + private: + header: 'Not shared: Private' + description: This project list has not been shared with anyone yet. Only you can access this list. + permissions: + view: View + view_description: Can view this project list. + edit: Edit + edit_description: Can view, share and edit this project list. + upsell: + message: Sharing project lists with individual users is an enterprise add-on. + working_days: + info: 'Days that are not selected are skipped when scheduling work packages and project life cycles (and not included in the day count). These can be overridden at a work-package level. + + ' + instance_wide_info: 'Dates added to the list below are considered non-working and skipped when scheduling work packages. + + ' + change_button: Change working days + warning: 'Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance. + + ' + journal_note: + changed: _**Working days** changed (%{changes})._ + days: + working: "%{day} is now working" + non_working: "%{day} is now non-working" + dates: + working: "%{date} is now working" + non_working: "%{date} is now non-working" + nothing_to_preview: Nothing to preview + api_v3: + attributes: + property: Property + errors: + code_400: 'Bad request: %{message}' + code_401: You need to be authenticated to access this resource. + code_401_wrong_credentials: You did not provide the correct credentials. + code_403: You are not authorized to access this resource. + code_404: The requested resource could not be found. + code_409: Could not update the resource because of conflicting modifications. + code_429: Too many requests. Please try again later. + code_500: An internal error has occurred. + code_500_outbound_request_failure: An outbound request to another resource has failed with status code %{status_code}. + code_500_missing_enterprise_token: The request can not be handled due to invalid or missing Enterprise token. + bad_request: + emoji_reactions_activity_type_not_supported: This activity type does not support emoji reactions. + invalid_link: The link under key '%{key}' is not valid. + links_not_an_object: _links must be a JSON object. + conflict: + multiple_reminders_not_allowed: You can only set one reminder at a time for a work package. Please delete or update the existing reminder. + not_found: + work_package: The work package you are looking for cannot be found or has been deleted. + reminder: The reminder you are looking for cannot be found or has been deleted. + expected: + date: YYYY-MM-DD (ISO 8601 date only) + datetime: YYYY-MM-DDThh:mm:ss[.lll][+hh:mm] (any compatible ISO 8601 datetime) + duration: ISO 8601 duration + invalid_content_type: Expected CONTENT-TYPE to be '%{content_type}' but got '%{actual}'. + invalid_format: 'Invalid format for property ''%{property}'': Expected format like ''%{expected_format}'', but got ''%{actual}''.' + invalid_json: The request could not be parsed as JSON. + invalid_relation: The relation is invalid. + invalid_resource: For property '%{property}' a link like '%{expected}' is expected, but got '%{actual}'. + invalid_signal: + embed: The requested embedding of %{invalid} is not supported. Supported embeddings are %{supported}. + select: The requested select of %{invalid} is not supported. Supported selects are %{supported}. + invalid_user_status_transition: The current user account status does not allow this operation. + missing_content_type: not specified + missing_property: Missing property '%{property}'. + missing_request_body: There was no request body. + missing_or_malformed_parameter: The query parameter '%{parameter}' is missing or malformed. + multipart_body_error: The request body did not contain the expected multipart parts. + multiple_errors: Multiple field constraints have been violated. + unable_to_create_attachment: The attachment could not be created + unable_to_create_attachment_permissions: The attachment could not be saved due to lacking file system permissions + user: + name_readonly: The name attribute is read-only. Changes can be written through the attributes firstname and lastname. + render: + context_not_parsable: The context provided is not a link to a resource. + unsupported_context: The resource given is not supported as context. + context_object_not_found: Cannot find the resource given as the context. + validation: + due_date: Finish date cannot be set on parent work packages. + invalid_user_assigned_to_work_package: The chosen user is not allowed to be '%{property}' for this work package. + start_date: Start date cannot be set on parent work packages. + eprops: + invalid_gzip: 'is invalid gzip: %{message}' + invalid_json: 'is invalid json: %{message}' + resources: + schema: Schema + undisclosed: + parent: Undisclosed - The parent is invisible because of lacking permissions. + project: Undisclosed - The project is invisible because of lacking permissions. + ancestor: Undisclosed - The ancestor is invisible because of lacking permissions. + definingProject: Undisclosed - The project is invisible because of lacking permissions. + definingWorkspace: Undisclosed - The workspace is invisible because of lacking permissions. + doorkeeper: + pre_authorization: + status: Pre-authorization + auth_url: Auth URL + access_token_url: Access token URL + errors: + messages: + invalid_request: + unknown: The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed. + missing_param: 'Missing required parameter: %{value}.' + request_not_authorized: Request need to be authorized. Required parameter for authorizing request is missing or invalid. + invalid_redirect_uri: The requested redirect uri is malformed or doesn't match client redirect URI. + unauthorized_client: The client is not authorized to perform this request using this method. + access_denied: The resource owner or authorization server denied the request. + invalid_scope: The requested scope is invalid, unknown, or malformed. + invalid_code_challenge_method: The code challenge method must be plain or S256. + server_error: The authorization server encountered an unexpected condition which prevented it from fulfilling the request. + temporarily_unavailable: The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server. + credential_flow_not_configured: Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured. + resource_owner_authenticator_not_configured: Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfigured. + admin_authenticator_not_configured: Access to admin panel is forbidden due to Doorkeeper.configure.admin_authenticator being unconfigured. + unsupported_response_type: The authorization server does not support this response type. + unsupported_response_mode: The authorization server does not support this response mode. + invalid_client: Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method. + invalid_grant: The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. + unsupported_grant_type: The authorization grant type is not supported by the authorization server. + invalid_token: + revoked: The access token was revoked + expired: The access token expired + unknown: The access token is invalid + revoke: + unauthorized: You are not authorized to revoke this token. + forbidden_token: + missing_scope: Access to this resource requires scope "%{oauth_scopes}". + unsupported_browser: + title: Your browser is outdated and unsupported. + message: You may run into errors and degraded experience on this page. + update_message: Please update your browser. + close_warning: Ignore this warning. + oauth: + application: + builtin: Built-in instance application + confidential: Confidential + singular: OAuth application + scopes: Scopes + client_credentials: Client credentials + plural: OAuth applications + named: OAuth application '%{name}' + new: New OAuth application + non_confidential: Non confidential + default_scopes: "(Default scopes)" + instructions: + enabled: Enable this application, allowing users to perform authorization grants with it. + name: The name of your application. This will be displayed to other users upon authorization. + redirect_uri_html: 'The allowed URLs authorized users can be redirected to. One entry per line.
If you''re registering a desktop application, use the following URL. + + ' + confidential: Check if the application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are assumed non-confidential. + scopes: Check the scopes you want the application to grant access to. If no scope is checked, api_v3 is assumed. + client_credential_user_id: Optional user ID to impersonate when clients use this application. Leave empty to allow public access only + register_intro: If you are developing an OAuth API client application for OpenProject, you can register it using this form for all users to use. + default_scopes: '' + header: + builtin_applications: Built-in OAuth applications + other_applications: Other OAuth applications + empty_application_lists: No OAuth applications have been registered. + client_id: Client ID + client_secret_notice: 'This is the only time we can print the client secret, please note it down and keep it secure. It should be treated as a password and cannot be retrieved by OpenProject at a later time. + + ' + authorization_dialog: + authorize: Authorize + cancel: Cancel and deny authorization. + prompt_html: Authorize %{application_name} to use your account %{login}? + title: Authorize %{application_name} + wants_to_access_html: 'This application requests access to your OpenProject account.
It has requested the following permissions: + + ' + scopes: + api_v3: Full API v3 access + api_v3_text: Application will receive full read & write access to the OpenProject API v3 to perform actions on your behalf. + grants: + created_date: Approved on + scopes: Permissions + successful_application_revocation: Revocation of application %{application_name} successful. + none_given: No OAuth applications have been granted access to your user account. + flows: + authorization_code: Authorization code flow + client_credentials: Client credentials flow + client_credentials: User used for Client credentials + client_credentials_impersonation_set_to: Client credentials user set to + client_credentials_impersonation_warning: 'Note: Clients using the ''Client credentials'' flow in this application will have the rights of this user' + client_credentials_impersonation_html: 'By default, OpenProject provides OAuth 2.0 authorization via %{authorization_code_flow_link}. You can optionally enable %{client_credentials_flow_link}, but you must provide a user on whose behalf requests will be performed. + + ' + confirm_revoke_my_application: + one: Do you really want to remove this application? This will revoke one token active for it. + other: Do you really want to remove this application? This will revoke %{count} tokens active for it. + authorization_error: An authorization error has occurred. + my_registered_applications: Registered OAuth applications + oauth_client: + urn_connection_status: + connected: Connected + error: Error + failed_authorization: Authorization failed + not_connected: Not connected + labels: + label_oauth_integration: OAuth2 integration + label_redirect_uri: Redirect URI + label_request_token: Request token + label_refresh_token: Refresh token + errors: + oauth_authorization_code_grant_had_errors: OAuth2 Authorization grant unsuccessful + oauth_reported: OAuth2 provider reported + oauth_returned_error: OAuth2 returned an error + oauth_returned_json_error: OAuth2 returned a JSON error + oauth_returned_http_error: OAuth2 returned a network error + oauth_returned_standard_error: OAuth2 returned an internal error + wrong_token_type_returned: OAuth2 returned a wrong type of token, expecting AccessToken::Bearer + oauth_issue_contact_admin: OAuth2 reported an error. Please contact your system administrator. + oauth_client_not_found: OAuth2 client not found in 'callback' endpoint (redirect_uri). + refresh_token_called_without_existing_token: 'Internal error: Called refresh_token without a previously existing token. + + ' + refresh_token_updated_failed: Error during update of OAuthClientToken + oauth_client_not_found_explanation: 'This error appears after you have updated the client_id and client_secret in OpenProject, but haven''t updated the ''Return URI'' field in the OAuth2 provider. + + ' + oauth_code_not_present: OAuth2 'code' not found in 'callback' endpoint (redirect_uri). + oauth_code_not_present_explanation: 'This error appears if you have selected the wrong response_type in the OAuth2 provider. Response_type should be ''code'' or similar. + + ' + oauth_state_not_present: OAuth2 'state' not found in 'callback' endpoint (redirect_uri). + oauth_state_not_present_explanation: 'The ''state'' is used to indicate to OpenProject where to continue after a successful OAuth2 authorization. A missing ''state'' is an internal error that may appear during setup. Please contact your system administrator. + + ' + rack_oauth2: + client_secret_invalid: Client secret is invalid (client_secret_invalid) + invalid_request: 'OAuth2 Authorization Server responded with ''invalid_request''. This error appears if you try to authorize multiple times or in case of technical issues. + + ' + invalid_response: OAuth2 Authorization Server provided an invalid response (invalid_response) + invalid_grant: The OAuth2 Authorization Server asks you to reauthorize (invalid_grant). + invalid_client: The OAuth2 Authorization Server doesn't recognize OpenProject (invalid_client). + unauthorized_client: The OAuth2 Authorization Server rejects the grant type (unauthorized_client) + unsupported_grant_type: The OAuth2 Authorization Server asks you to reauthorize (unsupported_grant_type). + invalid_scope: You are not allowed to access the requested resource (invalid_scope). + http: + request: + failed_authorization: The server side request failed authorizing itself. + missing_authorization: The server side request failed due to missing authorization information. + response: + unexpected: Unexpected response received. + you: you + link: link + plugin_openproject_auth_plugins: + name: OpenProject Auth Plugins + description: Integration of OmniAuth strategy providers for authentication in OpenProject. + plugin_openproject_auth_saml: + name: OmniAuth SAML / Single-Sign On + description: Adds the OmniAuth SAML provider to OpenProject + enterprise_plans: + legacy_enterprise: Enterprise Plan + token: + hashed_token: + display_value_placeholder: "***" + external_link_warning: + title: Leaving OpenProject + warning_message: You are about to leave OpenProject and visit an external website. Please be aware that external websites are not under our control and may have different privacy and security policies. + continue_message: Are you sure you want to proceed to the following external link? + continue_button: Continue to external website diff --git a/config/locales/crowdin/id.yml b/config/locales/crowdin/id.yml index 3bbb9fdfb45..c0008a93da7 100644 --- a/config/locales/crowdin/id.yml +++ b/config/locales/crowdin/id.yml @@ -1320,6 +1320,37 @@ id: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Project enable_all: Enable for all projects @@ -1418,8 +1449,8 @@ id: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2396,7 +2427,7 @@ id: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Kelompok tak bernama tidak dibolehkan. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4367,6 +4398,10 @@ id: one: 1 comment other: "%{count} komentar" zero: tak ada komentar + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} terbuka" @@ -4694,13 +4729,6 @@ id: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Tambah Paket-Penugasan permission_add_messages: Kirim pesan diff --git a/config/locales/crowdin/it.yml b/config/locales/crowdin/it.yml index 97185bea7a5..ecf09b6b40d 100644 --- a/config/locales/crowdin/it.yml +++ b/config/locales/crowdin/it.yml @@ -1337,6 +1337,37 @@ it: edit: form_configuration: tab: Configurazione del modulo + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Progetti enable_all: Abilita per tutti i progetti @@ -1437,8 +1468,8 @@ it: bulk_delete_dialog: title: Elimina %{count} macro-attività heading: Eliminare definitivamente queste %{count} macro-attività? - description: 'Le seguenti macro-attività, inclusi gli elementi figli e tutti i dati associati, verranno eliminate definitivamente:' - description_with_children: 'Le seguenti macro-attività, incluse le macro-attività figlie e tutti i dati associati, verranno eliminate definitivamente:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: Confermo che tutte le macro-attività selezionate e le relative macro-attività figlie verranno eliminate definitivamente. cross_project_warning: 'Queste macro-attività comprendono più progetti: %{projects}' children_label: 'Verranno inoltre cancellati i seguenti figli:' @@ -2412,7 +2443,7 @@ it: attribute_unknown_name: 'Attributo di macro-attività non valido utilizzato: %{attribute}' duplicate_group: Il nome di gruppo "%{group}" è usato più di una volta. I nomi dei gruppi devono essere univoci. query_invalid: 'La query incorporata "%{group}" non è valida: %{details}' - group_without_name: Non sono ammessi gruppi senza nome. + group_without_name: Group name can't be blank. patterns: invalid_tokens: Uno o più attributi all'interno del campo non sono validi. Correggi gli attributi prima di salvare. user: @@ -4420,6 +4451,10 @@ it: one: 1 commento other: "%{count} commenti" zero: nessun commento + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 aperto other: "%{count} aperti" @@ -4752,13 +4787,6 @@ it: work_package_card_component: menu: label_actions: Azioni sulla macro-attività - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Aggiungi commenti permission_add_work_packages: Aggiungere macro-attività (work package) permission_add_messages: Postare messaggi diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml index 413cb643213..560d5f2829d 100644 --- a/config/locales/crowdin/ja.yml +++ b/config/locales/crowdin/ja.yml @@ -1322,6 +1322,37 @@ ja: edit: form_configuration: tab: フォームの設定 + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: プロジェクト enable_all: すべてのプロジェクトで有効 @@ -1420,8 +1451,8 @@ ja: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2395,7 +2426,7 @@ ja: attribute_unknown_name: '無効なワークパッケージの属性が使用されています: %{attribute}' duplicate_group: グループ名 '%{group}' は複数回使用されています。グループ名は一意でなければなりません。 query_invalid: 埋め込みクエリ '%{group}' は無効です: %{details} - group_without_name: 名前のないグループは指定できません。 + group_without_name: Group name can't be blank. patterns: invalid_tokens: フィールド内の1つ以上の属性が無効です。保存する前に属性を修正してください。 user: @@ -4359,6 +4390,10 @@ ja: one: 1件のコメント other: "%{count}件のコメント" zero: コメントがありません + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1件未完了 other: "%{count}件未完了" @@ -4684,13 +4719,6 @@ ja: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: コメントの追加 permission_add_work_packages: ワークパッケージの追加 permission_add_messages: メッセージの投稿 diff --git a/config/locales/crowdin/js-af.yml b/config/locales/crowdin/js-af.yml index 39be3096870..0d3e4b6011c 100644 --- a/config/locales/crowdin/js-af.yml +++ b/config/locales/crowdin/js-af.yml @@ -202,16 +202,7 @@ af: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Pasgemaakte veld - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Voeg tabel van verwante werkpakkette toe edit_query: Edit query - new_group: Nuwe groep - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ af: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Log tyd diff --git a/config/locales/crowdin/js-ar.yml b/config/locales/crowdin/js-ar.yml index 7bca67352c4..08d031633c8 100644 --- a/config/locales/crowdin/js-ar.yml +++ b/config/locales/crowdin/js-ar.yml @@ -202,16 +202,7 @@ ar: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: حقل مخصص - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: مجموعة جديدة - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -618,18 +609,6 @@ ar: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} كتب:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: وقت السجل diff --git a/config/locales/crowdin/js-az.yml b/config/locales/crowdin/js-az.yml index 4079c5005f6..fc444d6c5fb 100644 --- a/config/locales/crowdin/js-az.yml +++ b/config/locales/crowdin/js-az.yml @@ -202,16 +202,7 @@ az: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Custom field - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: New group - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ az: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Jurnal vaxtı diff --git a/config/locales/crowdin/js-be.yml b/config/locales/crowdin/js-be.yml index 715f8014922..7716a713ca6 100644 --- a/config/locales/crowdin/js-be.yml +++ b/config/locales/crowdin/js-be.yml @@ -202,16 +202,7 @@ be: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Карыстальніцкае поле - inactive: Неактыўны - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: New group - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -616,18 +607,6 @@ be: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Абнавіць да Enterprise on-premises edition - upgrade_to_ee_text: Нічога сабе! Калі вам патрэбны гэты дадатак, вы вялікі прафесіянал! Ці будзеце вы ветлівы падтрымаць нас, OpenSource распрацоўшчыкаў, пераўтварыўшыся ў карыстальніка Enterprise edition? - more_information: Дадатковыя звесткі - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Log time diff --git a/config/locales/crowdin/js-bg.yml b/config/locales/crowdin/js-bg.yml index 7a84a3882b6..7bb891a0d8b 100644 --- a/config/locales/crowdin/js-bg.yml +++ b/config/locales/crowdin/js-bg.yml @@ -202,16 +202,7 @@ bg: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Потребителски полета - inactive: Неактивен - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Редактирай заявка - new_group: Нова група - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Възстановяване по подразбиране working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ bg: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} написа:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: Повече информация - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Отчетено време diff --git a/config/locales/crowdin/js-ca.yml b/config/locales/crowdin/js-ca.yml index a8234eddcfa..640f6e26dd1 100644 --- a/config/locales/crowdin/js-ca.yml +++ b/config/locales/crowdin/js-ca.yml @@ -202,16 +202,7 @@ ca: text: "[Placeholder] Calendari incrustat" admin: type_form: - custom_field: Camp personalitzat - inactive: Inactiu - drag_to_activate: Arrossega els camps aquí per activar-los - add_group: Afegir atribut de grup - add_table: Afegeix una taula de paquets de treball relacionats edit_query: Editar consulta - new_group: Nou grup - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Restableix als valors per defecte working_days: calendar: empty_state_header: Dies no laborables @@ -614,18 +605,6 @@ ca: breadcrumb: Breadcrumb text_data_lost: Totes les dades entrades es perdran. text_user_wrote: "%{value} va escriure:" - types: - attribute_groups: - error_duplicate_group_name: El nom %{group} ja està en ús. Els noms de grup han de ser únics. - error_no_table_configured: Si us plau, configuri una taula per a %{group}. - reset_title: Reiniciar la configuració del formulari - confirm_reset: 'Alerta: Estàs segur que vols reiniciar la configuració del formulari? Això reiniciarà els atributs als seus grups per defecte i desactivarà TOTS els camps personalitzats. - - ' - upgrade_to_ee: Actualitza a l'edició Enterprise on-premises - upgrade_to_ee_text: Ostres! Si necessita aquesta funció ets un súper pro! T'importaria donar suport als nostres desenvolupadors de codi obert convertint-te en client de l'edició Enterprise? - more_information: Més informació - nevermind: Sense importància time_entry: work_package_required: Requereix seleccionar un paquet de treball primer. title: Registrar temps diff --git a/config/locales/crowdin/js-ckb-IR.yml b/config/locales/crowdin/js-ckb-IR.yml index 79a003cdf4c..cddbba5f8f9 100644 --- a/config/locales/crowdin/js-ckb-IR.yml +++ b/config/locales/crowdin/js-ckb-IR.yml @@ -202,16 +202,7 @@ ckb-IR: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Custom field - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: New group - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ ckb-IR: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Log time diff --git a/config/locales/crowdin/js-cs.yml b/config/locales/crowdin/js-cs.yml index 576c363463a..3e6ab2286d0 100644 --- a/config/locales/crowdin/js-cs.yml +++ b/config/locales/crowdin/js-cs.yml @@ -202,16 +202,7 @@ cs: text: "[Placeholder] Vložený kalendář" admin: type_form: - custom_field: Vlastní pole - inactive: Neaktivní - drag_to_activate: Přetáhněte pole zde, abyste je aktivovali - add_group: Přidat skupinu atributů - add_table: Přidat seznam se souvisejícími pracovními balíčky edit_query: Upravit dotaz - new_group: Nová skupina - delete_group: Odstranit skupinu - remove_attribute: Odebrat ze skupiny - reset_to_defaults: Obnovit výchozí nastavení working_days: calendar: empty_state_header: Nepracovní dny @@ -620,18 +611,6 @@ cs: breadcrumb: Navigační lišta text_data_lost: Všechna zadaná data budou ztracena. text_user_wrote: "%{value} napsal:" - types: - attribute_groups: - error_duplicate_group_name: Jméno %{group} je již použito. Jména skupin musí být jedinečná. - error_no_table_configured: Prosím nastavte tabulku pro %{group}. - reset_title: Vyčistit konfiguraci formuláře - confirm_reset: 'Upozornění: Opravdu chcete resetovat konfiguraci formuláře? Tato akce obnoví atributy na jejich výchozí skupinu a zakáže VŠECHNY vlastní pole. - - ' - upgrade_to_ee: Upgradovat na Enterprise Edition on premises - upgrade_to_ee_text: Páni! Pokud potřebujete tuto funkci, jste super pro! Nevadili byste nám podpořit OpenSource vývojáře tím, že se stanete klientem Enterprise Edition? - more_information: Více informací - nevermind: Nevadí time_entry: work_package_required: Vyžaduje nejprve výběr pracovního balíčku. title: Čas protokolu diff --git a/config/locales/crowdin/js-da.yml b/config/locales/crowdin/js-da.yml index 6337f9f4311..e5932ee36d2 100644 --- a/config/locales/crowdin/js-da.yml +++ b/config/locales/crowdin/js-da.yml @@ -202,16 +202,7 @@ da: text: "[Placeholder] Embedded kalender" admin: type_form: - custom_field: Selvvalgt felt - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: Ny gruppe - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ da: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} skrev:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Tidsregistrering diff --git a/config/locales/crowdin/js-de.yml b/config/locales/crowdin/js-de.yml index 86a946bc6f1..0cc1627c537 100644 --- a/config/locales/crowdin/js-de.yml +++ b/config/locales/crowdin/js-de.yml @@ -202,16 +202,7 @@ de: text: "[Platzhalter] Eingebetteter Kalender" admin: type_form: - custom_field: Benutzerdefiniertes Feld - inactive: Inaktiv - drag_to_activate: Felder aus diesem Bereich herausziehen, um sie zu aktivieren - add_group: Attributgruppe hinzufügen - add_table: Tabelle mit zugehörigen Arbeitspaketen hinzufügen edit_query: Abfrage bearbeiten - new_group: Neue Gruppe - delete_group: Gruppe löschen - remove_attribute: Aus Gruppe entfernen - reset_to_defaults: Auf Standardwerte zurücksetzen working_days: calendar: empty_state_header: Arbeitsfreie Tage @@ -616,18 +607,6 @@ de: breadcrumb: Navigationspfad text_data_lost: Alle eingegebenen Daten gehen verloren. text_user_wrote: "%{value} schrieb:" - types: - attribute_groups: - error_duplicate_group_name: Der Name %{group} wird mehr als einmal verwendet. Gruppen-Namen müssen eindeutig sein. - error_no_table_configured: Bitte konfigurieren Sie eine Tabelle für %{group}. - reset_title: Formularkonfiguration zurücksetzen - confirm_reset: 'Achtung: Wollen Sie die Formkonfiguration wirklich zurücksetzen? Alle Attribute und Gruppen werden auf ihre Standardwerte zurück gesetzt und ALLE benutzerdefinierten Felder entfernt. - - ' - upgrade_to_ee: Enterprise on-premises bestellen - upgrade_to_ee_text: Wow! Wenn du dieses Add-on benötigst, bist du ein super Pro! Würdest du uns OpenProject-Entwickler unterstützen, indem du ein Enterprise Edition-Kunde wirst? - more_information: Weitere Informationen - nevermind: Nein danke time_entry: work_package_required: Benötigt zuerst die Auswahl eines Arbeitspakets. title: Zeit buchen diff --git a/config/locales/crowdin/js-el.yml b/config/locales/crowdin/js-el.yml index 47e4b8f2af0..73b75e52afa 100644 --- a/config/locales/crowdin/js-el.yml +++ b/config/locales/crowdin/js-el.yml @@ -202,16 +202,7 @@ el: text: "[Placeholder] Ενσωματωμένο ημερολόγιο" admin: type_form: - custom_field: Προσαρμοσμένο πεδίο - inactive: Ανενεργό - drag_to_activate: Σύρετε πεδία από εδώ για να τα ενεργοποιήσετε - add_group: Προσθήκη ομάδας χαρακτηριστικών - add_table: Προσθήκη πίνακα με σχετιζόμενες εργασίες edit_query: Επεξεργασία αναζήτησης - new_group: Νέα ομάδα - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Επαναφορά στα προεπιλεγμένα working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ el: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} έγραψε:" - types: - attribute_groups: - error_duplicate_group_name: Το όνομα %{group} χρησιμοποιείται περισσότερες από μία φορές. Τα ονόματα ομάδων πρέπει να είναι μοναδικά. - error_no_table_configured: Παρακαλώ διαμορφώστε έναν πίνακα για %{group}. - reset_title: Επαναφορά φόρμας διαμόρφωσης - confirm_reset: 'Προειδοποίηση: Είστε σίγουροι ότι θέλετε να επαναφέρετε τη φόρμα διαμόρφωσης; Αυτό θα επαναφέρει τα χαρακτηριστικά στις προεπιλεγμένες ομάδες τους και θα απενεργοποιήσει ΌΛΑ τα προσαρμοσμένα πεδία. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: Περισσότερες πληροφορίες - nevermind: Δεν πειράζει time_entry: work_package_required: Πρέπει πρώτα να επιλεγεί ένα πακέτο εργασίας. title: Καταγραφή χρόνου diff --git a/config/locales/crowdin/js-eo.yml b/config/locales/crowdin/js-eo.yml index aa809a8f2b6..dd342e621a4 100644 --- a/config/locales/crowdin/js-eo.yml +++ b/config/locales/crowdin/js-eo.yml @@ -202,16 +202,7 @@ eo: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Propra kampo - inactive: Neaktiva - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Aldoni tabelon de rilataj laborpakaĵoj edit_query: Edit query - new_group: Nova grupo - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Restarigi la defaŭltojn working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ eo: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Bonvolu agordi la tabelon por %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: Pliaj informoj - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Tempoprotokolo diff --git a/config/locales/crowdin/js-es.yml b/config/locales/crowdin/js-es.yml index 06e82c44b0a..f25d7f0647c 100644 --- a/config/locales/crowdin/js-es.yml +++ b/config/locales/crowdin/js-es.yml @@ -200,16 +200,7 @@ es: text: "[Placeholder] Calendario insertado" admin: type_form: - custom_field: Campo personalizado - inactive: Inactivo - drag_to_activate: Arrastre campos desde aquí para activarlos - add_group: Agregar grupo de atributos - add_table: Agregar tabla de paquetes de trabajo relacionados edit_query: Editar consulta - new_group: Nuevo grupo - delete_group: Eliminar grupo - remove_attribute: Eliminar del grupo - reset_to_defaults: Restablecer valores predeterminados working_days: calendar: empty_state_header: Días no laborables @@ -612,18 +603,6 @@ es: breadcrumb: Ruta de navegación text_data_lost: Todos los datos introducidos se perderán. text_user_wrote: "%{value} escribió:" - types: - attribute_groups: - error_duplicate_group_name: El nombre %{group} se ha usado más de una vez. Los nombres de grupos deben ser únicos. - error_no_table_configured: Configure una tabla para %{group}. - reset_title: Restablecer la configuración del formulario - confirm_reset: 'Advertencia: ¿Está seguro de que quiere restablecer la configuración del formulario? Se restablecerán los atributos al grupo predeterminado y se deshabilitarán TODOS los campos personalizados. - - ' - upgrade_to_ee: Actualizar a Enterprise On-Premises - upgrade_to_ee_text: "¡Vaya! Si necesita está extensión, entonces ¡es un usuario experto! ¿Le importaría apoyar a los desarrolladores de código abierto convirtiéndose en cliente de Enterprise?" - more_information: Más información - nevermind: No importa time_entry: work_package_required: Es necesario seleccionar primero un paquete de trabajo. title: Registro de tiempo diff --git a/config/locales/crowdin/js-et.yml b/config/locales/crowdin/js-et.yml index 1777a5cfcf6..1cd95638bf3 100644 --- a/config/locales/crowdin/js-et.yml +++ b/config/locales/crowdin/js-et.yml @@ -202,16 +202,7 @@ et: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Lisaväli - inactive: Mitteaktiivne - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: Uus rühm - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Taasta vaikesätted working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ et: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} kirjutas:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: Lisainfo - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Ajakulu diff --git a/config/locales/crowdin/js-eu.yml b/config/locales/crowdin/js-eu.yml index 5849d78b37d..547e0875a64 100644 --- a/config/locales/crowdin/js-eu.yml +++ b/config/locales/crowdin/js-eu.yml @@ -202,16 +202,7 @@ eu: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Custom field - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: New group - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ eu: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Log time diff --git a/config/locales/crowdin/js-fa.yml b/config/locales/crowdin/js-fa.yml index 36c4f820b8e..7c38cfb1841 100644 --- a/config/locales/crowdin/js-fa.yml +++ b/config/locales/crowdin/js-fa.yml @@ -202,16 +202,7 @@ fa: text: "[Placeholder] تقویم جاسازی شده" admin: type_form: - custom_field: فیلدهای سفارشی - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: New group - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ fa: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: ارتقاء به نسخه انترپرایز (on-premises) بر روی سرور مجازی یا اختصاصی شخصی خود - upgrade_to_ee_text: وااای! اگر به این افزونه نیاز دارید، شما یک حرفه‌ای هستید! آیا ممکن است ما توسعه‌دهندگان متن‌باز را با تبدیل شدن به مشتری نسخه انترپرایز حمایت کنید؟ - more_information: اطلاعات بیشتر - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: پیگیری زمان diff --git a/config/locales/crowdin/js-fi.yml b/config/locales/crowdin/js-fi.yml index f2d8ae4680d..a65c8307e95 100644 --- a/config/locales/crowdin/js-fi.yml +++ b/config/locales/crowdin/js-fi.yml @@ -202,16 +202,7 @@ fi: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Mukautettu kenttä - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: Uusi ryhmä - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ fi: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} kirjoitti:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: Lisätietoja - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Kirjaa työaika diff --git a/config/locales/crowdin/js-fil.yml b/config/locales/crowdin/js-fil.yml index cdce47f357a..efd9a8beae1 100644 --- a/config/locales/crowdin/js-fil.yml +++ b/config/locales/crowdin/js-fil.yml @@ -202,16 +202,7 @@ fil: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Pasadyang patlang - inactive: Hindi aktibo - drag_to_activate: I-drag ang mga patlang mula dito upang paganahin sila - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: Bagong grupo - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: I-reset sa mga default working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ fil: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} isinulat:" - types: - attribute_groups: - error_duplicate_group_name: Ang pangalan ng %{group} ay ginamit higit pa sa isang beses. Ang mga pangalan ng grupo ay dapat nakakakatangi. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: Karagdagang impornasyon - nevermind: Hindi bale time_entry: work_package_required: Requires selecting a work package first. title: Ang oras ng tala diff --git a/config/locales/crowdin/js-fr.yml b/config/locales/crowdin/js-fr.yml index d32b74f3917..124e3a2695c 100644 --- a/config/locales/crowdin/js-fr.yml +++ b/config/locales/crowdin/js-fr.yml @@ -202,16 +202,7 @@ fr: text: "[Placeholder] Calendrier embarqué" admin: type_form: - custom_field: Champ personnalisé - inactive: Inactif - drag_to_activate: Faire glisser les champs ici pour les activer - add_group: Ajouter un groupe d'attributs - add_table: Ajouter un tableau des lots de travaux associés edit_query: Modifier la requête - new_group: Nouveau groupe - delete_group: Supprimer le groupe - remove_attribute: Retirer du groupe - reset_to_defaults: Rétablir les valeurs par défaut working_days: calendar: empty_state_header: Jours non travaillés @@ -614,18 +605,6 @@ fr: breadcrumb: Fil d'Ariane text_data_lost: Toutes les données saisies seront perdues. text_user_wrote: "%{value} écrit:" - types: - attribute_groups: - error_duplicate_group_name: Le nom %{group} est utilisé plus d'une fois. Les noms de groupe doivent être uniques. - error_no_table_configured: Veuillez configurer un tableau pour %{group}. - reset_title: Réinitialiser la configuration du formulaire - confirm_reset: 'Avertissement : voulez-vous vraiment réinitialiser la configuration du formulaire ? Ceci va réinitialiser les attributs à leur groupe par défaut et désactiver TOUS les champs personnalisés. - - ' - upgrade_to_ee: Mettre à niveau vers la version Entreprise autohébergée - upgrade_to_ee_text: Waouh ! Si vous avez besoin de cette fonctionnalité, vous êtes un véritable pro ! Cela vous dérangerait-il de soutenir les développeurs Open Source en devenant un client de la version Entreprise ? - more_information: Plus d'informations - nevermind: Peu importe time_entry: work_package_required: Nécessite d'abord la sélection d'un lot de travaux. title: Consigner du temps diff --git a/config/locales/crowdin/js-he.yml b/config/locales/crowdin/js-he.yml index dd7cbcb4097..2ac42316e87 100644 --- a/config/locales/crowdin/js-he.yml +++ b/config/locales/crowdin/js-he.yml @@ -202,16 +202,7 @@ he: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: שדה מותאם אישית - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: קבוצה חדשה - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -616,18 +607,6 @@ he: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: נרשם ביומן diff --git a/config/locales/crowdin/js-hi.yml b/config/locales/crowdin/js-hi.yml index 642a717e2db..a850c813851 100644 --- a/config/locales/crowdin/js-hi.yml +++ b/config/locales/crowdin/js-hi.yml @@ -202,16 +202,7 @@ hi: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: रुचि के अनुसार फिल्ड - inactive: Inactive - drag_to_activate: उंहें सक्रिय करने के लिए यहां से फ़ील्ड्स खींचें - add_group: विशेषता समूह जोड़ें - add_table: Add table of related work packages edit_query: Edit query - new_group: नया समूह - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ hi: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: लॉग समय diff --git a/config/locales/crowdin/js-hr.yml b/config/locales/crowdin/js-hr.yml index 67c0035186c..fdd2f8ba839 100644 --- a/config/locales/crowdin/js-hr.yml +++ b/config/locales/crowdin/js-hr.yml @@ -202,16 +202,7 @@ hr: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Prilagođeno polje - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: Nova grupa - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -615,18 +606,6 @@ hr: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} je napisao:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: Više informacija - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Vrijeme pristupa diff --git a/config/locales/crowdin/js-hu.yml b/config/locales/crowdin/js-hu.yml index 3e630f2f4ce..33bbfea469a 100644 --- a/config/locales/crowdin/js-hu.yml +++ b/config/locales/crowdin/js-hu.yml @@ -214,16 +214,7 @@ hu: text: "[Placeholder] Beágyazott naptár" admin: type_form: - custom_field: Választható mező - inactive: Inaktív - drag_to_activate: Innen húzza a mezőket, hogy aktiválja - add_group: Tulajdonságcsoport - add_table: Táblázat hozzáadása a kapcsolódó munkacsomagokhoz edit_query: Lekérdezés módosítása - new_group: Új csoport - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Alapbeállítások visszaállítása working_days: calendar: empty_state_header: Non-working days @@ -644,18 +635,6 @@ hu: breadcrumb: Breadcrumb text_data_lost: Az összes adat elveszhet text_user_wrote: "%{value} írta:" - types: - attribute_groups: - error_duplicate_group_name: A %{group} név már használatban van. A csoport névnek egyedinek kell lennie. - error_no_table_configured: Kérem konfigurálja a táblát a %{group} - reset_title: Űrlap konfiguráció visszaállítása - confirm_reset: 'Figyelmeztetés: Biztosan visszaállítja az alap űrlap beállítást? Az attribútumok az alapértelmezett csoportba kerülnek, a láthatósági beállítások elvesznek és MINDEN egyéni mező deaktiválódik. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: További információ - nevermind: Mindegy time_entry: work_package_required: Először ki kell választania egy munkacsomagot. title: Eltöltött idő rögzítése diff --git a/config/locales/crowdin/js-hy.yml b/config/locales/crowdin/js-hy.yml new file mode 100644 index 00000000000..5aead332475 --- /dev/null +++ b/config/locales/crowdin/js-hy.yml @@ -0,0 +1,1119 @@ +#-- copyright +#OpenProject is an open source project management software. +#Copyright (C) the OpenProject GmbH +#This program is free software; you can redistribute it and/or +#modify it under the terms of the GNU General Public License version 3. +#OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +#Copyright (C) 2006-2013 Jean-Philippe Lang +#Copyright (C) 2010-2013 the ChiliProject Team +#This program is free software; you can redistribute it and/or +#modify it under the terms of the GNU General Public License +#as published by the Free Software Foundation; either version 2 +#of the License, or (at your option) any later version. +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +#You should have received a copy of the GNU General Public License +#along with this program; if not, write to the Free Software +#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +#See COPYRIGHT and LICENSE files for more details. +#++ +--- +hy: + js: + ajax: + hide: Hide + loading: Loading… + updating: Updating… + attachments: + delete: Delete attachment + delete_confirmation: 'Are you sure you want to delete this file? This action is not reversible. + + ' + draggable_hint: 'Drag on editor field to inline image or reference attachment. Closed editor fields will be opened while you keep dragging. + + ' + quarantined_hint: The file is quarantined, as a virus was found. It is not available for download. + autocomplete_ng_select: + add_tag: Add item + clear_all: Clear all + loading: Loading... + not_found: No items found + type_to_search: Type to search + autocomplete_select: + placeholder: + multi: Add "%{name}" + single: Select "%{name}" + remove: Remove %{name} + active: Active %{label} %{name} + backup: + attachments_disabled: Attachments may not be included since they exceed the maximum overall size allowed. You can change this via the configuration (requires a server restart). + info: 'You can trigger a backup here. The process can take some time depending on the amount of data (especially attachments) you have. You will receive an email once it''s ready. + + ' + note: 'A new backup will override any previous one. Only a limited number of backups per day can be requested. + + ' + last_backup: Last backup + last_backup_from: Last backup from + title: Backup OpenProject + options: Options + include_attachments: Include attachments + download_backup: Download backup + request_backup: Request backup + close_popup_title: Close popup + close_filter_title: Close filter + close_form_title: Close form + button_add_watcher: Add watcher + button_add: Add + button_back: Back + button_back_to_list_view: Back to list view + button_cancel: Cancel + button_close: Close + button_change_project: Move to another project + button_check_all: Check all + button_configure-form: Configure form + button_confirm: Confirm + button_continue: Continue + button_copy: Copy + button_copy_to_clipboard: Copy to clipboard + button_copy_link_to_clipboard: Copy link to clipboard + button_copy_to_other_project: Duplicate in another project + button_custom-fields: Custom fields + button_delete: Delete + button_delete_watcher: Delete watcher + button_details_view: Details view + button_duplicate: Duplicate + button_edit: Edit + button_filter: Filter + button_collapse_all: Collapse all + button_expand_all: Expand all + button_advanced_filter: Advanced filter + button_list_view: List view + button_show_view: Fullscreen view + button_log_time: Log time + button_start_timer: Start timer + button_stop_timer: Stop timer + button_more: More + button_open_details: Open details view + button_close_details: Close details view + button_open_fullscreen: Open fullscreen view + button_show_cards: Show card view + button_show_list: Show list view + button_show_table: Show table view + button_show_gantt: Show Gantt view + button_show_fullscreen: Show fullscreen view + button_more_actions: More actions + button_quote: Quote + button_save: Save + button_settings: Settings + button_uncheck_all: Uncheck all + button_update: Update + button_export-atom: Download Atom + button_generate_pdf: Generate PDF + button_create: Create + card: + add_new: Add new card + highlighting: + inline: 'Highlight inline:' + entire_card_by: Entire card by + remove_from_list: Remove card from list + caption_rate_history: Rate history + clipboard: + browser_error: 'Your browser doesn''t support copying to clipboard. Please copy it manually: %{content}' + copied_successful: Successfully copied to clipboard! + chart: + type: Chart type + axis_criteria: Axis criteria + modal_title: Work package graph configuration + types: + line: Line + horizontal_bar: Horizontal bar + bar: Bar + pie: Pie + doughnut: Doughnut + radar: Radar + polar_area: Polar area + tabs: + graph_settings: General + dataset: Dataset %{number} + errors: + could_not_load: The data to display the graph could not be loaded. The necessary permissions may be lacking. + description_available_columns: Available Columns + description_current_position: 'You are here: ' + description_select_work_package: 'Select work package #%{id}' + description_subwork_package: 'Child of work package #%{id}' + editor: + revisions: Show local modifications + no_revisions: No local modifications found + preview: Toggle preview mode + source_code: Toggle Markdown source mode + error_saving_failed: 'Saving the document failed with the following error: %{error}' + ckeditor_error: An error occurred within CKEditor + mode: + manual: Switch to Markdown source + wysiwyg: Switch to WYSIWYG editor + macro: + error: 'Cannot expand macro: %{message}' + attribute_reference: + macro_help_tooltip: This text segment is being dynamically rendered by a macro. + not_found: Requested resource could not be found + nested_macro: This macro is recursively referencing %{model} %{id}. + invalid_attribute: The selected attribute '%{name}' does not exist. + child_pages: + button: Links to child pages + include_parent: Include parent + text: "[Placeholder] Links to child pages of" + page: Wiki page + this_page: this page + hint: 'Leave this field empty to list all child pages of the current page. If you want to reference a different page, provide its title or slug. + + ' + code_block: + button: Insert code snippet + title: Insert / edit Code snippet + language: Formatting language + language_hint: Enter the formatting language that will be used for highlighting (if supported). + dropdown: + macros: Macros + chose_macro: Choose macro + toc: Table of contents + toolbar_help: Click to select widget and show the toolbar. Double-click to edit widget + wiki_page_include: + button: Include content of another wiki page + text: "[Placeholder] Included wiki page of" + page: Wiki page + not_set: "(Page not yet set)" + hint: | + Include the content of another wiki page by specifying its title or slug. + You can include the wiki page of another project by separating them with a colon like the following example. + work_package_button: + button: Insert create work package button + type: Work package type + button_style: Use button style + button_style_hint: 'Optional: Check to make macro appear as a button, not as a link.' + without_type: Create work package + with_type: 'Create work package (Type: %{typename})' + embedded_table: + button: Embed work package table + text: "[Placeholder] Embedded work package table" + embedded_calendar: + text: "[Placeholder] Embedded calendar" + admin: + type_form: + edit_query: Edit query + working_days: + calendar: + empty_state_header: Non-working days + empty_state_description: No specific non-working days are defined for this year. Click on "+ Non-working day" below to add a date. + new_date: "(new)" + add_non_working_day: Non-working day + already_added_error: A non-working day for this date exists already. There can only be one non-working day created for each unique date. + change_button: Save and reschedule + change_title: Change working days + removed_title: 'You will remove the following days from the non-working days list:' + change_description: Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance. + warning: 'The changes might take some time to take effect. You will be notified when all relevant work packages and project life cycles have been updated. Are you sure you want to continue? + + ' + work_packages_settings: + warning_progress_calculation_mode_change_from_status_to_field_html: Changing progress calculation mode from status-based to work-based will make the % Complete field freely editable. If you optionally enter values for Work or Remaining work, they will also be linked to % Complete. Changing Remaining work can then update % Complete. + warning_progress_calculation_mode_change_from_field_to_status_html: Changing progress calculation mode from work-based to status-based will result in all existing % Complete values to be lost and replaced with values associated with each status. Existing values for Remaining work may also be recalculated to reflect this change. This action is not reversible. + custom_actions: + date: + specific: 'on' + current_date: Current date + error: + internal: An internal error has occurred. + cannot_save_changes_with_message: 'Cannot save your changes due to the following error: %{error}' + query_saving: The view could not be saved. + embedded_table_loading: 'The embedded view could not be loaded: %{message}' + enumeration_activities: Activities (time tracking) + enumeration_doc_categories: Document categories + enumeration_work_package_priorities: Work package priorities + filter: + more_values_not_shown: There are %{total} more results, search to filter results. + description: + text_open_filter: Open this filter with 'ALT' and arrow keys. + text_close_filter: To select an entry leave the focus for example by pressing enter. To leave without filter select the first (empty) entry. + noneElement: "(none)" + time_zone_converted: + two_values: "%{from} - %{to} in your local time." + only_start: From %{from} in your local time. + only_end: Till %{to} in your local time. + value_spacer: "-" + sorting: + criteria: + one: First sorting criteria + two: Second sorting criteria + three: Third sorting criteria + gantt_chart: + label: Gantt chart + quarter_label: Q%{quarter_number} + labels: + title: Label configuration + bar: Bar labels + left: Left + right: Right + farRight: Far right + description: 'Select the attributes you want to be shown in the respective positions of the Gantt chart at all times. Note that when hovering over an element, its date labels will be shown instead of these attributes. + + ' + button_activate: Show Gantt chart + button_deactivate: Hide Gantt chart + filter: + noneSelection: "(none)" + selection_mode: + notification: Click on any highlighted work package to create the relation. Press escape to cancel. + zoom: + in: Zoom in + out: Zoom out + auto: Auto zoom + days: Days + weeks: Weeks + months: Months + quarters: Quarters + years: Years + description: 'Select the initial zoom level that should be shown when autozoom is not available. + + ' + general_text_no: 'no' + general_text_yes: 'yes' + general_text_No: 'No' + general_text_Yes: 'Yes' + hal: + error: + update_conflict_refresh: Click here to refresh the resource and update to the newest version. + edit_prohibited: Editing %{attribute} is blocked for this resource. Either this attribute is derived from relations (e.g, children) or otherwise not configurable. + format: + date: "%{attribute} is no valid date - YYYY-MM-DD expected." + general: An error has occurred. + ical_sharing_modal: + title: Subscribe to calendar + inital_setup_error_message: An error occured while fetching data. + description: You can use the URL (iCalendar) to subscribe to this calendar in an external client and view up-to-date work package information from there. + warning: Please don't share this URL with other users. Anyone with this link will be able to view work package details without an account or password. + token_name_label: Where will you be using this? + token_name_placeholder: Type a name, e.g. "Phone" + token_name_description_text: If you subscribe to this calendar from multiple devices, this name will help you distinguish between them in your access tokens list. + copy_url_label: Copy URL + ical_generation_error_text: An error occured while generating the calendar URL. + success_message: The URL "%{name}" was successfully copied to your clipboard. Paste it in your calendar client to complete the subscription. + label_activate: Activate + label_assignee: Assignee + label_assignee_alt_text: This work package is assigned to %{name} + label_add_column_after: Add column after + label_add_column_before: Add column before + label_add_columns: Add columns + label_add_comment: Add comment + label_add_comment_title: Comment and type @ to notify other people + label_add_row_after: Add row after + label_add_row_before: Add row before + label_add_selected_columns: Add selected columns + label_added_by: added by + label_added_time_by: Added by %{author} at %{age} + label_ago: days ago + label_all: all + label_all_projects: All projects + label_all_uppercase: All + label_all_work_packages: all work packages + label_and: and + label_ascending: Ascending + label_author: 'Author: %{user}' + label_avatar: Avatar + label_between: between + label_board: Board + label_board_locked: Locked + label_board_plural: Boards + label_board_sticky: Sticky + label_change: Change + label_create: Create + label_create_work_package: Create new work package + label_created_by: Created by + label_current: current + label_date: Date + label_date_with_format: 'Enter the %{date_attribute} using the following format: %{format}' + label_deactivate: Deactivate + label_descending: Descending + label_description: Description + label_details: Details + label_display: Display + label_cancel_comment: Cancel comment + label_closed_work_packages: closed + label_collapse: Collapse + label_collapsed: collapsed + label_collapse_all: Collapse all + label_collapse_text: Collapse text + label_comment: Comment + label_contains: contains + label_created_on: created on + label_edit_comment: Edit this comment + label_edit_status: Edit the status of the work package + label_email: Email + label_equals: is + label_expand: Expand + label_expanded: expanded + label_expand_all: Expand all + label_expand_text: Show full text + label_expand_project_menu: Expand project menu + label_export: Export + label_export_preparing: The export is being prepared and will be downloaded shortly. + label_favorites: Favorites + label_filename: File + label_filesize: Size + label_general: General + label_greater_or_equal: ">=" + label_group: Group + label_group_by: Group by + label_group_plural: Groups + label_hide_attributes: Show less + label_hide_column: Hide column + label_hide_project_menu: Collapse project menu + label_in: in + label_in_less_than: in less than + label_in_more_than: in more than + label_incoming_emails: Incoming emails + label_information_plural: Information + label_invalid: Invalid + label_import: Import + label_latest_activity: Latest activity + label_last_updated_on: Last updated on + label_learn_more_link: Learn more + label_less_or_equal: "<=" + label_less_than_ago: less than days ago + label_loading: Loading... + label_mail_notification: Email notifications + label_manage_columns: Manage and reorder columns + label_me: me + label_meeting_agenda: Agenda + label_meeting_minutes: Minutes + label_more_than_ago: more than days ago + label_moderate_comment: Moderate comment + label_next: Next + label_no_color: No color + label_no_data: No data to display + label_no_due_date: no finish date + label_no_start_date: no start date + label_no_date: no date + label_no_value: No value + label_none: none + label_not_contains: doesn't contain + label_not_equals: is not + label_on: 'on' + label_open_menu: Open menu + label_open_context_menu: Open context menu + label_open_work_packages: open + label_password: Password + label_previous: Previous + label_per_page: 'Per page:' + label_please_wait: Please wait + label_project: Project + label_project_list: Project lists + label_project_plural: Projects + label_visibility_settings: Visibility settings + label_quote_comment: Quote this comment + label_recent: Recent + label_reset: Reset + label_remove: Remove + label_remove_column: Remove column + label_remove_columns: Remove selected columns + label_remove_row: Remove row + label_report: Report + label_repository_plural: Repositories + label_resize_project_menu: Resize project menu + label_save_as: Save as + label_search_columns: Search a column + label_select_watcher: Select a watcher... + label_selected_filter_list: Selected filters + label_show_attributes: Show all attributes + label_show_in_menu: Show view in menu + label_sort_by: Sort by + label_sorted_by: sorted by + label_sort_higher: Move up + label_sort_lower: Move down + label_sorting: Sorting + label_spent_time: Spent time + label_star_query: Favorited + label_press_enter_to_save: Press enter to save. + label_public_query: Public + label_sum: Sum + label_sum_for: Sum for + label_total_sum: Total sum + label_subject: Subject + label_this_week: this week + label_today: Today + label_time_entry_plural: Spent time + label_up: Up + label_user_plural: Users + label_activity_show_only_comments: Show activities with comments only + label_activity_show_all: Show all activities + label_total_progress: "%{percent}% Total progress" + label_total_amount: 'Total: %{amount}' + label_updated_on: updated on + label_value_derived_from_children: "(value derived from children)" + label_children_derived_duration: Work package's children derived duration + label_warning: Warning + label_work_package: Work package + label_work_package_parent: Parent work package + label_work_package_plural: Work packages + label_watch: Watch + label_watch_work_package: Watch work package + label_watcher_added_successfully: Watcher successfully added! + label_watcher_deleted_successfully: Watcher successfully deleted! + label_work_package_details_you_are_here: You are on the %{tab} tab for %{type} %{subject}. + label_work_package_context_menu: Work package context menu + label_unwatch: Unwatch + label_unwatch_work_package: Unwatch work package + label_uploaded_by: Uploaded by + label_default_queries: Default + label_starred_queries: Favorite + label_global_queries: Public + label_custom_queries: Private + label_columns: Columns + label_attachments: Attachments + label_drop_files: Drop files here to attach files. + label_drop_or_click_files: Drop files here or click to attach files. + label_drop_folders_hint: You cannot upload folders as an attachment. Please select single files. + label_add_attachments: Attach files + label_formattable_attachment_hint: Attach and link files by dropping on this field, or pasting from the clipboard. + label_remove_file: Delete %{fileName} + label_remove_watcher: Remove watcher %{name} + label_remove_all_files: Delete all files + label_add_description: Add a description for %{file} + label_upload_notification: Uploading files... + label_work_package_upload_notification: 'Uploading files for Work package #%{id}: %{subject}' + label_wp_id_added_by: "#%{id} added by %{author}" + label_files_to_upload: 'These files will be uploaded:' + label_rejected_files: 'These files cannot be uploaded:' + label_rejected_files_reason: These files cannot be uploaded as their size is greater than %{maximumFilesize} + label_wait: Please wait for configuration... + label_upload_counter: "%{done} of %{count} files finished" + label_validation_error: 'The work package could not be saved due to the following errors:' + label_version_plural: Versions + label_view_has_changed: This view has unsaved changes. Click to save them. + help_texts: + show_modal: Show help text + onboarding: + buttons: + skip: Skip + next: Next + got_it: Got it + steps: + help_menu: The Help (?) menu provides additional help resources. Here you can find a user guide, helpful how-to videos and more.
Enjoy your work with OpenProject! + members: Invite new members to join your project. + quick_add_button: Click on the plus (+) icon in the header navigation to create a new project or to invite coworkers. + sidebar_arrow: Use the return arrow in the top left corner to return to the project’s main menu. + welcome: Take a three-minute introduction tour to learn the most important features.
We recommend completing the steps until the end. You can restart the tour any time. + wiki: Within the wiki you can document and share knowledge together with your team. + boards: + overview: Select boards to shift the view and manage your project using the agile boards view. + lists_kanban: Here you can create multiple lists (columns) within your board. This feature allows you to create a Kanban board, for example. + lists_basic: Here you can create multiple lists (columns) within your agile board. + add: Click on the plus (+) icon to create a new card or add an existing card to the list on the board. + drag: Drag and drop your cards within a given list to reorder them, or to move them to another list.
You can click the info (i) icon in the upper right-hand corner or double-click a card to open its details. + wp: + toggler: Now let's have a look at the work package section, which gives you a more detailed view of your work. + list: This work package overview provides a list of all the work in your project, such as tasks, milestones, phases, and more.
Work packages can be created and edited directly from this view. To access the details of a particular work package, simply double-click its row. + full_view: The work package details view provides all the relevant information pertaining to a given work package, such as its description, status, priority, activities, dependencies, and comments. + back_button: Use the return arrow in the top left corner to exit and return to the work package list. + create_button: The + Create button will add a new work package to your project. + gantt_menu: Create project schedules and timelines effortlessly using the Gantt chart module. + timeline: Here you can edit your project plan, create new work packages, such as tasks, milestones, phases, and more, as well as add dependencies. All team members can see and update the latest plan at any time. + team_planner: + overview: The team planner lets you visually assign tasks to team members and get an overview of who is working on what. + calendar: The weekly or biweekly planning board displays all work packages assigned to your team members. + add_assignee: To get started, add assignees to the team planner. + add_existing: Search for existing work packages and drag them to the team planner to instantly assign them to a team member and define start and end dates. + card: Drag work packages horizontally to move them backwards or forwards in time, drag the edges to change start and end dates and even drag them vertically to a different row to assign them to another member. + notifications: + title: Notifications + no_unread: No unread notifications + reasons: + mentioned: Mentioned + watched: Watcher + assigned: Assignee + responsible: Accountable + created: Created + scheduled: Scheduled + commented: Commented + processed: Processed + prioritized: Prioritized + dateAlert: Date alert + shared: Shared + reminder: Reminder + date_alerts: + milestone_date: Milestone date + overdue: Overdue + overdue_since: for %{difference_in_days}. + property_today: is today. + property_is: is in %{difference_in_days}. + property_was: was %{difference_in_days} ago. + property_is_deleted: is deleted. + center: + label_actor_and: and + and_more_users: + one: and 1 other + other: and %{count} others + no_results: + at_all: New notifications will appear here when there is activity that concerns you. + with_current_filter: There are no notifications in this view at the moment + mark_all_read: Mark all as read + mark_as_read: Mark as read + mark_all_read_confirmation: This will mark all notifications in this view as read. Are you sure you want to do this? + text_update_date_by: "%{date} by" + total_count_warning: Showing the %{newest_count} most recent notifications. %{more_count} more are not displayed. + empty_state: + no_notification: Looks like you are all caught up. + no_notification_with_current_project_filter: Looks like you are all caught up with the selected project. + no_notification_with_current_filter: Looks like you are all caught up for %{filter} filter. + no_notification_for_filter: Looks like you are all caught up for this filter. + no_selection: Click on a notification to view all activity details. + new_notifications: + message: There are new notifications. + link_text: Click here to load them. + reminders: + note: 'Note: “%{note}”' + settings: + change_notification_settings: You can modify your notification settings to ensure you never miss an important update. + title: Notification settings + pagination: + no_other_page: You are on the only page. + pages_skipped: Pages skipped. + page_navigation: Pagination navigation + per_page_navigation: Items per page selection + pages: + page_number: Page %{number} + show_per_page: Show %{number} per page + placeholders: + default: "-" + subject: Enter subject here + selection: Please select + description: 'Description: Click to edit...' + relation_description: Click to add description for this relation + project: + autocompleter: + label: Project autocompletion + click_to_switch_to_project: 'Project: %{projectname}' + context: Project context + not_available: Project N/A + required_outside_context: 'Please choose a project to create the work package in to see all attributes. You can only select projects which have the type above activated. + + ' + text_are_you_sure: Are you sure? + text_are_you_sure_to_cancel: You have unsaved changes on this page. Are you sure you want to discard them? + breadcrumb: Breadcrumb + text_data_lost: All entered data will be lost. + text_user_wrote: "%{value} wrote:" + time_entry: + work_package_required: Requires selecting a work package first. + title: Log time + tracking: Time tracking + stop: Stop + timer: + start_new_timer: Start new timer + timer_already_running: 'To start a new timer, you must first stop the current timer:' + timer_already_stopped: No active timer for this work package, have you stopped it in another window? + tracking_time: Tracking time + button_stop: Stop current timer + two_factor_authentication: + label_two_factor_authentication: Two-factor authentication + watchers: + label_loading: loading watchers... + label_error_loading: An error occurred while loading the watchers + label_search_watchers: Search watchers + label_add: Add watchers + label_discard: Discard selection + typeahead_placeholder: Search for possible watchers + relation_labels: + parent: Parent + child: Child + children: Children + relates: Related To + duplicates: Duplicates + duplicated: Duplicated by + blocks: Blocks + blocked: Blocked by + precedes: Precedes + follows: Follows + includes: Includes + partof: Part of + requires: Requires + required: Required by + relation_type: relation type + relations_hierarchy: + parent_headline: Parent + hierarchy_headline: Hierarchy + children_headline: Children + relation_buttons: + set_parent: Set parent + change_parent: Change parent + remove_parent: Remove parent + hierarchy_indent: Indent hierarchy + hierarchy_outdent: Outdent hierarchy + group_by_wp_type: Group by work package type + group_by_relation_type: Group by relation type + add_parent: Add existing parent + add_new_child: Create new child + create_new: Create new + add_existing: Add existing + add_existing_child: Add child + remove_child: Remove child + add_new_relation: Create new relation + add_existing_relation: Add existing relation + update_description: Set or update description of this relation + toggle_description: Toggle relation description + update_relation: Click to change the relation type + show_relations: Show relations + add_predecessor: Add predecessor + add_successor: Add successor + remove: Remove relation + save: Save relation + abort: Abort + relations_autocomplete: + placeholder: Type to search + parent_placeholder: Choose new parent or press escape to cancel. + autocompleter: + placeholder: Type to search + notFoundText: No items found + search: Search + project: + placeholder: Select project + repositories: + select_tag: Select tag + select_branch: Select branch + field_value_enter_prompt: Enter a value for '%{field}' + project_menu_details: Details + sort: + sorted_asc: 'Ascending sort applied, ' + sorted_dsc: 'Descending sort applied, ' + sorted_no: 'No sort applied, ' + sorting_disabled: sorting is disabled + activate_asc: activate to apply an ascending sort + activate_dsc: activate to apply a descending sort + activate_no: activate to remove the sort + text_work_packages_destroy_confirmation: Are you sure you want to delete the selected work package(s)? + text_query_destroy_confirmation: Are you sure you want to delete the selected view? + tl_toolbar: + zooms: Zoom level + outlines: Hierarchy level + upsell: + ee_only: Enterprise edition add-on + wiki_formatting: + strong: Strong + italic: Italic + underline: Underline + deleted: Deleted + code: Inline Code + heading1: Heading 1 + heading2: Heading 2 + heading3: Heading 3 + unordered_list: Unordered List + ordered_list: Ordered List + quote: Quote + unquote: Unquote + preformatted_text: Preformatted Text + wiki_link: Link to a Wiki page + image: Image + sharing: + share: Share + selected_count: "%{count} selected" + selection: + mixed: Mixed + work_packages: + bulk_actions: + edit: Bulk edit + delete: Bulk delete + duplicate: Bulk duplicate + move: Bulk change of project + button_clear: Clear + comment_added: The comment was successfully added. + comment_send_failed: An error has occurred. Could not submit the comment. + comment_updated: The comment was successfully updated. + confirm_edit_cancel: Are you sure you want to cancel editing the work package? + description_filter: Filter + description_enter_text: Enter text + description_options_hide: Hide options + description_options_show: Show options + edit_attribute: "%{attribute} - Edit" + key_value: "%{key}: %{value}" + label_enable_multi_select: Enable multiselect + label_disable_multi_select: Disable multiselect + label_filter_add: Add filter + label_filter_by_text: Filter by text + label_options: Options + label_column_multiselect: 'Combined dropdown field: Select with arrow keys, confirm selection with enter, delete with backspace' + message_error_during_bulk_delete: An error occurred while trying to delete work packages. + message_successful_bulk_delete: Successfully deleted work packages. + message_successful_show_in_fullscreen: Click here to open this work package in fullscreen view. + message_view_spent_time: Show spent time for this work package + message_work_package_read_only: Work package is locked in this status. No attribute other than status can be altered. + message_work_package_status_blocked: Work package status is not writable due to closed status and closed version being assigned. + placeholder_filter_by_text: Subject, description, comments, ... + progress: + title: Work estimates and progress + baseline: + addition_label: Added to view within the comparison time period + removal_label: Removed from view within the comparison time period + modification_label: Modified within the comparison time period + column_incompatible: This column does not show changes in Baseline mode. + filters: + title: Filter work packages + baseline_incompatible: This filter attribute is not taken into consideration in Baseline mode. + baseline_warning: Baseline mode is on but some of your active filters are not included in the comparison. + inline_create: + title: Click here to add a new work package to this list + create: + title: New work package + header: New %{type} + header_no_type: New work package (Type not yet set) + header_with_parent: 'New %{type} (Child of %{parent_type} #%{id})' + button: Create + duplicate: + title: Duplicate work package + hierarchy: + show: Show hierarchy mode + hide: Hide hierarchy mode + toggle_button: Click to toggle hierarchy mode. + leaf: Work package leaf at level %{level}. + children_collapsed: Hierarchy level %{level}, collapsed. Click to show the filtered children + children_expanded: Hierarchy level %{level}, expanded. Click to collapse the filtered children + faulty_query: + title: Work packages could not be loaded. + description: Your view is erroneous and could not be processed. + no_results: + title: No work packages to display. + description: Either none have been created or all work packages are filtered out. + limited_results: Only %{count} work packages can be shown in manual sorting mode. Please reduce the results by filtering, or switch to automatic sorting. + property_groups: + details: Details + people: People + estimatesAndTime: Estimates & Time + other: Other + properties: + assignee: Assignee + author: Author + createdAt: Created on + description: Description + date: Date + percentComplete: "% Complete" + percentCompleteAlternative: Progress + dueDate: Finish date + duration: Duration + spentTime: Spent time + category: Category + percentageDone: Percentage done + priority: Priority + projectName: Project + remainingWork: Remaining work + remainingWorkAlternative: Remaining hours + responsible: Responsible + startDate: Start date + status: Status + subject: Subject + subproject: Subproject + title: Title + type: Type + updatedAt: Updated on + versionName: Version + version: Version + work: Work + workAlternative: Estimated time + remainingTime: Remaining work + default_queries: + manually_sorted: New manually sorted query + latest_activity: Latest activity + created_by_me: Created by me + assigned_to_me: Assigned to me + recently_created: Recently created + all_open: All open + overdue: Overdue + summary: Summary + shared_with_users: Shared with users + shared_with_me: Shared with me + jump_marks: + pagination: Jump to table pagination + label_pagination: Click here to skip over the work packages table and go to pagination + content: Jump to content + label_content: Click here to skip over the menu and go to the content + placeholders: + default: "-" + date: Select date + query: + column_names: Columns + group_by: Group results by + group: Group by + group_by_disabled_by_hierarchy: Group by is disabled due to the hierarchy mode being active. + hierarchy_disabled_by_group_by: Hierarchy mode is disabled due to results being grouped by %{column}. + sort_ascending: Sort ascending + sort_descending: Sort descending + move_column_left: Move column left + move_column_right: Move column right + hide_column: Hide column + insert_columns: Insert columns + filters: Filters + display_sums: Display Sums + confirm_edit_cancel: Are you sure you want to cancel editing the name of this view? Title will be set back to previous value. + click_to_edit_query_name: Click to edit title of this view. + rename_query_placeholder: Name of this view + star_text: Mark this view as favorite and add to the saved views sidebar on the left. + public_text: 'Publish this view, allowing other users to access your view. Users with the ''Manage public views'' permission can modify or remove public query. This does not affect the visibility of work package results in that view and depending on their permissions, users may see different results. + + ' + errors: + unretrievable_query: Unable to retrieve view from URL + not_found: There is no such view + duplicate_query_title: Name of this view already exists. Change anyway? + text_no_results: No matching views were found. + reminders: + button_label: Set reminder + title: + new: Set a reminder + edit: Edit reminder + subtitle: You will receive a notification for this work package at the chosen time. + presets: + tomorrow: Tomorrow + three_days: In 3 days + week: In a week + month: In a month + custom: At a particular date/time + scheduling: + is_parent: The dates of this work package are automatically deduced from its children. Activate 'Manual scheduling' to set the dates. + is_switched_from_manual_to_automatic: The dates of this work package may need to be recalculated after switching from manual to automatic scheduling due to relationships with other work packages. + sharing: + title: Share work package + show_all_users: Show all users with whom the work package has been shared with + table: + configure_button: Configure work package table + summary: Table with rows of work package and columns of work package attributes. + text_inline_edit: Most cells of this table are buttons that activate inline-editing functionality of that attribute. + text_sort_hint: With the links in the table headers you can sort, group, reorder, remove and add table columns. + text_select_hint: Select boxes should be opened with 'ALT' and arrow keys. + table_configuration: + button: Configure this work package table + choose_display_mode: Display work packages as + modal_title: Work package table configuration + embedded_tab_disabled: This configuration tab is not available for the embedded view you are editing. + default: default + display_settings: Display settings + default_mode: Flat list + hierarchy_mode: Hierarchy + hierarchy_hint: All filtered table results will be augmented with their ancestors. Hierarchies can be expanded and collapsed. + display_sums_hint: Display sums of all summable attributes in a row below the table results. + show_timeline_hint: Show an interactive gantt chart on the right side of the table. You can change its width by dragging the divider between table and gantt chart. + highlighting: Highlighting + highlighting_mode: + description: Highlight with color + none: No highlighting + inline: Highlighted attribute(s) + inline_all: All attributes + entire_row_by: Entire row by + status: Status + priority: Priority + type: Type + sorting_mode: + description: 'Chose the mode to sort your Work packages:' + automatic: Automatic + manually: Manually + warning: You will lose your previous sorting when activating the automatic sorting mode. + columns_help_text: Use the input field above to add columns to your table view. You can drag and drop the columns to reorder them. + relation_filters: + filter_work_packages_by_relation_type: Filter work packages by relation type + tabs: + overview: Overview + activity: Activity + relations: Relations + watchers: Watchers + files: Files + time_relative: + days: days + weeks: weeks + months: months + toolbar: + settings: + configure_view: Configure view + columns: Columns + sort_by: Sort by + group_by: Group by + display_sums: Display sums + display_hierarchy: Display hierarchy + hide_hierarchy: Hide hierarchy + hide_sums: Hide sums + save: Save + save_as: Save as + export: Export + visibility_settings: Visibility settings + share_calendar: Subscribe to calendar + page_settings: Rename view + delete: Delete + filter: Filter + unselected_title: Work package + search_query_label: Search saved views + modals: + label_name: Name + label_delete_page: Delete current page + button_apply: Apply + button_save: Save + button_submit: Submit + button_cancel: Cancel + button_delete: Delete + form_submit: + title: Confirm to continue + text: Are you sure you want to perform this action? + destroy_time_entry: + title: Confirm deletion of time entry + text: Are you sure you want to delete the following time entry? + notice_no_results_to_display: No visible results to display. + notice_successful_create: Successful creation. + notice_successful_delete: Successful deletion. + notice_successful_update: Successful update. + notice_job_started: job started. + no_job_id: No job ID returned from server. + invalid_job_response: Invalid response from server. + notice_bad_request: Bad Request. + relations: + empty: No relation exists + remove: Remove relation + inplace: + button_edit: "%{attribute}: Edit" + button_save: "%{attribute}: Save" + button_cancel: "%{attribute}: Cancel" + button_save_all: Save + button_cancel_all: Cancel + link_formatting_help: Text formatting help + btn_preview_enable: Preview + btn_preview_disable: Disable preview + null_value_label: No value + clear_value_label: "-" + errors: + required: "%{field} cannot be empty" + number: "%{field} is not a valid number" + maxlength: "%{field} cannot contain more than %{maxLength} digit(s)" + minlength: "%{field} cannot contain less than %{minLength} digit(s)" + messages_on_field: 'This field is invalid: %{messages}' + error_could_not_resolve_version_name: Couldn't resolve version name + error_could_not_resolve_user_name: Couldn't resolve user name + error_attachment_upload: 'File failed to upload: %{error}' + error_attachment_upload_permission: You don't have the permission to upload files on this resource. + units: + workPackage: + one: work package + other: work packages + child_work_packages: + one: one child work package + other: "%{count} work package children" + hour_string: "%{hours} h" + hour: + one: 1 h + other: "%{count} h" + zero: 0 h + day: + one: 1 day + other: "%{count} days" + zero: 0 days + word: + one: 1 word + other: "%{count} words" + zen_mode: + button_activate: Activate zen mode + button_deactivate: Deactivate zen mode + global_search: + all_projects: In all projects + close_search: Close search + items_available: "%{count} items available" + direct_hit_available: Work package with exact ID found. Press Enter to open it. + current_project_and_all_descendants: In this project + subprojects + current_project: In this project + recently_viewed: Recently viewed + search_placeholder_expanded: Search work packages by subject, project, type, status or ID + title: + all_projects: all projects + project_and_subprojects: and all subprojects + search_for: Search for + views: + card: Cards + list: Table + timeline: Gantt + invite_user_modal: + invite: Invite + placeholder_add_tag: Create placeholder user + exclusion_info: + modal: + title: Status excluded from hierarchy totals + content: The status '%{status_name}' has been configured to be excluded from hierarchy totals of Work, Remaining work, and % Complete. The totals do not take this value into account. + favorite_projects: + no_results: You have no favorite projects + no_results_subtext: Add one or multiple projects as favorite through their overview or in a project list. + include_projects: + toggle_title: Include projects + title: Projects + clear_selection: Clear selection + apply: Apply + selected_filter: + all: All projects + selected: Only selected + search_placeholder: Search projects... + search_placeholder_favorites: Search favorites... + include_subprojects: Include all sub-projects + tooltip: + include_all_selected: Project already included since Include all sub-projects is enabled. + current_project: This is the current project you are in. + does_not_match_search: Project does not match the search criteria. + no_results: No project matches your search criteria. + no_favorite_results: No favorite project matches your search criteria. + include_workspaces: + search_placeholder: Search... + types: + program: Program + portfolio: Portfolio + baseline: + toggle_title: Baseline + clear: Clear + apply: Apply + header_description: Highlight changes made to this list since any point in the past. + show_changes_since: Show changes since + help_description: Reference time zone for the baseline. + time_description: 'In your local time: %{datetime}' + time: Time + from: From + to: To + drop_down: + none: "-" + yesterday: yesterday + last_working_day: last working day + last_week: last week + last_month: last month + a_specific_date: a specific date + between_two_specific_dates: between two specific dates + legends: + changes_since: Changes since + changes_between: Changes between + now_meets_filter_criteria: Now meets filter criteria + no_longer_meets_filter_criteria: No longer meets filter criteria + maintained_with_changes: Maintained with changes + in_your_timezone: 'In your local timezone:' + icon_tooltip: + added: Added to view within the comparison time period + removed: Removed from view within the comparison time period + changed: Maintained with modifications + forms: + submit_success_message: The form was successfully submitted + load_error_message: There was an error loading the form + validation_error_message: Please fix the errors present in the form + advanced_settings: Advanced settings + spot: + filter_chip: + remove: Remove + drop_modal: + focus_grab: This is a focus anchor for modals. Press shift+tab to go back to the modal trigger element. + close: Close modal + open_project_storage_modal: + waiting_title: + timeout: Timeout + waiting_subtitle: + network_off: There is a network problem. + network_on: Network is back. We are trying. + projects: + identifier_suggestion: + loading: Loading suggestion... + set_name_first: Please set the name first. diff --git a/config/locales/crowdin/js-id.yml b/config/locales/crowdin/js-id.yml index 292ae98be47..5ec2320e44f 100644 --- a/config/locales/crowdin/js-id.yml +++ b/config/locales/crowdin/js-id.yml @@ -202,16 +202,7 @@ id: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Isian kustom - inactive: Tidak aktif - drag_to_activate: Tarik field dari sini untuk mengaktifkkannya - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: Grup baru - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Disetel ulang ke kondisi bawaan working_days: calendar: empty_state_header: Non-working days @@ -613,18 +604,6 @@ id: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: Nama %{group} digunakan lebih dari satu kali. Nama grup harus unik. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Setel ulang konfigurasi formulir - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: Informasi lebih lanjut - nevermind: Tidak masalah time_entry: work_package_required: Requires selecting a work package first. title: Catatan waktu diff --git a/config/locales/crowdin/js-it.yml b/config/locales/crowdin/js-it.yml index 510689b4e83..4df90362d67 100644 --- a/config/locales/crowdin/js-it.yml +++ b/config/locales/crowdin/js-it.yml @@ -202,16 +202,7 @@ it: text: "[Placeholder] Calendario incorporato" admin: type_form: - custom_field: Campo personalizzato - inactive: Inattivo - drag_to_activate: Trascina i campi da qui per attivarli - add_group: Aggiungi gruppo di attributi - add_table: Aggiungi la tabella dei pacchetti di lavoro correlati edit_query: Modifica query - new_group: Nuovo gruppo - delete_group: Elimina gruppo - remove_attribute: Rimuovi dal gruppo - reset_to_defaults: Ripristino predefinite working_days: calendar: empty_state_header: Giorni non lavorativi @@ -614,18 +605,6 @@ it: breadcrumb: Percorso di navigazione text_data_lost: Tutti i dati inseriti andranno persi. text_user_wrote: "%{value} ha scritto:" - types: - attribute_groups: - error_duplicate_group_name: Il nome %{group} è usato più di una volta. I nomi dei gruppi devono essere univoci. - error_no_table_configured: Si prega di configurare una tabella per %{group}. - reset_title: Reimposta la configurazione del modulo - confirm_reset: 'Attenzione: vuoi reimpostare la configurazione del modulo? Tutti gli attributi torneranno al loro gruppo predefinito e TUTTI i campi personalizzati verranno disattivati. - - ' - upgrade_to_ee: Passa all'edizione Enterprise in sede - upgrade_to_ee_text: Wow! Se hai bisogno di questa aggiunta sei un vero professionista! Ti andrebbe di aiutare noi sviluppatori OpenSource diventando un cliente dell'edizione Enterprise? - more_information: Altre informazioni - nevermind: Lascia stare time_entry: work_package_required: Richiede prima di selezionare una macro-attività. title: Registra l'orario effettuato diff --git a/config/locales/crowdin/js-ja.yml b/config/locales/crowdin/js-ja.yml index e92442dacf1..9393c58aefe 100644 --- a/config/locales/crowdin/js-ja.yml +++ b/config/locales/crowdin/js-ja.yml @@ -202,16 +202,7 @@ ja: text: "[Placeholder] 埋め込みカレンダー" admin: type_form: - custom_field: カスタムフィールド - inactive: 非アクティブ - drag_to_activate: ここからフィールドをドラッグして、有効にします - add_group: 属性グループを追加する - add_table: 関係するワークパッケージのテーブルを追加する edit_query: クエリの編集 - new_group: 新規グループ - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: デフォルトに戻す working_days: calendar: empty_state_header: 休業日 @@ -613,18 +604,6 @@ ja: breadcrumb: パンくず text_data_lost: 入力されたデータはすべて失われる。 text_user_wrote: "%{value} が書き込みました:" - types: - attribute_groups: - error_duplicate_group_name: 名前 %{group} が複数回使用されています。グループ名は一意にする必要があります。 - error_no_table_configured: "%{group} のテーブルを設定してください。" - reset_title: フォーム設定をリセット - confirm_reset: '警告:フォーム設定をリセットしてもよろしいですか? これにより、属性がデフォルトのグループにリセットされ、すべてのカスタムフィールドが無効になります。   コンテキスト| リクエストコンテキスト - - ' - upgrade_to_ee: エンタープライズ・オンプレミス版へのアップグレード - upgrade_to_ee_text: すごい!このアドオンが必要なら、あなたはスーパープロです!エンタープライズ版のクライアントになって、私たちオープンソース開発者をサポートしていただけませんか? - more_information: 詳細情報 - nevermind: 無視 time_entry: work_package_required: 最初にワークパッケージを選択する必要があります。 title: 時間を記録 diff --git a/config/locales/crowdin/js-ka.yml b/config/locales/crowdin/js-ka.yml index 5d5a8338b65..ca5bd2a9c76 100644 --- a/config/locales/crowdin/js-ka.yml +++ b/config/locales/crowdin/js-ka.yml @@ -202,16 +202,7 @@ ka: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: მორგებადი ველი - inactive: არააქტიური - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: მოთხოვნის ჩასწორება - new_group: ახალი ჯგუფი - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ ka: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: გადავიფიქრე time_entry: work_package_required: Requires selecting a work package first. title: ჟურნალში დროის ჩაწერა diff --git a/config/locales/crowdin/js-kk.yml b/config/locales/crowdin/js-kk.yml index 35baaecdaa8..f63063b483a 100644 --- a/config/locales/crowdin/js-kk.yml +++ b/config/locales/crowdin/js-kk.yml @@ -202,16 +202,7 @@ kk: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Custom field - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: New group - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ kk: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Log time diff --git a/config/locales/crowdin/js-ko.yml b/config/locales/crowdin/js-ko.yml index f7c1dfe2074..0988c768be6 100644 --- a/config/locales/crowdin/js-ko.yml +++ b/config/locales/crowdin/js-ko.yml @@ -202,16 +202,7 @@ ko: text: "[Placeholder] 포함 달력" admin: type_form: - custom_field: 사용자 정의 필드 - inactive: 비활성화 - drag_to_activate: 여기에서 필드를 끌어와서 활성화 - add_group: 특성 그룹 추가 - add_table: 관련 작업 패키지의 테이블 추가 edit_query: 쿼리 편집 - new_group: 새 그룹 - delete_group: 그룹 삭제 - remove_attribute: 그룹에서 제거 - reset_to_defaults: 기본값으로 초기화 working_days: calendar: empty_state_header: 휴무일 @@ -613,18 +604,6 @@ ko: breadcrumb: 이동 경로 text_data_lost: 입력한 모든 데이터가 손실됩니다. text_user_wrote: "%{value}이(가) 작성함:" - types: - attribute_groups: - error_duplicate_group_name: "%{group} 이라는 그룹 이름은 한 번 이상 사용되었습니다. 그룹 이름은 중복될 수 없습니다." - error_no_table_configured: "%{group}에 대한 테이블을 구성하세요." - reset_title: 구성 양식 초기화 - confirm_reset: '경고: 양식 구성을 재설정하시겠습니까? 그러면 특성이 기본 그룹으로 재설정되고, 모든 사용자 지정 필드가 비활성화됩니다. - - ' - upgrade_to_ee: Enterprise on-premises Edition으로 업그레이드 - upgrade_to_ee_text: 와우! 만약 이 추가 기능이 필요하다면 여러분은 진짜 프로입니다! Enterprise Edition 고객이 되어서 OpenSource 개발자들을 지원할 생각이 있으신가요? - more_information: 더보기 - nevermind: 신경쓰지 마세요 time_entry: work_package_required: 먼저 작업 패키지를 선택해야 합니다. title: 작업시간 기록 diff --git a/config/locales/crowdin/js-lt.yml b/config/locales/crowdin/js-lt.yml index a2123dcb83a..a923608bdec 100644 --- a/config/locales/crowdin/js-lt.yml +++ b/config/locales/crowdin/js-lt.yml @@ -202,16 +202,7 @@ lt: text: "[Placeholder] Įterptinis kalendorius" admin: type_form: - custom_field: Savas laukas - inactive: Neaktyvus - drag_to_activate: Vilkite laukelius iš čia, kad juos aktyvuotumėte - add_group: Pridėti atributų grupę - add_table: Pridėti susijusių darbų paketų lentelę edit_query: Keisti užklausą - new_group: Nauja grupė - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Atstatyti į numatytuosius working_days: calendar: empty_state_header: Nedarbo dienos @@ -616,18 +607,6 @@ lt: breadcrumb: Susijusi informacija text_data_lost: Visa įvesta informacija bus prarasta. text_user_wrote: "%{value} parašė:" - types: - attribute_groups: - error_duplicate_group_name: "%{group} grupės pavadinimas naudotas daugiau nei kartą. Grupių pavadinimai privalo būti unikalūs." - error_no_table_configured: Prašome sukonfigūruoti lentelę, skirtą grupei %{group}. - reset_title: Atstatyti formos konfigūraciją - confirm_reset: 'Įspėjimas: Ar tikrai norite atstatyti pradinę formos konfigūraciją? Atributai bus perkelti į jų numatytąsias grupes ir VISUS savi laukeliai bus išjungti. - - ' - upgrade_to_ee: Patobulinkite į Enteprise vietinę versiją - upgrade_to_ee_text: Oho! Jei Jums reikia šio priedo, esate super pro! Ar nenorėtumėte paremti mus, OpenSource (atviro kodo) kūrėjus, tapdami Enterprise versijos klientu? - more_information: Daugiau informacijos - nevermind: Nesvarbu time_entry: work_package_required: Reikia pirma pasirinkti darbo paketą. title: Registruoti laiką diff --git a/config/locales/crowdin/js-lv.yml b/config/locales/crowdin/js-lv.yml index 1d0e6703022..7d0d4d9fc26 100644 --- a/config/locales/crowdin/js-lv.yml +++ b/config/locales/crowdin/js-lv.yml @@ -202,16 +202,7 @@ lv: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Custom field - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: New group - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -615,18 +606,6 @@ lv: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Log time diff --git a/config/locales/crowdin/js-mn.yml b/config/locales/crowdin/js-mn.yml index 14ec7f4832a..56420ec5830 100644 --- a/config/locales/crowdin/js-mn.yml +++ b/config/locales/crowdin/js-mn.yml @@ -202,16 +202,7 @@ mn: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Custom field - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: New group - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ mn: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Log time diff --git a/config/locales/crowdin/js-ms.yml b/config/locales/crowdin/js-ms.yml index 6de174f5ed9..a221617a04d 100644 --- a/config/locales/crowdin/js-ms.yml +++ b/config/locales/crowdin/js-ms.yml @@ -202,16 +202,7 @@ ms: text: "[Placeholder] Kalendar yang tersemat" admin: type_form: - custom_field: Ruang tersuai - inactive: Tidak aktif - drag_to_activate: Tarik ruangan dari sini untuk mengaktifkan mereka - add_group: Tambah kumpulan atribut - add_table: Tambah jadual pakej kerja yang berkaitan edit_query: Edit pertanyaan - new_group: Kumpulan baharu - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Set semula ke default working_days: calendar: empty_state_header: Hari tidak bekerja @@ -613,18 +604,6 @@ ms: breadcrumb: Breadcrumb text_data_lost: Semua data yang dimasukkan akan hilang. text_user_wrote: "%{value} menulis:" - types: - attribute_groups: - error_duplicate_group_name: Nama %{group} digunakan lebih dari sekali. Nama kumpulan mestilah unik. - error_no_table_configured: Sila konfigurasi jadual untuk %{group}. - reset_title: Set semula konfigurasi borang - confirm_reset: 'Amaran: Adakah anda pasti anda ingin set semula konfigurasi borang? Ini akan set semula atribut kumpulan asal mereka dan menyahdayakan SEMUA ruang tersuai. - - ' - upgrade_to_ee: Naik taraf edisi Enterprise di premis - upgrade_to_ee_text: Wah! Jika anda perlukan tambahan ini anda sungguh pro! Bolehkah anda menyokong kami, pembangun OpenProject, dengan menjadi pelanggan edisi Enterprise? - more_information: Maklumat lanjut - nevermind: Tidak mengapa time_entry: work_package_required: Memerlukan pemilihan pakej kerja dahulu. title: Masa log diff --git a/config/locales/crowdin/js-ne.yml b/config/locales/crowdin/js-ne.yml index 2cccded24bc..9c20079dc93 100644 --- a/config/locales/crowdin/js-ne.yml +++ b/config/locales/crowdin/js-ne.yml @@ -202,16 +202,7 @@ ne: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Custom field - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: सम्बन्धित कार्य प्याकेजहरूको तालिका थप्नुहोस् edit_query: Edit query - new_group: New group - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ ne: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: सबै भन्दा पहिले कार्य प्याकेज चयन गर्न आवश्यक छ। title: Log time diff --git a/config/locales/crowdin/js-nl.yml b/config/locales/crowdin/js-nl.yml index ec460500b61..2f4666e8d4f 100644 --- a/config/locales/crowdin/js-nl.yml +++ b/config/locales/crowdin/js-nl.yml @@ -202,16 +202,7 @@ nl: text: "[Placeholder] ingesloten kalender" admin: type_form: - custom_field: Aangepast veld - inactive: Inactief - drag_to_activate: Sleep velden uit hier om ze te activeren - add_group: Toevoegen van nieuwe attribuutgroepen - add_table: Tabel van verwante werkpakketten toevoegen edit_query: Zoekopdracht bewerken - new_group: Nieuwe groep - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Standaardinstellingen herstellen working_days: calendar: empty_state_header: Vrije dagen @@ -614,18 +605,6 @@ nl: breadcrumb: Breadcrumb text_data_lost: Alle ingevoerde gegevens zullen verloren gaan. text_user_wrote: "%{value} schreef:" - types: - attribute_groups: - error_duplicate_group_name: De groep naam %{group} word meer dan één keer gebruikt. Groep namen moeten uniek zijn. - error_no_table_configured: Gelieve een tabel voor %{group} instellen. - reset_title: Reset formulier configuratie - confirm_reset: 'Waarschuwing: Weet u zeker dat u de configuratie van het formulier wilt herstellen? Dit herstelt de kenmerken hun standaardgroep, deselecteerd de selectievakjes van de zichtbaarheid, en schakeld alle aangepaste velden uit. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: Meer informatie - nevermind: Geen aandacht time_entry: work_package_required: Vereist eerst een werkpakket te selecteren. title: Gelogde tijd diff --git a/config/locales/crowdin/js-no.yml b/config/locales/crowdin/js-no.yml index 5595dae373e..7bf04e6a5af 100644 --- a/config/locales/crowdin/js-no.yml +++ b/config/locales/crowdin/js-no.yml @@ -202,16 +202,7 @@ text: "[Placeholder] Innbygget kalender" admin: type_form: - custom_field: Egendefinert felt - inactive: Inaktiv - drag_to_activate: Dra feltene herfra for å aktivere dem - add_group: Legg til egenskapsgruppe - add_table: Legg til tabell over tilknyttede arbeidspakker edit_query: Rediger spørring - new_group: Ny gruppe - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Tilbakestill til standard working_days: calendar: empty_state_header: Arbeidsfrie dager @@ -614,18 +605,6 @@ breadcrumb: Brødsmule text_data_lost: Alle angitte data vil gå tapt. text_user_wrote: "%{value} skrev:" - types: - attribute_groups: - error_duplicate_group_name: Navnet %{group} brukes flere ganger. Gruppenavn må være unike. - error_no_table_configured: Vennligst konfigurer en tabell for %{group}. - reset_title: Tilbakestill konfigurasjon for skjema - confirm_reset: 'Advarsel: Er du sikker på at du vil tilbakestille skjemakonfigurasjonen? Dette vil tilbakestille egenskaper til deres standard gruppe og deaktivere ALLE egendefinerte felt. - - ' - upgrade_to_ee: Oppgradere til Enterprise on-premises-utgave - upgrade_to_ee_text: Wow! Hvis du trenger dette tillegget er en superpro! Vil du støtte oss OpenSource-utviklere ved å bli en Enterpris-utgave-kunde? - more_information: Mer informasjon - nevermind: Glem det time_entry: work_package_required: Valg av arbeidspakke er påkrevd title: Logg tid diff --git a/config/locales/crowdin/js-pl.yml b/config/locales/crowdin/js-pl.yml index c77d8f5a2f7..0dcd8728f70 100644 --- a/config/locales/crowdin/js-pl.yml +++ b/config/locales/crowdin/js-pl.yml @@ -202,16 +202,7 @@ pl: text: "[Placeholder] Wbudowany kalendarz" admin: type_form: - custom_field: Pola niestandardowe - inactive: Nieaktywny - drag_to_activate: Aby aktywować pola, przeciągaj je stąd - add_group: Dodaj grupę atrybutów - add_table: Dodaj tabelę powiązanych pakietów roboczych edit_query: Edytuj zapytanie - new_group: Nowa Grupa - delete_group: Usuń grupę - remove_attribute: Usuń z grupy - reset_to_defaults: Resetuj do ustawień domyślnych working_days: calendar: empty_state_header: Dni nierobocze @@ -616,18 +607,6 @@ pl: breadcrumb: Ścieżka text_data_lost: Wszystkie wprowadzone dane zostaną utracone. text_user_wrote: "%{value} napisał(a):" - types: - attribute_groups: - error_duplicate_group_name: Nazwa %{group} jest użyta więcej niż raz. Nazwy grup muszą być unikalne. - error_no_table_configured: Skonfiguruj tabelę grupy %{group}. - reset_title: Zresetuj konfigurację formularza - confirm_reset: 'Ostrzeżenie: czy na pewno chcesz zresetować konfigurację formularza? Przywróci to atrybuty do ich grupy domyślnej i wyłączy WSZYSTKIE pola niestandardowe. - - ' - upgrade_to_ee: Zaktualizuj do wersji Enterprise On-Premises - upgrade_to_ee_text: Znakomicie! Jeśli potrzebujesz tego dodatku, jesteś superprofesjonalistą! Może wesprzesz nas jako deweloperów OpenSource, zostając nabywcą wersji Enterprise? - more_information: Więcej informacji - nevermind: Nieważne time_entry: work_package_required: Wymaga wybrania najpierw pakietu roboczego. title: Rejestruj czas pracy diff --git a/config/locales/crowdin/js-pt-BR.yml b/config/locales/crowdin/js-pt-BR.yml index 4a4c43597f3..2b7c5e356ea 100644 --- a/config/locales/crowdin/js-pt-BR.yml +++ b/config/locales/crowdin/js-pt-BR.yml @@ -202,16 +202,7 @@ pt-BR: text: "[Marcador de posição] Calendário embutido" admin: type_form: - custom_field: Campo personalizado - inactive: Inativo - drag_to_activate: Arraste campos daqui para ativá-los - add_group: Adicionar grupo de atributo - add_table: Adicionar tabela de pacotes de trabalho relacionados edit_query: Editar consulta - new_group: Novo grupo - delete_group: Excluir grupo - remove_attribute: Remover do grupo - reset_to_defaults: Voltar à configuração original working_days: calendar: empty_state_header: Dias não úteis @@ -614,18 +605,6 @@ pt-BR: breadcrumb: Trilha de navegação text_data_lost: Todos os dados inseridos serão perdidos. text_user_wrote: "%{value} escreveu:" - types: - attribute_groups: - error_duplicate_group_name: O nome %{group} foi usado mais de uma vez. Nomes de Grupos devem ser únicos. - error_no_table_configured: Por favor, configure uma tabela para %{group}. - reset_title: Voltar para a configuração padrão do formulário - confirm_reset: 'Aviso: Tem certeza de que deseja redefinir a configuração de formulário? Isto irá redefinir os atributos para o grupo padrão e desativar TODOS os campos personalizados. - - ' - upgrade_to_ee: Atualizar para a edição Enterprise local - upgrade_to_ee_text: Uau! Se você precisa deste complemento, é porque é um super profissional! Gostaria de mostrar seu suporte aos desenvolvedores de código aberto tornando-se um cliente da edição Enterprise? - more_information: Mais informações - nevermind: Deixa pra lá time_entry: work_package_required: É necessário selecionar primeiro um pacote de trabalho. title: Registro de tempo diff --git a/config/locales/crowdin/js-pt-PT.yml b/config/locales/crowdin/js-pt-PT.yml index 1703ba94717..1468b2381e7 100644 --- a/config/locales/crowdin/js-pt-PT.yml +++ b/config/locales/crowdin/js-pt-PT.yml @@ -202,16 +202,7 @@ pt-PT: text: "[Placeholder] Calendário embutido" admin: type_form: - custom_field: Campo personalizado - inactive: Inativo - drag_to_activate: Arraste campos daqui para ativá-los - add_group: Adicionar grupo de atributos - add_table: Adicionar tabela de pacotes de trabalho relacionados edit_query: Editar consulta - new_group: Novo Grupo - delete_group: Eliminar grupo - remove_attribute: Remover do grupo - reset_to_defaults: Repor predefinições working_days: calendar: empty_state_header: Dias não úteis @@ -614,18 +605,6 @@ pt-PT: breadcrumb: Estrutural text_data_lost: Todos os dados inseridos serão perdidos. text_user_wrote: "%{value} escreveu:" - types: - attribute_groups: - error_duplicate_group_name: O nome %{group} é utilizado mais de uma vez. Os nomes de grupos têm de ser exclusivos. - error_no_table_configured: Por favor configure uma tabela para %{group}. - reset_title: Redefinir configuração do formulário - confirm_reset: 'Aviso: Tem a certeza que deseja redefinir a configuração de formulário? Isto irá redefinir os atributos para o grupo padrão e desativar TODOS os campos personalizados. - - ' - upgrade_to_ee: Aprimore para a edição Enterprise on-premises - upgrade_to_ee_text: Uau! Se precisa deste complemento é porque faz parte dos utilizadores especializados! Gostaria de apoiar os desenvolvedores de OpenSource tornando-se cliente da edição Enterprise? - more_information: Mais informação - nevermind: Não, obrigado time_entry: work_package_required: Requer que selecione um pacote de trabalho primeiro. title: Tempo de registro diff --git a/config/locales/crowdin/js-ro.yml b/config/locales/crowdin/js-ro.yml index 05b6cff6b0c..46f0c3973ff 100644 --- a/config/locales/crowdin/js-ro.yml +++ b/config/locales/crowdin/js-ro.yml @@ -202,16 +202,7 @@ ro: text: "[Placeholder] Tabelul pachetului de lucru inserat" admin: type_form: - custom_field: Câmp personalizat - inactive: Inactiv - drag_to_activate: Mutați câmpuri de aici pentru a le activa - add_group: Adaugă grup atribut - add_table: Adaugă pachet de lucru asociat edit_query: Editează interogare - new_group: Grupare nouă - delete_group: Șterge grup - remove_attribute: Elimină din grup - reset_to_defaults: Resetare AVATAR implicit working_days: calendar: empty_state_header: Zile nelucrătoare @@ -615,18 +606,6 @@ ro: breadcrumb: Breadcrumb text_data_lost: Toate datele introduse vor fi pierdute. text_user_wrote: "%{value} a scris:" - types: - attribute_groups: - error_duplicate_group_name: Numele %{group} este utilizat de mai multe ori. Fiecare grup trebuie să aibă un nume unic. - error_no_table_configured: Vă rugăm să configurați un tabel pentru %{group}. - reset_title: Resetare configurare formular - confirm_reset: 'Atenție: Ești sigur că vrei să resetați conjurația formularului? Această acțiune va reseta toate atributele înapoi la grupul implicit și va dezactiva TOATE câmpurile personalizate. - - ' - upgrade_to_ee: Actualizare la ediția Enterprise on-premise - upgrade_to_ee_text: Wow! Dacă ai nevoie de acest add-on ești un super profesionist! Vrei să ne sprijini pe noi, dezvoltatorii OpenSource, devenind client al ediției Enterprise? - more_information: Mai multe informatii - nevermind: Asta e... time_entry: work_package_required: Necesită să se selecteze mai întâi un pachet de lucru. title: Registru timp diff --git a/config/locales/crowdin/js-ru.yml b/config/locales/crowdin/js-ru.yml index 2420a7ae5af..76f0dfabfc1 100644 --- a/config/locales/crowdin/js-ru.yml +++ b/config/locales/crowdin/js-ru.yml @@ -202,16 +202,7 @@ ru: text: "[Placeholder] Встроенный календарь" admin: type_form: - custom_field: Настраиваемое поле - inactive: Неактивен - drag_to_activate: Чтобы включить поля, перетащите их отсюда - add_group: Добавить группу атрибутов - add_table: Добавить таблицу связанных пакетов работ edit_query: Редактировать запрос - new_group: Новая группа - delete_group: Удалить группу - remove_attribute: Удалить из группы - reset_to_defaults: Восстановить значения по умолчанию working_days: calendar: empty_state_header: Нерабочие дни @@ -618,18 +609,6 @@ ru: breadcrumb: Навигационная цепочка text_data_lost: Все введенные данные будут потеряны. text_user_wrote: "%{value} написал:" - types: - attribute_groups: - error_duplicate_group_name: Имя %{group} используется более одного раза. Имена групп должны быть уникальными. - error_no_table_configured: Пожалуйста, настройте таблицу для %{group}. - reset_title: Сбросить настройки формы - confirm_reset: 'Предупреждение: хотите сбросить форму? Это сбросит атрибуты к первоначальным значениям группы и запретит правку настраиваемых полей. - - ' - upgrade_to_ee: Обновление до корпоративной версии - upgrade_to_ee_text: Уууууууууу! Если вам нужен этот дополнение, то вы бы хотели поддержать нас разработчиков OpenSource, став клиентом корпоративной версии? - more_information: Больше информации - nevermind: Неважно time_entry: work_package_required: Сначала необходимо выбрать пакет работ. title: Журналировать время diff --git a/config/locales/crowdin/js-rw.yml b/config/locales/crowdin/js-rw.yml index f744ef2fc9f..e7a3f810ce2 100644 --- a/config/locales/crowdin/js-rw.yml +++ b/config/locales/crowdin/js-rw.yml @@ -202,16 +202,7 @@ rw: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Custom field - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: New group - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ rw: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Log time diff --git a/config/locales/crowdin/js-si.yml b/config/locales/crowdin/js-si.yml index 36836526eb9..fb18bcf1b2c 100644 --- a/config/locales/crowdin/js-si.yml +++ b/config/locales/crowdin/js-si.yml @@ -202,16 +202,7 @@ si: text: "[Placeholder] කාවැද්දූ දින දර්ශනය" admin: type_form: - custom_field: අභිරුචි ක්ෂේත්රය - inactive: අක්රිය - drag_to_activate: ඒවා සක්රිය කිරීම සඳහා මෙතැන් සිට ක්ෂේත්ර ඇදගෙන යන්න - add_group: ගුණාංග පිරිසක් එකතු කරන්න - add_table: අදාළ වැඩ පැකේජ වගුව එකතු කරන්න edit_query: විමසුම සංස්කරණය කරන්න - new_group: නව කණ්ඩායම - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: පැහැර හැරීම් නැවත සකසන්න working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ si: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} ලිවීය:" - types: - attribute_groups: - error_duplicate_group_name: "%{group} නම එක් වරකට වඩා භාවිතා වේ. කණ්ඩායම් නම් අද්විතීය විය යුතුය." - error_no_table_configured: කරුණාකර %{group}සඳහා වගුවක් වින්යාස කරන්න. - reset_title: ආකෘති වින්යාසය සකසන්න - confirm_reset: 'අවවාදයයි: ආකෘති වින්යාසය නැවත සැකසීමට ඔබට අවශ්යද? මෙය ඔවුන්ගේ පෙරනිමි කණ්ඩායමට ගුණාංග නැවත සකස් කර සියලුම අභිරුචි ක්ෂේත්ර අක්රීය කරනු ඇත. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: වැඩි විස්තර - nevermind: කිසි විටෙකත් මනස time_entry: work_package_required: පළමුව වැඩ පැකේජයක් තෝරා ගැනීම අවශ්ය වේ. title: ලොග් වේලාව diff --git a/config/locales/crowdin/js-sk.yml b/config/locales/crowdin/js-sk.yml index e2890e09f9b..ba10072e1c1 100644 --- a/config/locales/crowdin/js-sk.yml +++ b/config/locales/crowdin/js-sk.yml @@ -202,16 +202,7 @@ sk: text: "[Placeholder] Vložený kalendár" admin: type_form: - custom_field: Vlastné pole - inactive: Neaktívny - drag_to_activate: Potiahnite polia z tejto oblasti a aktivujte ich - add_group: Pridať skupinu vlastností - add_table: Pridajte tabuľku súvisiacich pracovných balíčkov edit_query: Edit query - new_group: Nová skupina - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Obnoviť predvolené working_days: calendar: empty_state_header: Non-working days @@ -616,18 +607,6 @@ sk: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} napísal:" - types: - attribute_groups: - error_duplicate_group_name: Názov skupiny %{group} sa používa viackrát. Názvy skupín musia byť jedinečné. - error_no_table_configured: Prosím nastavte tabuľku pre %{group}. - reset_title: Obnoviť konfiguráciu formulára - confirm_reset: 'Upozornenie: Naozaj chcete resetovať konfiguráciu formulára? Týmto sa obnovia atribúty na ich predvolenú skupinu a vypnú sa VŠETKY vlastné polia. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: Viac informácií - nevermind: Nie ďakujem time_entry: work_package_required: Requires selecting a work package first. title: Zaprotokoluj čas diff --git a/config/locales/crowdin/js-sl.yml b/config/locales/crowdin/js-sl.yml index 0e95dc8ada6..207b3b9ad9f 100644 --- a/config/locales/crowdin/js-sl.yml +++ b/config/locales/crowdin/js-sl.yml @@ -202,16 +202,7 @@ sl: text: "[Placeholder] Vdelan koledar" admin: type_form: - custom_field: Polje po meri - inactive: Neaktiven - drag_to_activate: Povlecite polja od tod, da jih aktivirate - add_group: Dodajte skupino atributov - add_table: Dodajte tabelo povezanih delovnih paketov edit_query: Uredi poizvedbo - new_group: Nova skupina - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Ponastavi na privzete vrednosti working_days: calendar: empty_state_header: Non-working days @@ -618,18 +609,6 @@ sl: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} je napisal(a):" - types: - attribute_groups: - error_duplicate_group_name: Ime skupine %{group} je že uporabljeno. Imena skupin morajo biti unikatna. - error_no_table_configured: Prosimo konfigurirajte tabelo za %{group}. - reset_title: Po nastavi iz konfiguracije - confirm_reset: 'Opozorilo: Ali ste prepričani, da želite po nastaviti formo konfiguracije? To bo ponastavilo atribute v njihovo privzeto skupino in onemogočilo vsa polja po meri. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: Več informacij - nevermind: Pozabi time_entry: work_package_required: Najprej izberite opravilo title: Beleži čas diff --git a/config/locales/crowdin/js-sr.yml b/config/locales/crowdin/js-sr.yml index eacac083a77..b0f356a983d 100644 --- a/config/locales/crowdin/js-sr.yml +++ b/config/locales/crowdin/js-sr.yml @@ -202,16 +202,7 @@ sr: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Custom field - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: New group - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -615,18 +606,6 @@ sr: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Log time diff --git a/config/locales/crowdin/js-sv.yml b/config/locales/crowdin/js-sv.yml index f1cc8a5607e..85a31f5dcf2 100644 --- a/config/locales/crowdin/js-sv.yml +++ b/config/locales/crowdin/js-sv.yml @@ -202,16 +202,7 @@ sv: text: "[Placeholder] Inbäddad kalender" admin: type_form: - custom_field: Anpassat fält - inactive: Inaktiv - drag_to_activate: Dra fält härifrån för att aktivera dom - add_group: Lägg till attributgrupp - add_table: Lägg till tabell med relaterade arbetspaket edit_query: Redigera fråga - new_group: Ny grupp - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Återställ standardinställningar working_days: calendar: empty_state_header: Arbetsfria dagar @@ -614,18 +605,6 @@ sv: breadcrumb: Breadcrumb text_data_lost: Alla inmatade data kommer att gå förlorade. text_user_wrote: "%{value} skrev:" - types: - attribute_groups: - error_duplicate_group_name: Namnet %{group} används redan. Gruppnamn måste vara unika. - error_no_table_configured: Konfigurera en tabell för %{group}. - reset_title: Återställ formulärinställningarna - confirm_reset: 'Varning: Är du säker på att du vill återställa formulärinställningarna? Detta kommer att återställa attributen till deras standardgrupper, avmarkera kryssrutorna för synlighet samt inaktivera samtliga anpassade fält. - - ' - upgrade_to_ee: Uppgradera till Enterprise on-premise-utgåva - upgrade_to_ee_text: Wow! Om du behöver det här tillägget är du ett superproffs! Skulle du kunna stödja oss OpenSource utvecklare genom att bli en klient till Enterprise utgåvan? - more_information: Mer information - nevermind: Det var inget time_entry: work_package_required: Kräver att man först väljer ett arbetspaket. title: Logga tid diff --git a/config/locales/crowdin/js-th.yml b/config/locales/crowdin/js-th.yml index e2492cae3d4..ff0e924007c 100644 --- a/config/locales/crowdin/js-th.yml +++ b/config/locales/crowdin/js-th.yml @@ -202,16 +202,7 @@ th: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: ฟิลด์ที่กำหนดเอง - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: กลุ่มใหม่ - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -613,18 +604,6 @@ th: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} เขียนว่า:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: เวลาที่ถูกบันทึก diff --git a/config/locales/crowdin/js-tr.yml b/config/locales/crowdin/js-tr.yml index d368508ddd5..d7a2c519111 100644 --- a/config/locales/crowdin/js-tr.yml +++ b/config/locales/crowdin/js-tr.yml @@ -202,16 +202,7 @@ tr: text: "[Placeholder] Göümülü takvim" admin: type_form: - custom_field: Özel alan - inactive: Etkin değil - drag_to_activate: Alanları buraya sürükleyerek aktifleştirebilirsiniz - add_group: Özellik grubu ekle - add_table: Bağlantılı iş paketlerinin tablosunu ekle edit_query: Sorguyu düzenle - new_group: Yeni grup - delete_group: Grubu Sil - remove_attribute: Gruptan çıkar - reset_to_defaults: Varsayılanlara geri yükle working_days: calendar: empty_state_header: Çalışılmayan günler @@ -614,18 +605,6 @@ tr: breadcrumb: Gezinti Menüsü text_data_lost: Girilen tüm veriler silinecek. text_user_wrote: "%{value} demiş ki:" - types: - attribute_groups: - error_duplicate_group_name: "%{group} ismi birden fazla kullanıldı. Grup isimleri eşsiz olmalıdır." - error_no_table_configured: Lütfen %{group} için bir tablo yapılandırın. - reset_title: Form ayarlarlarını sıfırlayın - confirm_reset: 'Uyarı: Form yapılandırmasını sıfırlamak istediğinizden emin misiniz? Bu, nitelikleri varsayılan gruplarına sıfırlar ve TÜM özel alanları devre dışı bırakır. - - ' - upgrade_to_ee: Enterprise on-premises edition'a yükseltin - upgrade_to_ee_text: Vay! Bu eklentiye ihtiyacınız varsa, siz bir süper profesyonelsiniz! Enterprise sürümü müşterisi olarak biz OpenSource geliştiricilerini destekler misiniz? - more_information: Daha fazla bilgi - nevermind: Boşver time_entry: work_package_required: Önce bir iş paketi seçilmesini gerektirir. title: Zaman kaydet diff --git a/config/locales/crowdin/js-uk.yml b/config/locales/crowdin/js-uk.yml index 2277c30f7f8..ba32fbd7f61 100644 --- a/config/locales/crowdin/js-uk.yml +++ b/config/locales/crowdin/js-uk.yml @@ -202,16 +202,7 @@ uk: text: "[Placeholder] вбудований календар" admin: type_form: - custom_field: Користувацьке поле - inactive: Неактивні - drag_to_activate: Перетягніть поля звідти, щоб активувати їх - add_group: Додати групу атрибутів - add_table: Додати таблицю пов'язаних пакетів робіт edit_query: Редагувати запит - new_group: Нова група - delete_group: Видалити групу - remove_attribute: Вилучити з групи - reset_to_defaults: Скинути на типові значення working_days: calendar: empty_state_header: Неробочі дні @@ -616,18 +607,6 @@ uk: breadcrumb: Ланцюжок навігації text_data_lost: Усі введені дані буде втрачено. text_user_wrote: "%{value} написав(-ла):" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} використовується більше одного разу. Назви груп повинні бути унікальними. - error_no_table_configured: Please configure a table for %{group} - reset_title: Скидання конфігурації форми - confirm_reset: 'Попередження: Справді скинути конфігурацію форми? Це призведе до скидання атрибутів до групи за замовчуванням і вимкнення всіх користувацьких полів. - - ' - upgrade_to_ee: Перейти на локальну версію Enterprise - upgrade_to_ee_text: Оце так! Якщо вам потрібне це доповнення, ви справжній профі! Підтримайте розробників OpenSource, перейшовши на версію Enterprise! - more_information: Додаткові відомості - nevermind: Nevermind time_entry: work_package_required: Спочатку потрібно вибрати пакет робіт. title: Записування часу diff --git a/config/locales/crowdin/js-uz.yml b/config/locales/crowdin/js-uz.yml index 6fa739ffc6c..1b515a47bea 100644 --- a/config/locales/crowdin/js-uz.yml +++ b/config/locales/crowdin/js-uz.yml @@ -202,16 +202,7 @@ uz: text: "[Placeholder] Embedded calendar" admin: type_form: - custom_field: Custom field - inactive: Inactive - drag_to_activate: Drag fields from here to activate them - add_group: Add attribute group - add_table: Add table of related work packages edit_query: Edit query - new_group: New group - delete_group: Delete group - remove_attribute: Remove from group - reset_to_defaults: Reset to defaults working_days: calendar: empty_state_header: Non-working days @@ -614,18 +605,6 @@ uz: breadcrumb: Breadcrumb text_data_lost: All entered data will be lost. text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: The name %{group} is used more than once. Group names must be unique. - error_no_table_configured: Please configure a table for %{group}. - reset_title: Reset form configuration - confirm_reset: 'Warning: Are you sure you want to reset the form configuration? This will reset the attributes to their default group and disable ALL custom fields. - - ' - upgrade_to_ee: Upgrade to Enterprise on-premises edition - upgrade_to_ee_text: Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client? - more_information: More information - nevermind: Nevermind time_entry: work_package_required: Requires selecting a work package first. title: Log time diff --git a/config/locales/crowdin/js-vi.yml b/config/locales/crowdin/js-vi.yml index 4e34fc537bc..af19690a54a 100644 --- a/config/locales/crowdin/js-vi.yml +++ b/config/locales/crowdin/js-vi.yml @@ -202,16 +202,7 @@ vi: text: "[Placeholder] Lịch nhúng" admin: type_form: - custom_field: Tùy chỉnh mục - inactive: Không hoạt động - drag_to_activate: Kéo các trường từ đây để kích hoạt chúng - add_group: Thêm 1 nhóm thuộc tính - add_table: Thêm bảng các gói công việc liên quan edit_query: Chỉnh sửa truy vấn - new_group: Nhóm mới - delete_group: Xóa nhóm - remove_attribute: Xóa khỏi nhóm - reset_to_defaults: Đặt lại về mặc định working_days: calendar: empty_state_header: Những ngày không làm việc @@ -613,18 +604,6 @@ vi: breadcrumb: vụn bánh mì text_data_lost: Tất cả dữ liệu đã nhập sẽ bị mất. text_user_wrote: "%{value} đã viết:" - types: - attribute_groups: - error_duplicate_group_name: Tên %{group} được sử dụng nhiều lần. Tên nhóm phải là duy nhất. - error_no_table_configured: Vui lòng định cấu hình bảng cho %{group}. - reset_title: Đặt lại cấu hình biểu mẫu - confirm_reset: 'Cảnh báo: Bạn có chắc chắn muốn đặt lại cấu hình biểu mẫu không? Điều này sẽ đặt lại các thuộc tính về nhóm mặc định của chúng và vô hiệu hóa TẤT CẢ các trường tùy chỉnh. - - ' - upgrade_to_ee: Nâng cấp lên phiên bản Enterprise tại chỗ - upgrade_to_ee_text: Ồ! Nếu bạn cần tiện ích bổ sung này thì bạn là một siêu chuyên nghiệp! Bạn có phiền khi hỗ trợ các nhà phát triển OpenSource của chúng tôi bằng cách trở thành khách hàng phiên bản Enterprise không? - more_information: Thêm thông tin - nevermind: bỏ qua time_entry: work_package_required: Yêu cầu chọn gói công việc trước tiên. title: Đăng nhập thời gian diff --git a/config/locales/crowdin/js-zh-CN.yml b/config/locales/crowdin/js-zh-CN.yml index 32f29d7f603..3a650dfddd5 100644 --- a/config/locales/crowdin/js-zh-CN.yml +++ b/config/locales/crowdin/js-zh-CN.yml @@ -202,16 +202,7 @@ zh-CN: text: "[Placeholder]嵌入式日历" admin: type_form: - custom_field: 自定义字段 - inactive: 非活动 - drag_to_activate: 从此处拖动字段将其激活 - add_group: 增加属性组 - add_table: 添加相关工作包表 edit_query: 编辑查询 - new_group: 新建组 - delete_group: 删除群组 - remove_attribute: 从群组中移除 - reset_to_defaults: 重置为默认值 working_days: calendar: empty_state_header: 非工作日 @@ -613,18 +604,6 @@ zh-CN: breadcrumb: 面包屑导航 text_data_lost: 输入的所有数据将全部丢失。 text_user_wrote: "%{value} 写道:" - types: - attribute_groups: - error_duplicate_group_name: 名称 %{group} 已多次使用。组名不能重复。 - error_no_table_configured: 请为 %{group} 配置一个表。 - reset_title: 重置表单配置 - confirm_reset: '警告:确定想要重置表单配置吗?这会将属性重置为它们的默认组,并禁用所有自定义字段。 - - ' - upgrade_to_ee: 升级到本地部署的 Enterprise edition - upgrade_to_ee_text: 哇!如果您需要此插件,那么您是位超级行家!您是否考虑成为我们的企业版客户,以支持我们这些开源开发者? - more_information: 更多信息 - nevermind: 我愿意 time_entry: work_package_required: 需要先选择一个工作包。 title: 记录时间 diff --git a/config/locales/crowdin/js-zh-TW.yml b/config/locales/crowdin/js-zh-TW.yml index 58d066fc883..0f72de42aca 100644 --- a/config/locales/crowdin/js-zh-TW.yml +++ b/config/locales/crowdin/js-zh-TW.yml @@ -202,16 +202,7 @@ zh-TW: text: "[Placeholder] 嵌入式日曆" admin: type_form: - custom_field: 客製欄位 - inactive: 未啟用 - drag_to_activate: 從這裡拖曳欄位來啟用它們 - add_group: 增加群組屬性 - add_table: 新增相關工作套件表格 edit_query: 編輯查詢 - new_group: 新增群組 - delete_group: 刪除群組 - remove_attribute: 從群組中移除 - reset_to_defaults: 重設為預設值 working_days: calendar: empty_state_header: 非工作日 @@ -613,18 +604,6 @@ zh-TW: breadcrumb: 導覽軌跡 text_data_lost: 所有資料將遺失 text_user_wrote: "%{value} 寫道:" - types: - attribute_groups: - error_duplicate_group_name: 名稱 %{group} 已被使用,群組名稱不可重複。 - error_no_table_configured: 請為 %{group} 配置表。 - reset_title: 重設表單配置 - confirm_reset: '警告: 確實要重置表單配置嗎?這將重置屬性到其預設組, 並禁用所有客製欄位。 - - ' - upgrade_to_ee: 升級至地端企業版 - upgrade_to_ee_text: 哇!如果您需要使用此功能,說明您非常專業!您是否願意成為企業版客戶來支持 OpenSource 開發人員? - more_information: 更多資訊 - nevermind: 無視 time_entry: work_package_required: 需要先選擇一個工作套件。 title: 工時紀錄 diff --git a/config/locales/crowdin/ka.yml b/config/locales/crowdin/ka.yml index 28264b948a0..a9996c00add 100644 --- a/config/locales/crowdin/ka.yml +++ b/config/locales/crowdin/ka.yml @@ -1339,6 +1339,37 @@ ka: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: პროექტები enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ ka: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ ka: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ ka: one: 1 კომენტარი other: "%{count} comments" zero: კომენტარების გარეშე + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 ღიაა other: "%{count} ღია" @@ -4752,13 +4787,6 @@ ka: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: შეტყობინებების დაწერა diff --git a/config/locales/crowdin/kk.yml b/config/locales/crowdin/kk.yml index 220a7fc50df..6c8dc522a11 100644 --- a/config/locales/crowdin/kk.yml +++ b/config/locales/crowdin/kk.yml @@ -1339,6 +1339,37 @@ kk: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projects enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ kk: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ kk: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ kk: one: 1 comment other: "%{count} comments" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4752,13 +4787,6 @@ kk: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/ko.yml b/config/locales/crowdin/ko.yml index 4d47f37e2e2..6a0ed268df4 100644 --- a/config/locales/crowdin/ko.yml +++ b/config/locales/crowdin/ko.yml @@ -1319,6 +1319,37 @@ ko: edit: form_configuration: tab: 양식 구성 + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: 프로젝트 enable_all: 모든 프로젝트에 대해 활성화 @@ -1417,8 +1448,8 @@ ko: bulk_delete_dialog: title: "%{count}개 작업 패키지 삭제" heading: 이러한 작업 패키지 %{count}개를 영구적으로 삭제하시겠습니까? - description: '자식 및 모든 관련 데이터를 포함한 다음 작업 패키지가 영구적으로 삭제됩니다:' - description_with_children: '자식 작업 패키지를 포함한 다음 작업 패키지 및 모든 관련 데이터가 영구적으로 삭제됩니다:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: 본인은 선택한 모든 작업 패키지 및 해당 자식이 영구적으로 삭제됨을 확인합니다. cross_project_warning: '이러한 작업 패키지는 여러 프로젝트에 걸쳐 있습니다: %{projects}' children_label: '다음 자식도 삭제됩니다:' @@ -2390,7 +2421,7 @@ ko: attribute_unknown_name: '잘못된 작업 패키지 특성이 사용되었습니다: %{attribute}' duplicate_group: "'%{group}'(이)라는 그룹 이름이 두 번 이상 사용되었습니다. 그룹 이름은 중복될 수 없습니다." query_invalid: 포함된 쿼리 '%{group}'이(가) 잘못되었습니다. %{details} - group_without_name: 명명되지 않은 그룹은 허용되지 않습니다. + group_without_name: Group name can't be blank. patterns: invalid_tokens: 필드 내의 특성이 하나 이상의 유효하지 않습니다. 특성을 수정한 후에 저장하세요. user: @@ -4372,6 +4403,10 @@ ko: one: 1개 코멘트 other: "%{count}개 코멘트" zero: 코멘트 없음 + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1개 열림 other: "%{count}개 열림" @@ -4703,13 +4738,6 @@ ko: work_package_card_component: menu: label_actions: 작업 패키지 액션 - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: 코멘트 추가 permission_add_work_packages: 작업 패키지 추가 permission_add_messages: 메시지 게시 diff --git a/config/locales/crowdin/lt.yml b/config/locales/crowdin/lt.yml index 39c14aef145..3b598befdb6 100644 --- a/config/locales/crowdin/lt.yml +++ b/config/locales/crowdin/lt.yml @@ -1374,6 +1374,37 @@ lt: edit: form_configuration: tab: Formos konfigūracija + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projektai enable_all: Enable for all projects @@ -1478,8 +1509,8 @@ lt: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2457,7 +2488,7 @@ lt: attribute_unknown_name: 'Panaudotas netinkamas darbo paketo atributas: %{attribute}' duplicate_group: Grupės pavadinimas „%{group}“ panaudotas daugiau nei kartą. Grupių pavadinimai turi būti unikalūs. query_invalid: 'Įsiūta užklausa „%{group}“ yra netinkama: %{details}' - group_without_name: Bevardės grupės nėra leidžiamos. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4547,6 +4578,10 @@ lt: one: 1 komentaras other: "%{count} komentarai (-as, -ų)" zero: nėra komentarų + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 atidarytas other: "%{count} atidaryti (-as, -ų)" @@ -4891,13 +4926,6 @@ lt: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Pridėti darbų paketų permission_add_messages: Skelbti pranešimus diff --git a/config/locales/crowdin/lv.yml b/config/locales/crowdin/lv.yml index 4755d9727d4..1838878c2d1 100644 --- a/config/locales/crowdin/lv.yml +++ b/config/locales/crowdin/lv.yml @@ -1357,6 +1357,37 @@ lv: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projekti enable_all: Enable for all projects @@ -1459,8 +1490,8 @@ lv: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2436,7 +2467,7 @@ lv: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4484,6 +4515,10 @@ lv: one: 1 komentārs other: "%{count} komentāri" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 atvērts other: "%{count} atvērti" @@ -4821,13 +4856,6 @@ lv: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/mn.yml b/config/locales/crowdin/mn.yml index 1be80178057..a473783d5b8 100644 --- a/config/locales/crowdin/mn.yml +++ b/config/locales/crowdin/mn.yml @@ -1339,6 +1339,37 @@ mn: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projects enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ mn: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ mn: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ mn: one: 1 comment other: "%{count} comments" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4752,13 +4787,6 @@ mn: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/ms.yml b/config/locales/crowdin/ms.yml index ea13fdf97c0..7f0cc4bba6a 100644 --- a/config/locales/crowdin/ms.yml +++ b/config/locales/crowdin/ms.yml @@ -1319,6 +1319,37 @@ ms: edit: form_configuration: tab: Konfigurasi borang + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projek-projek enable_all: Enable for all projects @@ -1417,8 +1448,8 @@ ms: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2392,7 +2423,7 @@ ms: attribute_unknown_name: 'Atribut pakej kerja tidak sah digunakan: %{attribute}' duplicate_group: Nama kumpulan '%{group}' digunakan lebih dari sekali. Nama kumpulan mestilah unik. query_invalid: 'Pencarian yang ditetapkan ''%{group}'' tidak sah: %{details}' - group_without_name: Kumpulan tanpa nama tidak dibenarkan. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4368,6 +4399,10 @@ ms: one: 1 komen other: "%{count} komen" zero: Tiada komen + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 dibuka other: "%{count} dibuka" @@ -4695,13 +4730,6 @@ ms: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Tambah pakej kerja permission_add_messages: Pos mesej diff --git a/config/locales/crowdin/ne.yml b/config/locales/crowdin/ne.yml index 8e5ada6144e..fc78a3c4788 100644 --- a/config/locales/crowdin/ne.yml +++ b/config/locales/crowdin/ne.yml @@ -1339,6 +1339,37 @@ ne: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projects enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ ne: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ ne: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ ne: one: 1 comment other: "%{count} comments" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4752,13 +4787,6 @@ ne: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/nl.yml b/config/locales/crowdin/nl.yml index 30f62a2fbf2..03aec9ffb7d 100644 --- a/config/locales/crowdin/nl.yml +++ b/config/locales/crowdin/nl.yml @@ -1337,6 +1337,37 @@ nl: edit: form_configuration: tab: Formulierconfiguratie + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projecten enable_all: Enable for all projects @@ -1437,8 +1468,8 @@ nl: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2412,7 +2443,7 @@ nl: attribute_unknown_name: 'Ongeldig werkpakket attribuut gebruikt: %{attribute}' duplicate_group: De groep naam ‘%{group}’ word meer dan één keer gebruikt. Groep namen moeten uniek zijn. query_invalid: 'De ingesloten zoekopdracht ''%{group}'' is ongeldig: %{details}' - group_without_name: Groepen zonder namen zijn niet toegestaan. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4416,6 +4447,10 @@ nl: one: 1 reactie other: "%{count} reacties" zero: geen reacties + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4746,13 +4781,6 @@ nl: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Werkpakketten toevoegen permission_add_messages: Berichten posten diff --git a/config/locales/crowdin/no.yml b/config/locales/crowdin/no.yml index 7fa8e29c1ad..0b9b04ef97f 100644 --- a/config/locales/crowdin/no.yml +++ b/config/locales/crowdin/no.yml @@ -1337,6 +1337,37 @@ edit: form_configuration: tab: Skjema konfigurering + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Prosjekter enable_all: Enable for all projects @@ -1437,8 +1468,8 @@ bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2412,7 +2443,7 @@ attribute_unknown_name: 'Ugyldig arbeidspakke-egenskap som brukes: %{attribute}' duplicate_group: Gruppenavn '%{group}' brukes mer enn én gang. Gruppenavn må være unike. query_invalid: 'Den innebygde spørringen ''%{group}'' er ugyldig: %{details}' - group_without_name: Navnløs gruppe er ikke tillatt. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4418,6 +4449,10 @@ one: 1 kommentar other: "%{count} kommentarer" zero: ingen kommentarer + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 åpen other: "%{count} åpne" @@ -4750,13 +4785,6 @@ work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Legg til arbeidspakker permission_add_messages: Skriv meldinger diff --git a/config/locales/crowdin/pl.yml b/config/locales/crowdin/pl.yml index c2d159530de..21edf12d7a6 100644 --- a/config/locales/crowdin/pl.yml +++ b/config/locales/crowdin/pl.yml @@ -1373,6 +1373,37 @@ pl: edit: form_configuration: tab: Konfiguracja formularza + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projekty enable_all: Włącz dla wszystkich projektów @@ -1477,8 +1508,8 @@ pl: bulk_delete_dialog: title: Usuń pakiety robocze (%{count}) heading: Czy usunąć trwale te pakiety robocze (%{count})? - description: 'Następujące pakiety robocze, w tym elementy podrzędne i wszystkie powiązane dane, zostaną trwale usunięte:' - description_with_children: 'Następujące pakiety robocze, w tym podrzędne pakiety robocze oraz wszystkie powiązane z nimi dane, zostaną trwale usunięte:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: Przyjmuję do wiadomości, że wszystkie wybrane pakiety robocze i ich elementy podrzędne zostaną trwale usunięte. cross_project_warning: 'Te pakiety robocze obejmują wiele projektów: %{projects}' children_label: 'Zostaną również usunięte następujące elementy podrzędne:' @@ -2454,7 +2485,7 @@ pl: attribute_unknown_name: Użyto nieprawidłowego atrybutu pakietu roboczego. %{attribute} duplicate_group: Nazwy grupy %{group} użyto więcej niż raz. Nazwy grup muszą być niepowtarzalne. query_invalid: 'Osadzona kwerenda %{group} jest nieprawidłowowa: %{details}' - group_without_name: Grupy nienazwane nie są dozwolone. + group_without_name: Group name can't be blank. patterns: invalid_tokens: Co najmniej jeden atrybut wewnątrz pola jest nieprawidłowy. Popraw atrybuty przed zapisaniem. user: @@ -4540,6 +4571,10 @@ pl: one: 1 komentarz other: 'Liczba komentarzy: %{count}' zero: brak komentarzy + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 otwarty other: 'Liczba otwartych: %{count}' @@ -4886,13 +4921,6 @@ pl: work_package_card_component: menu: label_actions: Działania pakietu roboczego - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Dodaj komentarze permission_add_work_packages: Dodawanie pakietów roboczych permission_add_messages: Wysyłanie wiadomości diff --git a/config/locales/crowdin/pt-BR.yml b/config/locales/crowdin/pt-BR.yml index 1d2d9793cdd..24fc82348e5 100644 --- a/config/locales/crowdin/pt-BR.yml +++ b/config/locales/crowdin/pt-BR.yml @@ -465,8 +465,8 @@ pt-BR: label: 'Função: %{role}' no_role: Selecionar função roles: - one: One role selected - other: "%{count} roles selected" + one: Uma função selecionada + other: "%{count} funções selecionadas" blankslate: title: Nenhuma transição de status configurada description: Adicione status para começar a configurar fluxos de trabalho para esta função @@ -1336,6 +1336,37 @@ pt-BR: edit: form_configuration: tab: Configuração do formulário + label_group: Grupo + reset_to_defaults: Redefinir para os padrões + add_attribute_group: Adicionar grupo de atributo + add_query_group: Adicionar tabela de pacotes de trabalho relacionados + delete_group: Excluir grupo + remove_attribute: Remover do grupo + drag_to_activate: Arraste campos daqui para ativá-los + drag_to_reorder: Arraste para reordenar + edit_query: Editar consulta + custom_field: Campo personalizado + filter_inactive: Todos atributos + inactive_attributes_heading: Atributos inativos + no_inactive_attributes: Nenhum atributo inativo + blankslate_title: Nenhum grupo ainda + blankslate_description: Adicione grupos usando o botão acima ou arraste atributos do painel esquerdo. + group_actions: Ações de grupo + rename_group: Renomear grupo + confirm_delete_group: Tem certeza que deseja excluir este grupo? Esta ação não pode ser revertida automaticamente. + group_name_label: Nome do grupo + row_actions: Ações de linha + query_group_label: Tabela de pacotes de trabalho + empty_group_hint: Arraste atributos aqui + invalid_attribute_groups: O payload de configuração do formulário é inválido. + invalid_query: A configuração da consulta incorporada é inválida. + not_found: O item de configuração de formulário solicitado não foi encontrado. + untitled_group: Grupo sem título + reset_title: Redefinir a configuração do formulário + confirm_reset: Tem certeza de que deseja redefinir as configurações do formulário? + reset_description: 'Isto irá redefinir os atributos para seu grupo padrão e desativar TODOS os campos personalizados. + + ' projects: tab: Projetos enable_all: Habilitar para todos os projetos @@ -1436,8 +1467,8 @@ pt-BR: bulk_delete_dialog: title: Excluir %{count} pacotes de trabalho heading: Excluir estes %{count} pacotes de trabalho de forma permanente? - description: 'Os seguintes pacotes de trabalho, incluindo os itens filhos e todos os dados associados, serão excluídos permanentemente:' - description_with_children: 'Os seguintes pacotes de trabalho, incluindo pacotes de trabalho filhos, e todos os dados associados serão excluídos permanentemente:' + description: 'Os seguintes pacotes de trabalho e todos os dados associados serão excluídos permanentemente:' + description_with_children: 'Os seguintes pacotes de trabalho, incluindo filhos e todos os dados associados, serão permanentemente excluídos:' confirm_children_deletion: Reconheço que todos os pacotes de trabalho selecionados e seus itens filhos serão excluídos permanentemente. cross_project_warning: 'Esses pacotes de trabalho abrangem vários projetos: %{projects}' children_label: 'Os seguintes itens filhos também serão excluídos:' @@ -2411,7 +2442,7 @@ pt-BR: attribute_unknown_name: 'Atributo de pacote de trabalho inválido: %{attribute}' duplicate_group: O nome de grupo %{group} foi usado mais de uma vez. Nomes de grupos devem ser únicos. query_invalid: 'A consulta incorporada ''%{group}'' é inválida: %{details}' - group_without_name: Grupos sem nome não são permitidos. + group_without_name: O nome do grupo não pode estar em branco. patterns: invalid_tokens: Um ou mais atributos dentro do campo não são válidos. Corrija os atributos antes de salvar. user: @@ -4417,6 +4448,10 @@ pt-BR: one: 1 comentário other: "%{count} comentários" zero: sem comentários + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 aberto other: "%{count} abertos" @@ -4748,13 +4783,6 @@ pt-BR: work_package_card_component: menu: label_actions: Ações do pacote de trabalho - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Adicionar comentários permission_add_work_packages: Adicionar pacotes de trabalho permission_add_messages: Postar mensagens @@ -5071,7 +5099,7 @@ pt-BR: ' setting_after_login_default_redirect_url: Após redirecionamento de login - setting_after_login_default_redirect_url_example_html: 'Set a default path to redirect users after login, if no back link was provided. Redirects to home page if not set.
Example: %{example_code} + setting_after_login_default_redirect_url_example_html: 'Defina um caminho padrão para redirecionar os usuários após o login, se nenhum link de retorno foi fornecido. Redireciona para a página inicial se não estiver definido.
Exemplo: %{example_code} ' setting_apiv3_cors_title: Compartilhamento de recursos entre origens (CORS) diff --git a/config/locales/crowdin/pt-PT.yml b/config/locales/crowdin/pt-PT.yml index 9d41ab50ccf..49b1d214297 100644 --- a/config/locales/crowdin/pt-PT.yml +++ b/config/locales/crowdin/pt-PT.yml @@ -1337,6 +1337,37 @@ pt-PT: edit: form_configuration: tab: Configuração do formulário + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projetos enable_all: Ativar para todos os projetos @@ -1437,8 +1468,8 @@ pt-PT: bulk_delete_dialog: title: Eliminar %{count} pacotes de trabalho heading: Eliminar permanentemente estes %{count} pacotes de trabalho? - description: 'Os seguintes pacotes de trabalho, incluindo os secundários e todos os dados associados, serão permanentemente eliminados:' - description_with_children: 'Os seguintes pacotes de trabalho, incluindo os pacotes de trabalho secundários e todos os dados associados, serão permanentemente eliminados:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: Reconheço que todos os pacotes de trabalho selecionados e os seus filhos serão permanentemente eliminados. cross_project_warning: 'Estes pacotes de trabalho abrangem vários projetos: %{projects}' children_label: 'Os seguintes filhos também serão eliminados:' @@ -2412,7 +2443,7 @@ pt-PT: attribute_unknown_name: 'Foi utilizado um atributo de pacote de trabalho inválido: %{attribute}' duplicate_group: O nome de grupo '%{group}' foi usado mais de uma vez. Os nomes de grupos têm de ser exclusivos. query_invalid: 'A consulta incorporada ''%{group}'' é inválida: %{details}' - group_without_name: Não são permitidos grupos sem nome. + group_without_name: Group name can't be blank. patterns: invalid_tokens: Um ou mais atributos dentro do campo não são válidos. Corrija os atributos antes de os guardar. user: @@ -4418,6 +4449,10 @@ pt-PT: one: 1 comentário other: "%{count} comentários" zero: sem comentários + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 aberto other: "%{count} abertos" @@ -4747,13 +4782,6 @@ pt-PT: work_package_card_component: menu: label_actions: Ações do pacote de trabalho - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Adicionar comentários permission_add_work_packages: Adicionar pacotes de trabalho permission_add_messages: Publicar mensagens diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index 08e0dc98088..b1e78507664 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -1357,6 +1357,37 @@ ro: edit: form_configuration: tab: Configurare formular + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Proiecte enable_all: Activează pentru toate proiectele @@ -1459,8 +1490,8 @@ ro: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2436,7 +2467,7 @@ ro: attribute_unknown_name: 'A fost utilizat un atribut de pachet de lucru nevalabil: %{attribute}' duplicate_group: Numele grupului "%{group}" este utilizat de mai multe ori. Numele grupurilor trebuie să fie unice. query_invalid: 'Interogarea încorporată "%{group}" nu este valabilă: %{details}' - group_without_name: Grupurile fără nume nu sunt permise. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4484,6 +4515,10 @@ ro: one: un comentariu other: "%{count} comentarii" zero: fara comentarii + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: unul deschis other: "%{count} deschise" @@ -4823,13 +4858,6 @@ ro: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Adaugă pachete de lucru permission_add_messages: Publicare mesaje diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml index fcd264a6712..44088e256a8 100644 --- a/config/locales/crowdin/ru.yml +++ b/config/locales/crowdin/ru.yml @@ -481,10 +481,10 @@ ru: label: 'Роль: %{role}' no_role: Выберите роль roles: - one: One role selected - few: "%{count} roles selected" - many: "%{count} roles selected" - other: "%{count} roles selected" + one: Выбрана одна роль + few: "%{count} роли выбрано" + many: "%{count} ролей выбрано" + other: "%{count} ролей выбрано" blankslate: title: Переходы статуса не настроены description: Добавьте статусы, чтобы начать настройку рабочих процессов для этой роли @@ -732,7 +732,7 @@ ru: add_button: Добавить detail_blankslate: heading: Это подразделение не имеет дочерних подразделений - description: Add departments or users to create sub-items inside another one. + description: Добавить подразделения или пользователей для создания дочерних элементов. add_button: Добавить add_department_form: name_label: Наименование подразделения @@ -1374,6 +1374,37 @@ ru: edit: form_configuration: tab: Настройка форм + label_group: Группа + reset_to_defaults: Восстановить значения по умолчанию + add_attribute_group: Добавить группу атрибутов + add_query_group: Добавить таблицу связанных пакетов работ + delete_group: Удалить группу + remove_attribute: Удалить из группы + drag_to_activate: Чтобы включить поля, перетащите их отсюда + drag_to_reorder: Перетащите, чтобы изменить порядок + edit_query: Редактировать представление + custom_field: Настраиваемое поле + filter_inactive: Фильтровать атрибуты + inactive_attributes_heading: Неактивные атрибуты + no_inactive_attributes: Нет неактивных атрибутов + blankslate_title: Нет групп + blankslate_description: Добавьте группы с помощью кнопки выше или перетащите атрибуты с левой панели. + group_actions: Действия с группами + rename_group: Переименовать группу + confirm_delete_group: Вы уверены, что хотите удалить эту группу? Это действие не может быть автоматически отменено. + group_name_label: Имя группы + row_actions: Действия со строками + query_group_label: Таблица пакетов работ + empty_group_hint: Перетащите атрибуты сюда + invalid_attribute_groups: Конфигурация формы является недопустимой. + invalid_query: Недопустимая конфигурация встраиваемого представления. + not_found: Запрашиваемый элемент конфигурации формы не найден. + untitled_group: Безымянная группа + reset_title: Сбросить настройки формы + confirm_reset: Вы уверены, что хотите сбросить конфигурацию формы? + reset_description: 'Это сбросит атрибуты к группе по умолчанию и отключит ВСЕ настраиваемые поля. + + ' projects: tab: Проекты enable_all: Использовать во всех проектах @@ -1440,15 +1471,15 @@ ru: create: notice: one: Рабочий процесс успешно скопирован в роль '%{role_name}'. - few: Successfully copied workflow to %{count} roles. - many: Successfully copied workflow to %{count} roles. + few: Рабочий процесс успешно скопирован в %{count} ролей. + many: Рабочий процесс успешно скопирован в %{count} ролей. other: Рабочий процесс успешно скопирован в %{count} ролей. from_types: create: notice: one: Рабочий процесс успешно скопирован в тип '%{type_name}'. - few: Successfully copied workflow to %{count} types. - many: Successfully copied workflow to %{count} types. + few: Рабочий процесс успешно скопирован в %{count} типов. + many: Рабочий процесс успешно скопирован в %{count} типов. other: Рабочий процесс успешно скопирован в %{count} типов. new: title: Копировать рабочий процесс из типа "%{source_type}" @@ -1480,8 +1511,8 @@ ru: bulk_delete_dialog: title: Удалить %{count} пакетов работ heading: Окончательно удалить эти %{count} пакеты работ? - description: 'Следующие рабочие пакеты, включая дочерние и все связанные с ними данные, будут навсегда удалены:' - description_with_children: 'Следующие рабочие пакеты, включая дочерние пакеты работ и все связанные с ними данные, будут навсегда удалены:' + description: 'Следующие пакеты работ и все связанные с ними данные будут окончательно удалены:' + description_with_children: 'Следующие пакеты работ и все связанные с ними данные будут окончательно удалены:' confirm_children_deletion: Я подтверждаю, что все выбранные пакеты работ, включая дочерние, будут удалены навсегда. cross_project_warning: 'Эти пакеты работ охватывают несколько проектов: %{projects}' children_label: 'Следующие дочерние пакеты работ также будут удалены:' @@ -2061,7 +2092,7 @@ ru: views: Представления filters: Фильтры orders: Порядок - selects: Selects + selects: Выбирает persisted_view: name: Имя query: Запрос @@ -2071,10 +2102,10 @@ ru: secondary_info: Дополнительная информация show_status_badge: Показать значок статуса show_email: Показать адрес эл. почты - tag_source: Tag source - tag_limit: Tag limit + tag_source: Источник тегов + tag_limit: Лимит тегов card_size: Размер карты - columns_per_row: Columns per row + columns_per_row: Столбцов в строке errors: messages: accepted: нужно подтвердить. @@ -2461,7 +2492,7 @@ ru: attribute_unknown_name: 'Используется неверный атрибут пакета работ: %{attribute}' duplicate_group: Имя группы '%{group}' используется более одного раза. Имена групп должны быть уникальными. query_invalid: 'Встроенный запрос ''%{group}'' является недопустимым: %{details}' - group_without_name: Не именованные группы не допускаются. + group_without_name: Имя группы не может быть пустым. patterns: invalid_tokens: Один или несколько атрибутов внутри поля недействительны. Пожалуйста, исправьте атрибуты перед сохранением. user: @@ -3553,26 +3584,26 @@ ru: report_component: checks: failures: - one: "%{count} check failed" - few: "%{count} checks failed" - many: "%{count} checks failed" - other: "%{count} checks failed" - success: All checks passed + one: "%{count} проверка не удалась" + few: "%{count} проверки не удалось" + many: "%{count} проверок не удалось" + other: "%{count} проверок не удалось" + success: Все проверки пройдены warnings: - one: "%{count} check returned a warning" - few: "%{count} checks returned a warning" - many: "%{count} checks returned a warning" - other: "%{count} checks returned a warning" + one: "%{count} проверка с предупреждением" + few: "%{count} проверки с предупреждением" + many: "%{count} проверок с предупреждением" + other: "%{count} проверок с предупреждением" summary: - failure: Some checks failed and the system does not work as expected. - success: All connections and systems are working as expected. - warning: Some checks returned a warning. This can lead to unexpected behaviour. + failure: Некоторые проверки не удались, и система не работает, как ожидалось. + success: Все соединения и системы работают, как и ожидалось. + warning: Некоторые проверки вернули предупреждение. Это может привести к неожиданному поведению. result_component: status: - failed: Failed - passed: Passed - skipped: Skipped - warning: Warning + failed: Ошибка + passed: Пройден + skipped: Пропущен + warning: Предупреждение homescreen: additional: projects: Новые видимые проекты в данном экземпляре. @@ -3592,7 +3623,7 @@ ru: ' new_features_list: line_0: Мигратор Jira с поддержкой базовых пользовательских полей. - line_1: Сегменты бэклога для структурирования и приоритизации пакетов работ во время уточнения бэклога. + line_1: Разделы бэклога для структурирования и приоритизации пакетов работ во время уточнения бэклога. line_2: Более удобное перетаскивание и улучшенные опции перемещения в Бэклогах. line_3: Кнопки начала и завершения спринта в заголовке спринта. line_4: Копирование настроек рабочего процесса между ролями. @@ -4551,6 +4582,10 @@ ru: one: 1 комментарий other: "%{count} комментариев" zero: нет комментариев + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 открыт other: "%{count} открыто" @@ -4891,13 +4926,6 @@ ru: work_package_card_component: menu: label_actions: Действия с пакетом работ - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Добавить комментарии permission_add_work_packages: Добавление пакетов работ permission_add_messages: Написать сообщения @@ -5210,7 +5238,7 @@ ru: ' setting_after_login_default_redirect_url: Перенаправление после входа - setting_after_login_default_redirect_url_example_html: 'Set a default path to redirect users after login, if no back link was provided. Redirects to home page if not set.
Example: %{example_code} + setting_after_login_default_redirect_url_example_html: 'Установите путь по умолчанию для перенаправления пользователей после входа в систему, если обратная ссылка не была предоставлена. Если путь не задан, он перенаправляет на домашнюю страницу.
Пример: %{example_code} ' setting_apiv3_cors_title: Совместное использование ресурсов между источниками (Cross-Origin Resource Sharing, CORS) @@ -6201,8 +6229,8 @@ ru: ' confirm_revoke_my_application: one: Вы действительно хотите удалить это приложение? Это приведет к отзыву одного активного токена. - few: Do you really want to remove this application? This will revoke %{count} tokens active for it. - many: Do you really want to remove this application? This will revoke %{count} tokens active for it. + few: Вы действительно хотите удалить это приложение? Это приведет к отзыву %{count} активных токенов. + many: Вы действительно хотите удалить это приложение? Это приведет к отзыву %{count} активных токенов. other: Вы действительно хотите удалить это приложение? Это приведет к отзыву %{count} активных токенов. authorization_error: Произошла ошибка авторизации. my_registered_applications: Зарегистрированные OAuth приложения diff --git a/config/locales/crowdin/rw.yml b/config/locales/crowdin/rw.yml index c8759180351..9a31d12fed3 100644 --- a/config/locales/crowdin/rw.yml +++ b/config/locales/crowdin/rw.yml @@ -1339,6 +1339,37 @@ rw: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projects enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ rw: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ rw: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ rw: one: 1 comment other: "%{count} comments" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4752,13 +4787,6 @@ rw: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/si.yml b/config/locales/crowdin/si.yml index 08a69deb866..dcc5af80a73 100644 --- a/config/locales/crowdin/si.yml +++ b/config/locales/crowdin/si.yml @@ -1339,6 +1339,37 @@ si: edit: form_configuration: tab: ආකෘති වින්යාසය + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: ව්‍යාපෘති enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ si: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ si: attribute_unknown_name: 'භාවිතා වලංගු නොවන වැඩ පැකේජ ගුණාංගය: %{attribute}' duplicate_group: කණ්ඩායම් නාමය '%{group}' එක් වරකට වඩා භාවිතා වේ. කණ්ඩායම් නම් අද්විතීය විය යුතුය. query_invalid: 'කාවැද්දූ විමසුම ''%{group}'' අවලංගුයි: %{details}' - group_without_name: නම් නොකළ කණ්ඩායම් වලට අවසර නැත. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ si: one: 1 අදහස් other: "%{count} අදහස්" zero: අදහස් නැත + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 විවෘත other: "%{count} විවෘත" @@ -4752,13 +4787,6 @@ si: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: වැඩ පැකේජ එකතු කරන්න permission_add_messages: තැපැල් පණිවිඩ diff --git a/config/locales/crowdin/sk.yml b/config/locales/crowdin/sk.yml index 93043fd954f..db0386d38d5 100644 --- a/config/locales/crowdin/sk.yml +++ b/config/locales/crowdin/sk.yml @@ -1375,6 +1375,37 @@ sk: edit: form_configuration: tab: Konfigurácia formulára + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projekty enable_all: Enable for all projects @@ -1479,8 +1510,8 @@ sk: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2458,7 +2489,7 @@ sk: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: Názov skupiny '%{group}' sa používa viackrát. Názvy skupín musia byť jedinečné. query_invalid: 'Vložený dopyt ''%{group}'' je neplatný: %{details}' - group_without_name: Nepomenované skupiny nie sú povolené. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4546,6 +4577,10 @@ sk: one: 1 komentár other: "%{count} komentárov" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 otvorený other: "%{count} otvorených" @@ -4890,13 +4925,6 @@ sk: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Pridať pracovné balíky permission_add_messages: Publikovať správy diff --git a/config/locales/crowdin/sl.yml b/config/locales/crowdin/sl.yml index c82eaa35f0d..dc51e64d163 100644 --- a/config/locales/crowdin/sl.yml +++ b/config/locales/crowdin/sl.yml @@ -1376,6 +1376,37 @@ sl: edit: form_configuration: tab: Konfiguracija obrazca + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projekti enable_all: Enable for all projects @@ -1480,8 +1511,8 @@ sl: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2463,7 +2494,7 @@ sl: attribute_unknown_name: 'Neveljaven atribut delovnega paketa je bil uporabljen: %{attribute}' duplicate_group: Ime skupine '%{group}' je uporabljeno več kot enkrat. Ime skupine mora biti unikatno. query_invalid: 'Poizvedba ''%{group}'' je neveljavna: %{details}' - group_without_name: Ne poimenovane skupine niso dovoljene. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4561,6 +4592,10 @@ sl: one: 1 komentar other: "%{count} komentarjev" zero: Ni komentarjev + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 odprt other: "%{count} odprtih" @@ -4907,13 +4942,6 @@ sl: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Dodaj delovne pakete permission_add_messages: Objavi sporočila diff --git a/config/locales/crowdin/sr.yml b/config/locales/crowdin/sr.yml index 8e4d8b0539c..9ca52a4059e 100644 --- a/config/locales/crowdin/sr.yml +++ b/config/locales/crowdin/sr.yml @@ -1357,6 +1357,37 @@ sr: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projects enable_all: Enable for all projects @@ -1459,8 +1490,8 @@ sr: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2436,7 +2467,7 @@ sr: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4484,6 +4515,10 @@ sr: one: 1 comment other: "%{count} comments" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4821,13 +4856,6 @@ sr: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/sv.yml b/config/locales/crowdin/sv.yml index 9ee2bc9afbe..4a6836adeb2 100644 --- a/config/locales/crowdin/sv.yml +++ b/config/locales/crowdin/sv.yml @@ -1339,6 +1339,37 @@ sv: edit: form_configuration: tab: Ställ in formulär + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projekt enable_all: Aktivera för alla projekt @@ -1439,8 +1470,8 @@ sv: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ sv: attribute_unknown_name: 'Ogiltigt attributet för arbetspaket används: %{attribute}' duplicate_group: Namnet %{group} används redan. Gruppnamn måste vara unika. query_invalid: 'Den inbäddade frågan ''%{group}'' är ogiltigt: %{details}' - group_without_name: Namnlösa grupper tillåts inte. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ sv: one: En kommentar other: "%{count} kommentarer" zero: inga kommentarer + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: en öppen other: "%{count} öppnade" @@ -4750,13 +4785,6 @@ sv: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Lägg till kommentarer permission_add_work_packages: Lägg till arbetspaket permission_add_messages: Publicera meddelanden diff --git a/config/locales/crowdin/th.yml b/config/locales/crowdin/th.yml index 09825c3cee8..989d84ba9c8 100644 --- a/config/locales/crowdin/th.yml +++ b/config/locales/crowdin/th.yml @@ -1321,6 +1321,37 @@ th: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: โครงการ enable_all: Enable for all projects @@ -1419,8 +1450,8 @@ th: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2392,7 +2423,7 @@ th: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4356,6 +4387,10 @@ th: one: 1 comment other: มี %{count} ความคิดเห็น zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: มี %{count} เปิดอยู่ @@ -4683,13 +4718,6 @@ th: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: เพิ่มลำดับภารกิจ permission_add_messages: โพสต์ข้อความ diff --git a/config/locales/crowdin/tr.yml b/config/locales/crowdin/tr.yml index e6bba2b5083..6718eb94b20 100644 --- a/config/locales/crowdin/tr.yml +++ b/config/locales/crowdin/tr.yml @@ -1340,6 +1340,37 @@ tr: edit: form_configuration: tab: Formu yapılandırma + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projeler enable_all: Tüm projeler için etkinleştir @@ -1440,8 +1471,8 @@ tr: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2419,7 +2450,7 @@ tr: attribute_unknown_name: 'Geçersiz iş paketi özelliği kullanılmış: %{attribute}' duplicate_group: "'%{group}' grup adı, birden fazla kez kullanıldı. Grup isimleri benzersiz olmalıdır." query_invalid: 'Gömülü sorgu ''%{group}'' geçersiz: %{details}' - group_without_name: Adsız gruplara izin verilmez. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4425,6 +4456,10 @@ tr: one: 1 yorum other: "%{count} yorumlar" zero: yorum yok + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 açık other: "%{count} açık" @@ -4756,13 +4791,6 @@ tr: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Yorum ekle permission_add_work_packages: İş paketi eklemek permission_add_messages: İleti göndermek diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index ff548d770fe..d9eb1ab87fe 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -1372,6 +1372,37 @@ uk: edit: form_configuration: tab: Конфігурація форми + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Проєкти enable_all: Використовувати у всіх проєктах @@ -1476,8 +1507,8 @@ uk: bulk_delete_dialog: title: Видалити пакети робіт (%{count}) heading: Видалити ці пакети робіт (%{count}) остаточно? - description: 'Наведені нижче пакети робіт, зокрема їх дочірні елементи й усі пов’язані дані, буде видалено назавжди:' - description_with_children: 'Наведені нижче пакети робіт, зокрема дочірні пакети робіт і всі пов’язані дані, буде видалено назавжди:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: Я усвідомлюю, що всі вибрані пакети робіт і їх дочірні елементи буде видалено назавжди. cross_project_warning: 'Ці пакети робіт використовуються в кількох проєктах: %{projects}' children_label: 'Також видаляться наведені нижче дочірні елементи:' @@ -2457,7 +2488,7 @@ uk: attribute_unknown_name: Недійсний атрибут робочого пакета:%{attribute} duplicate_group: Назва групи %{group} використовується більше одного разу. Назви груп повинні бути унікальними. query_invalid: 'Вбудований запит %{group} недійсний: %{details}' - group_without_name: Неназвані групи не дозволені. + group_without_name: Group name can't be blank. patterns: invalid_tokens: Один або кілька атрибутів усередині поля недійсні. Виправте їх, перш ніж зберігати. user: @@ -4547,6 +4578,10 @@ uk: one: 1 коментар other: 'Коментарів: %{count}' zero: немає коментарів + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 відкрито other: 'Відкрито: %{count}' @@ -4888,13 +4923,6 @@ uk: work_package_card_component: menu: label_actions: Дії з пакетом робіт - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Додавання коментарів permission_add_work_packages: Додати робочі пакети permission_add_messages: Відправка повідомлень diff --git a/config/locales/crowdin/uz.yml b/config/locales/crowdin/uz.yml index 922b1ffcda5..e716d1c52e0 100644 --- a/config/locales/crowdin/uz.yml +++ b/config/locales/crowdin/uz.yml @@ -1339,6 +1339,37 @@ uz: edit: form_configuration: tab: Form configuration + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: Projects enable_all: Enable for all projects @@ -1439,8 +1470,8 @@ uz: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2414,7 +2445,7 @@ uz: attribute_unknown_name: 'Invalid work package attribute used: %{attribute}' duplicate_group: The group name '%{group}' is used more than once. Group names must be unique. query_invalid: 'The embedded query ''%{group}'' is invalid: %{details}' - group_without_name: Unnamed groups are not allowed. + group_without_name: Group name can't be blank. patterns: invalid_tokens: One or more attributes inside the field are not valid. Please, fix the attributes before saving. user: @@ -4420,6 +4451,10 @@ uz: one: 1 comment other: "%{count} comments" zero: no comments + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 open other: "%{count} open" @@ -4752,13 +4787,6 @@ uz: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Add comments permission_add_work_packages: Add work packages permission_add_messages: Post messages diff --git a/config/locales/crowdin/vi.yml b/config/locales/crowdin/vi.yml index cc1939db72a..874473c0790 100644 --- a/config/locales/crowdin/vi.yml +++ b/config/locales/crowdin/vi.yml @@ -1323,6 +1323,37 @@ vi: edit: form_configuration: tab: Cấu hình biểu mẫu + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: dự án enable_all: Kích hoạt cho tất cả các dự án @@ -1421,8 +1452,8 @@ vi: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2394,7 +2425,7 @@ vi: attribute_unknown_name: 'Thuộc tính gói công việc được sử dụng không hợp lệ: %{attribute}' duplicate_group: Nhóm tên '%{group}' được sử dụng nhiều hơn một lần. Tên nhóm phải là duy nhất. query_invalid: 'Truy vấn nhúng ''%{group}'' là không hợp lệ: %{details}' - group_without_name: Nhóm không có tên không được phép. + group_without_name: Group name can't be blank. patterns: invalid_tokens: Một hoặc nhiều thuộc tính bên trong trường này không hợp lệ. Vui lòng sửa các thuộc tính trước khi lưu. user: @@ -4358,6 +4389,10 @@ vi: one: 1 bình luận other: "%{count} bình luận" zero: Không có bình luận + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 mở other: "%{count} mở" @@ -4693,13 +4728,6 @@ vi: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: Thêm nhận xét permission_add_work_packages: Thêm mới Gói công việc permission_add_messages: Đăng tin nhắn diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index c0f9f4c9d8d..86e5d7fcc04 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -1317,6 +1317,37 @@ zh-CN: edit: form_configuration: tab: 表单配置 + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: 项目 enable_all: 对所有项目启用 @@ -1415,8 +1446,8 @@ zh-CN: bulk_delete_dialog: title: 删除 %{count} 个工作包 heading: 永久删除这些 %{count} 工作包? - description: 以下工作包,包括子工作包和所有相关数据,将被永久删除: - description_with_children: 以下工作包(包括子工作包)和所有相关数据将被永久删除: + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: 我确认所有选定的工作包及其子工作包将被永久删除。 cross_project_warning: 这些工作包跨越多个项目: %{projects} children_label: 以下子工作包也将被删除: @@ -2388,7 +2419,7 @@ zh-CN: attribute_unknown_name: 使用了无效的工作包属性:%{attribute} duplicate_group: 组名 %{group} 已多次使用。组名不能重复。 query_invalid: '嵌入式查询 "%{group}" 无效: %{details}' - group_without_name: 不允许未命名的组。 + group_without_name: Group name can't be blank. patterns: invalid_tokens: 字段内的一个或多个属性无效。请在保存前修正。 user: @@ -4352,6 +4383,10 @@ zh-CN: one: 1 条评论 other: "%{count} 条评论" zero: 没有注释 + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 个打开 other: "%{count} 个打开" @@ -4682,13 +4717,6 @@ zh-CN: work_package_card_component: menu: label_actions: 工作包操作 - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: 添加评论 permission_add_work_packages: 添加工作包 permission_add_messages: 发布消息 diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index 875019d421b..a994241f5a4 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -1319,6 +1319,37 @@ zh-TW: edit: form_configuration: tab: 表單設置 + label_group: Group + reset_to_defaults: Reset to defaults + add_attribute_group: Add attribute group + add_query_group: Add table of related work packages + delete_group: Delete group + remove_attribute: Remove from group + drag_to_activate: Drag fields from here to activate them + drag_to_reorder: Drag to reorder + edit_query: Edit query + custom_field: Custom field + filter_inactive: Filter attributes + inactive_attributes_heading: Inactive attributes + no_inactive_attributes: No inactive attributes + blankslate_title: No groups yet + blankslate_description: Add groups using the button above or drag attributes from the left panel. + group_actions: Group actions + rename_group: Rename group + confirm_delete_group: Are you sure you want to delete this group? This action cannot be automatically reversed. + group_name_label: Group name + row_actions: Row actions + query_group_label: Work packages table + empty_group_hint: Drag attributes here + invalid_attribute_groups: The form configuration payload is invalid. + invalid_query: The embedded query configuration is invalid. + not_found: The requested form configuration item could not be found. + untitled_group: Untitled group + reset_title: Reset form configuration + confirm_reset: Are you sure you want to reset the form configuration? + reset_description: 'This will reset the attributes to their default group and disable ALL custom fields. + + ' projects: tab: 專案 enable_all: 在所有專案中啟用 @@ -1417,8 +1448,8 @@ zh-TW: bulk_delete_dialog: title: Delete %{count} work packages heading: Permanently delete these %{count} work packages? - description: 'The following work packages, including children and all associated data, will permanently be deleted:' - description_with_children: 'The following work packages, including child work packages, and all associated data will be permanently deleted:' + description: 'The following work packages and all associated data will be permanently deleted:' + description_with_children: 'The following work packages, including children and all associated data, will be permanently deleted:' confirm_children_deletion: I acknowledge that all selected work packages and their children will be permanently deleted. cross_project_warning: 'These work packages span multiple projects: %{projects}' children_label: 'The following children will also be deleted:' @@ -2388,7 +2419,7 @@ zh-TW: attribute_unknown_name: '使用了無效的工作套件屬性: %{attribute}' duplicate_group: 群組名稱 %{group} 已被使用,群組名稱不可重複。 query_invalid: '這個遷入的查詢 ''%{group}'' 是無效的: %{details}' - group_without_name: 不允許未命名的群組。 + group_without_name: Group name can't be blank. patterns: invalid_tokens: 欄位內的一個或多個屬性不正確。請在儲存前修正屬性。 user: @@ -4350,6 +4381,10 @@ zh-TW: one: 1 則留言 other: "%{count} 個留言" zero: 沒有留言 + label_x_items: + one: 1 item + other: "%{count} items" + zero: no items label_x_open_work_packages_abbr: one: 1 開啟中 other: "%{count} 個未完成" @@ -4682,13 +4717,6 @@ zh-TW: work_package_card_component: menu: label_actions: Work package actions - work_package_card_list_component: - header: - label_actions: Open menu - label_work_package_count: - zero: No work packages - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: 增加留言 permission_add_work_packages: 新增工作套件 permission_add_messages: 張貼訊息 diff --git a/config/locales/en.yml b/config/locales/en.yml index 482b3031d11..59f407d2e24 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -136,7 +136,7 @@ en: title: "Beta - Try it out!" description: "This Jira Migrator is currently in beta. We currently only support Jira Server/Data Center versions 10.x and 11.x. Cloud instances are not supported at this time." contribution_callout: > - Please, help us improve the Jira Migrator with your feedback and private data donations. You can [join the development community](link) of the Jira Migrator. + Please, help us improve the Jira Migrator with your feedback and private data donations. You can [join the development community](link) of the Jira Migrator. supported_versions: "" form: fields: @@ -1407,6 +1407,36 @@ en: edit: form_configuration: tab: "Form configuration" + label_group: "Group" + reset_to_defaults: "Reset to defaults" + add_attribute_group: "Add attribute group" + add_query_group: "Add table of related work packages" + delete_group: "Delete group" + remove_attribute: "Remove from group" + drag_to_activate: "Drag fields from here to activate them" + drag_to_reorder: "Drag to reorder" + edit_query: "Edit query" + custom_field: "Custom field" + filter_inactive: "Filter attributes" + inactive_attributes_heading: "Inactive attributes" + no_inactive_attributes: "No inactive attributes" + blankslate_title: "No groups yet" + blankslate_description: "Add groups using the button above or drag attributes from the left panel." + group_actions: "Group actions" + rename_group: "Rename group" + confirm_delete_group: "Are you sure you want to delete this group? This action cannot be automatically reversed." + group_name_label: "Group name" + row_actions: "Row actions" + query_group_label: "Work packages table" + empty_group_hint: "Drag attributes here" + invalid_attribute_groups: "The form configuration payload is invalid." + invalid_query: "The embedded query configuration is invalid." + not_found: "The requested form configuration item could not be found." + untitled_group: "Untitled group" + reset_title: "Reset form configuration" + confirm_reset: "Are you sure you want to reset the form configuration?" + reset_description: > + This will reset the attributes to their default group and disable ALL custom fields. projects: tab: Projects enable_all: Enable for all projects @@ -1513,8 +1543,8 @@ en: bulk_delete_dialog: title: "Delete %{count} work packages" heading: "Permanently delete these %{count} work packages?" - description: "The following work packages, including children and all associated data, will permanently be deleted:" - description_with_children: "The following work packages, including child work packages, and all associated data will be permanently deleted:" + description: "The following work packages and all associated data will be permanently deleted:" + description_with_children: "The following work packages, including children and all associated data, will be permanently deleted:" confirm_children_deletion: "I acknowledge that all selected work packages and their children will be permanently deleted." cross_project_warning: "These work packages span multiple projects: %{projects}" children_label: "The following children will also be deleted:" @@ -2503,7 +2533,7 @@ en: attribute_unknown_name: "Invalid work package attribute used: %{attribute}" duplicate_group: "The group name '%{group}' is used more than once. Group names must be unique." query_invalid: "The embedded query '%{group}' is invalid: %{details}" - group_without_name: "Unnamed groups are not allowed." + group_without_name: "Group name can't be blank." patterns: invalid_tokens: "One or more attributes inside the field are not valid. Please, fix the attributes before saving." user: @@ -4562,6 +4592,10 @@ en: one: "1 comment" other: "%{count} comments" zero: "no comments" + label_x_items: + one: "1 item" + other: "%{count} items" + zero: "no items" label_x_open_work_packages_abbr: one: "1 open" other: "%{count} open" @@ -4907,13 +4941,6 @@ en: menu: label_actions: "Work package actions" parent: "Parent" - work_package_card_list_component: - header: - label_actions: "Open menu" - label_work_package_count: - zero: "No work packages" - one: "%{count} work package" - other: "%{count} work packages" permission_add_work_package_comments: "Add comments" permission_add_work_packages: "Add work packages" diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml index 50545ecb125..ec5086e25d0 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -218,16 +218,7 @@ en: admin: type_form: - custom_field: "Custom field" - inactive: "Inactive" - drag_to_activate: "Drag fields from here to activate them" - add_group: "Add attribute group" - add_table: "Add table of related work packages" edit_query: "Edit query" - new_group: "New group" - delete_group: "Delete group" - remove_attribute: "Remove from group" - reset_to_defaults: "Reset to defaults" working_days: calendar: @@ -658,19 +649,6 @@ en: text_data_lost: "All entered data will be lost." text_user_wrote: "%{value} wrote:" - types: - attribute_groups: - error_duplicate_group_name: "The name %{group} is used more than once. Group names must be unique." - error_no_table_configured: "Please configure a table for %{group}." - reset_title: "Reset form configuration" - confirm_reset: > - Warning: Are you sure you want to reset the form configuration? - This will reset the attributes to their default group and disable ALL custom fields. - upgrade_to_ee: "Upgrade to Enterprise on-premises edition" - upgrade_to_ee_text: "Wow! If you need this add-on you are a super pro! Would you mind supporting us OpenSource developers by becoming an Enterprise edition client?" - more_information: "More information" - nevermind: "Nevermind" - time_entry: work_package_required: "Requires selecting a work package first." title: "Log time" diff --git a/config/routes.rb b/config/routes.rb index 0b53becfc63..7b965f99179 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -149,7 +149,27 @@ Rails.application.routes.draw do get "/roles/workflow/:id/:role_id/:type_id" => "roles#workflow" resources :types, module: "work_package_types", except: [:update] do - resource :form_configuration, only: %i[edit update], controller: "form_configuration_tab" + resource :form_configuration, only: %i[edit update], controller: "form_configuration_tab" do + get :reset_dialog + resources :groups, only: %i[create edit update destroy], controller: "form_configuration_groups_tab", param: :key do + collection do + post :add_group + end + + member do + post :cancel_edit + put :drop + put :move + patch :update_query + end + end + resources :rows, only: %i[destroy], controller: "form_configuration_tab", param: :row_key do + member do + put :drop + put :move + end + end + end resource :projects, controller: "projects_tab", only: %i[update edit] do collection do post :enable_all, to: "projects_tab#enable_all_projects" @@ -968,6 +988,9 @@ Rails.application.routes.draw do end resources :users, constraints: { id: /(\d+|me)/ }, except: :edit do + collection do + get :configure_view_modal + end resources :memberships, controller: "users/memberships", only: %i[update create destroy] resources :working_hours, controller: "users/working_hours", except: [:index] resources :non_working_times, controller: "users/non_working_times", except: [:index] do diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index 6f13a27e083..1e6d1c509ee 100755 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -141,7 +141,7 @@ ENV PGDATA=/var/openproject/pgdata COPY --from=openproject/gosu /go/bin/gosu /usr/local/bin/gosu RUN chmod +x /usr/local/bin/gosu && gosu nobody true -COPY --from=openproject/hocuspocus:17.3.1 --chown=$APP_USER:$APP_USER /app /opt/hocuspocus +COPY --from=openproject/hocuspocus:17.4.0 --chown=$APP_USER:$APP_USER /app /opt/hocuspocus # Keep node/npm in all-in-one for bundled hocuspocus even when BIM support is disabled. COPY --from=build-base /usr/local/bin/node /usr/local/bin/node COPY --from=build-base /usr/local/lib/node_modules /usr/local/lib/node_modules diff --git a/docker/prod/setup/precompile-assets.sh b/docker/prod/setup/precompile-assets.sh index 1ab0388486f..b75b9b4de45 100755 --- a/docker/prod/setup/precompile-assets.sh +++ b/docker/prod/setup/precompile-assets.sh @@ -9,7 +9,7 @@ else echo "Assets need to be compiled" JOBS=8 npm install - SECRET_KEY_BASE=1 RAILS_ENV=production DATABASE_URL=nulldb://db \ + SECRET_KEY_BASE="$(openssl rand -hex 64)" RAILS_ENV=production DATABASE_URL=nulldb://db \ bin/rails openproject:plugins:register_frontend assets:precompile if [ "$DOCKER" = "1" ]; then diff --git a/docs/api/apiv3/openapi-spec.yml b/docs/api/apiv3/openapi-spec.yml index 2a50ea44013..8abeb68cf5f 100644 --- a/docs/api/apiv3/openapi-spec.yml +++ b/docs/api/apiv3/openapi-spec.yml @@ -70,7 +70,8 @@ info: This means you have to login to OpenProject via the Web-Interface to be authenticated in the API. This method is well-suited for clients acting within the browser, like the Angular-Client built into OpenProject. - In this case, you always need to pass the HTTP header `X-Requested-With "XMLHttpRequest"` for authentication. + Keep in mind that session-based authentication is only possible when accessing the API from the same origin. This + is assured by checking the `Sec-Fetch-Site` HTTP header. ### API token as bearer token diff --git a/docs/development/profiling/README.md b/docs/development/profiling/README.md index 9ee193abb75..7fe85d5743a 100644 --- a/docs/development/profiling/README.md +++ b/docs/development/profiling/README.md @@ -49,7 +49,7 @@ gem 'stackprof' Start thin via: ```shell -SECRET_KEY_BASE='abcd' RAILS_ENV=production CUSTOM_PLUGIN_GEMFILE=gemfile.profiling OPENPROJECT_RACK_PROFILER_ENABLED=true thin start +SECRET_KEY_BASE="$(openssl rand -hex 64)" RAILS_ENV=production CUSTOM_PLUGIN_GEMFILE=gemfile.profiling OPENPROJECT_RACK_PROFILER_ENABLED=true thin start ``` ## Using the profiling tools diff --git a/docs/installation-and-operations/configuration/incoming-emails/README.md b/docs/installation-and-operations/configuration/incoming-emails/README.md index 4f3f68fd93d..251c15a4f41 100644 --- a/docs/installation-and-operations/configuration/incoming-emails/README.md +++ b/docs/installation-and-operations/configuration/incoming-emails/README.md @@ -164,7 +164,10 @@ If a matching account is found, the mail handler impersonates the user to create If no matching account is found, the mail is rejected. To override this behavior and allow unknown mail address to create work packages, set the option `no_permission_check=1` and specify with `unknown_user=accept` -**Note**: This feature only provides a mapping of mail to user account, it does not authenticate the user based on the mail. Since you can easily spoof mail addresses, you should not rely on the authenticity of work packages created that way. At the moment in the OpenProject Enterprise cloud work package generation by emails can only be triggered by registered email addresses. +> [!CAUTION] +> This feature only provides a mapping of mail to user account, it does not authenticate the user based on the mail. Since you can easily spoof mail addresses, you should not rely on the authenticity of work packages created that way. At the moment in the OpenProject Enterprise cloud work package generation by emails can only be triggered by registered email addresses. +> +> The same is true for `unknown_user=create`. This will effectively allow users to create new accounts in the system, same as with the self registration setting. **Users with mail suffixes** diff --git a/docs/installation-and-operations/installation/docker/README.md b/docs/installation-and-operations/installation/docker/README.md index 15ae8029b2d..d447eff6385 100644 --- a/docs/installation-and-operations/installation/docker/README.md +++ b/docs/installation-and-operations/installation/docker/README.md @@ -71,7 +71,7 @@ following command: ```shell docker run -it -p 8080:80 \ - -e SECRET_KEY_BASE=secret \ + -e SECRET_KEY_BASE= \ -e OPENPROJECT_HOST__NAME=localhost:8080 \ -e OPENPROJECT_HTTPS=false \ -e OPENPROJECT_DEFAULT__LANGUAGE=en \ @@ -81,7 +81,7 @@ docker run -it -p 8080:80 \ Explanation of the used configuration values: - `-p 8080:80` binds the port 80 of the container to 8080 on the machine running docker. -- `SECRET_KEY_BASE` sets the secret key base for Rails. Please use a pseudo-random value for this and treat it like a password. +- `SECRET_KEY_BASE` sets the secret key base for Rails. Replace `` with a strong, random value (for example generated with `openssl rand -hex 64`). Treat it like a password and **store it securely** — the same value must be reused on every container start, otherwise existing sessions and encrypted database content become unreadable. OpenProject will refuse to start with a default or weak value. - `OPENPROJECT_HOST__NAME` sets the host name of the application. This value is used for generating forms and links in emails, and needs to match the external request host name (The value users are seeing in their browsers). - `OPENPROJECT_HTTPS=false` disables the on-by-default HTTPS mode of OpenProject so you can access the instance over HTTP-only. For all production systems we strongly advise not to set this to false, and instead set up a proper TLS/SSL termination on your outer web server. - `OPENPROJECT_DEFAULT__LANGUAGE` does two things. It controls for the very first installation, in which language basic data (such as types, status names, etc.) and demo data is being created in. It also sets the default fallback language for new users. @@ -102,7 +102,7 @@ achieved with the `-d` flag: ```shell docker run -d -p 8080:80 \ - -e SECRET_KEY_BASE=secret \ + -e SECRET_KEY_BASE= \ -e OPENPROJECT_HOST__NAME=localhost:8080 \ -e OPENPROJECT_HTTPS=false \ openproject/openproject:17 @@ -134,7 +134,7 @@ sudo mkdir -p /var/lib/openproject/{pgdata,assets} docker run -d -p 8080:80 --name openproject \ -e OPENPROJECT_HOST__NAME=openproject.example.com \ - -e SECRET_KEY_BASE=secret \ + -e SECRET_KEY_BASE= \ -v /var/lib/openproject/pgdata:/var/openproject/pgdata \ -v /var/lib/openproject/assets:/var/openproject/assets \ openproject/openproject:17 @@ -143,7 +143,7 @@ docker run -d -p 8080:80 --name openproject \ Please make sure you set the correct public facing hostname in `OPENPROJECT_HOST__NAME`. If you don't have a load-balancing or proxying web server in front of your docker container, you will otherwise be vulnerable to [HOST header injections](https://portswigger.net/web-security/host-header), as the internal server has no way of identifying the correct host name. We strongly recommend you use an external load-balancing or proxying web server for termination of TLS/SSL and general security hardening. -**Note**: Make sure to replace `secret` with a random string. One way to generate one is to run `head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32 ; echo ''` if you are on Linux. +**Note**: Make sure to replace `` with a random string. One way to generate one is to run `openssl rand -hex 64`. Store this value securely — it must remain the same across container restarts, otherwise sessions and encrypted database content will be lost. **Note**: MacOS users might encounter an "Operation not permitted" error on the mounted directories. The fix for this is to create the two directories in a user-owned directory of the host machine. @@ -474,7 +474,7 @@ The first way is to mount the root certificate via the ```--mount``` option into ```shell sudo docker run -it -p 8080:80 \ - -e SECRET_KEY_BASE=secret \ + -e SECRET_KEY_BASE= \ -e OPENPROJECT_HOST__NAME=localhost:8080 \ -e OPENPROJECT_HTTPS=false \ -e OPENPROJECT_DEFAULT__LANGUAGE=en \ diff --git a/docs/installation-and-operations/jira-migration/README.md b/docs/installation-and-operations/jira-migration/README.md index 25d3dc11de4..98e7c74b8f2 100644 --- a/docs/installation-and-operations/jira-migration/README.md +++ b/docs/installation-and-operations/jira-migration/README.md @@ -32,7 +32,7 @@ This import tool is currently in beta and can only import basic data: - Users (name, email, project membership) - Statuses - Types -- Basic custom fields (see [Custom fields migration](./custom-fields/README.md)) +- Basic custom fields (see [Custom fields migration](./custom-fields/)) ## Data not yet covered by the Migrator diff --git a/docs/installation-and-operations/jira-migration/custom-fields/README.md b/docs/installation-and-operations/jira-migration/custom-fields/README.md index fe189a9433f..85b4c251919 100644 --- a/docs/installation-and-operations/jira-migration/custom-fields/README.md +++ b/docs/installation-and-operations/jira-migration/custom-fields/README.md @@ -34,8 +34,8 @@ The following Jira custom field types are imported: | Cascading select | Hierarchy or List | | Labels | List | -Read more about [OpenProject custom field formats](../../../system-admin-guide/custom-fields/README.md#custom-field-formats) and -the [Hierarchy format](../../../system-admin-guide/custom-fields/README.md#hierarchy-custom-field-enterprise-add-on) in particular in the system administration guide. +Read more about [OpenProject custom field formats](../../../system-admin-guide/custom-fields/#custom-field-formats) and +the [Hierarchy format](../../../system-admin-guide/custom-fields/#hierarchy-custom-field-enterprise-add-on) in particular in the system administration guide. ## Currently unsupported field types diff --git a/docs/installation-and-operations/misc/migration-to-postgresql13/README.md b/docs/installation-and-operations/misc/migration-to-postgresql13/README.md index 533361db10e..014c7c21d05 100644 --- a/docs/installation-and-operations/misc/migration-to-postgresql13/README.md +++ b/docs/installation-and-operations/misc/migration-to-postgresql13/README.md @@ -213,7 +213,7 @@ sudo mv /var/lib/openproject/pgdata-next /var/lib/openproject/pgdata Finally, you can restart OpenProject with the same command that you used before. For instance: -docker run -d -p 8080:80 --name openproject -e SECRET_KEY_BASE=secret \ +docker run -d -p 8080:80 --name openproject -e SECRET_KEY_BASE= \ -v /var/lib/openproject/pgdata:/var/openproject/pgdata \ -v /var/lib/openproject/assets:/var/openproject/assets \ [...] diff --git a/docs/installation-and-operations/misc/migration-to-postgresql17/README.md b/docs/installation-and-operations/misc/migration-to-postgresql17/README.md index b839d9f1829..dc32c2b5f4d 100644 --- a/docs/installation-and-operations/misc/migration-to-postgresql17/README.md +++ b/docs/installation-and-operations/misc/migration-to-postgresql17/README.md @@ -165,7 +165,7 @@ You can now run a new OpenProject container connected to your upgraded PostgreSQ ```bash docker run -d -p 8080:80 --name openproject \ -e OPENPROJECT_HOST__NAME=openproject.example.com \ - -e SECRET_KEY_BASE=secret \ + -e SECRET_KEY_BASE= \ -v /var/lib/openproject/pgdata17:/var/openproject/pgdata \ -v /var/lib/openproject/assets:/var/openproject/assets \ openproject/openproject:17 diff --git a/docs/installation-and-operations/operation/backing-up/README.md b/docs/installation-and-operations/operation/backing-up/README.md index 9351f53c7e3..4e91cbce0fb 100644 --- a/docs/installation-and-operations/operation/backing-up/README.md +++ b/docs/installation-and-operations/operation/backing-up/README.md @@ -59,7 +59,7 @@ If you are using the all-in-one container, then you can simply backup any local ```shell sudo mkdir -p /var/lib/openproject/{pgdata,assets} -docker run -d -p 8080:80 --name openproject -e SECRET_KEY_BASE=secret \ +docker run -d -p 8080:80 --name openproject -e SECRET_KEY_BASE= \ -v /var/lib/openproject/pgdata:/var/openproject/pgdata \ -v /var/lib/openproject/assets:/var/openproject/assets \ openproject/openproject:17 diff --git a/docs/release-notes/17-2-4/README.md b/docs/release-notes/17-2-4/README.md new file mode 100644 index 00000000000..0e42132707e --- /dev/null +++ b/docs/release-notes/17-2-4/README.md @@ -0,0 +1,139 @@ +--- +title: OpenProject 17.2.4 +sidebar_navigation: + title: 17.2.4 +release_version: 17.2.4 +release_date: 2026-05-13 +--- + + # OpenProject 17.2.4 + + Release date: 2026-05-13 + + We released [OpenProject 17.2.4](https://community.openproject.org/versions/2300). + The release contains several bug fixes and we recommend updating to the newest version. + Below you will find a complete list of all changes and bug fixes. + + + +## Security fixes + + + +### CVE-2026-46386 - Docker Container starts with SECRET_KEY_BASE default value + +When an attacker knew the secret key base that the application used to derive internal keys from, they could construct encrypted cookies that on the server side were decoded using [Object Marshalling](https://docs.ruby-lang.org/en/4.0/Marshal.html) which allowed the attacker to execute almost arbitrary ruby code within the container, up to a complete remote code execution. This was especially present in Docker containers that shipped with a default value as the secret key base, when it was not manually overwritten, as mentioned in the documentation. + + + +As a fix, the docker containers now validate that a proper `SECRET_KEY_BASE` environment variable is set Otherwise the application aborts the boot process with an error message. The documentation has been updated to make it even clearer, that the `SECRET_KEY_BASE` env variable must be set. And the decoding of the encrypted cookies has been updated to use JSON encoding instead of Object Marshalling.  + + + +**Administrators that have not set a `SECRET_KEY_BASE` environment before need to set one now. Otherwise the application will not boot.** + + + +**This will force all users using 2 factor authentication to authenticate on their next login, even if they have saved a cookie to skip 2FA for the next 14 days.** + + + +This vulnerability was responsibly reported by GitHub user [hkolvenbach](https://github.com/hkolvenbach). + + + +For more information, please see the [GitHub advisory #GHSA-r85r-gjq2-f83r](https://github.com/opf/openproject/security/advisories/GHSA-r85r-gjq2-f83r) + + + +### CVE-2026-44731 - Improper Access Control on OpenProject through /projects/[projectName]/meetings via "invited_user_id" in GET parameter "filters" leads to user names disclosure + +The web application's meetings filter feature leaks whether a given user ID corresponds to a valid account and discloses the user's full name, allowing an attacker to enumerate all existing user accounts by probing user IDs and observing differences in the server response. + + + +This vulnerability was reported by user tuannq\_gg as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission. + + + +For more information, please see the [GitHub advisory #GHSA-x7j3-cfgf-7mc4](https://github.com/opf/openproject/security/advisories/GHSA-x7j3-cfgf-7mc4) + + + +### CVE-2026-44732 - IDOR on OpenProject through /api/v3/documents/{id} via PATCH parameter "project_id" leads to Unauthorized Modification of Resources + +OpenProject exposes a document update endpoint used to modify existing documents. The target document is loaded with visibility checks and then updated . + + + +During update, attacker-controlled attributes are applied to the persisted record before authorization is enforced. As a result, a user without `:manage_documents` in the source project can move and modify foreign project documents by setting `project_id` in a single PATCH request. + + + +This vulnerability was reported by sam91281 as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission. + + + +For more information, please see the [GitHub advisory #GHSA-mqvv-5mvc-7pg7](https://github.com/opf/openproject/security/advisories/GHSA-mqvv-5mvc-7pg7) + + + +### CVE-2026-44733 - Business Logic Error on OpenProject through PATCH request to /api/v3/users/me permits to bypass password requirements + +A password validation flaw in the change password behavior allows attackers to change a user's password only with an active session takeover. + + + +
+ + + +This vulnerability was reported by user herdiyanitdev as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission. + + + +For more information, please see the [GitHub advisory #GHSA-px7f-cj9f-7m4m](https://github.com/opf/openproject/security/advisories/GHSA-px7f-cj9f-7m4m) + + + +### CVE-2026-44734 - Improper Access Control on OpenProject through the POST request to /projects/[PROJECT_NAME]/cost_reports/[REPORT_ID]/rename + +A Missing Authorization vulnerability exists in OpenProject's CostReportsController. The rename and update actions allow any authenticated user to modify the name, filters, and grouping of any Public cost report in the system without verifying ownership or permission level. + +An attacker who discovers or guesses a public report's numeric ID can rename or overwrite its filter configuration without any warning to the report's owner. + + + +This vulnerability was reported by user herdiyanitdev as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission. + + + +For more information, please see the [GitHub advisory #GHSA-c767-34gh-gh2h](https://github.com/opf/openproject/security/advisories/GHSA-c767-34gh-gh2h) + + + +### CVE-2026-44735 - Shares API Information Disclosure + +The `GET /api/v3/shares` endpoint returns share details for ALL work packages in a project to any user with the `view_shared_work_packages` permission. The authorization check operates at the **project level** only — it does not verify the requesting user can actually view each individual shared work package. + + + +This vulnerability was reported by GitHub user [DAVIDAROCA27](https://github.com/DAVIDAROCA27). + + + +For more information, please see the [GitHub advisory #GHSA-cfg3-f34w-9xx5](https://github.com/opf/openproject/security/advisories/GHSA-cfg3-f34w-9xx5) + + + + + + +## Bug fixes and changes + + + + + + + diff --git a/docs/release-notes/17-3-2/README.md b/docs/release-notes/17-3-2/README.md new file mode 100644 index 00000000000..f0db3f6e761 --- /dev/null +++ b/docs/release-notes/17-3-2/README.md @@ -0,0 +1,142 @@ +--- +title: OpenProject 17.3.2 +sidebar_navigation: + title: 17.3.2 +release_version: 17.3.2 +release_date: 2026-05-13 +--- + + # OpenProject 17.3.2 + + Release date: 2026-05-13 + + We released [OpenProject 17.3.2](https://community.openproject.org/versions/2296). + The release contains several bug fixes and we recommend updating to the newest version. + Below you will find a complete list of all changes and bug fixes. + + + +## Security fixes + + + +### CVE-2026-46386 - Docker Container starts with SECRET_KEY_BASE default value + +When an attacker knew the secret key base that the application used to derive internal keys from, they could construct encrypted cookies that on the server side were decoded using [Object Marshalling](https://docs.ruby-lang.org/en/4.0/Marshal.html) which allowed the attacker to execute almost arbitrary ruby code within the container, up to a complete remote code execution. This was especially present in Docker containers that shipped with a default value as the secret key base, when it was not manually overwritten, as mentioned in the documentation. + + + +As a fix, the docker containers now validate that a proper `SECRET_KEY_BASE` environment variable is set Otherwise the application aborts the boot process with an error message. The documentation has been updated to make it even clearer, that the `SECRET_KEY_BASE` env variable must be set. And the decoding of the encrypted cookies has been updated to use JSON encoding instead of Object Marshalling.  + + + +**Administrators that have not set a `SECRET_KEY_BASE` environment before need to set one now. Otherwise the application will not boot.** + + + +**This will force all users using 2 factor authentication to authenticate on their next login, even if they have saved a cookie to skip 2FA for the next 14 days.** + + + +This vulnerability was responsibly reported by GitHub user [hkolvenbach](https://github.com/hkolvenbach). + + + +For more information, please see the [GitHub advisory #GHSA-r85r-gjq2-f83r](https://github.com/opf/openproject/security/advisories/GHSA-r85r-gjq2-f83r) + + + +### CVE-2026-44731 - Improper Access Control on OpenProject through /projects/[projectName]/meetings via "invited_user_id" in GET parameter "filters" leads to user names disclosure + +The web application's meetings filter feature leaks whether a given user ID corresponds to a valid account and discloses the user's full name, allowing an attacker to enumerate all existing user accounts by probing user IDs and observing differences in the server response. + + + +This vulnerability was reported by user tuannq\_gg as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission. + + + +For more information, please see the [GitHub advisory #GHSA-x7j3-cfgf-7mc4](https://github.com/opf/openproject/security/advisories/GHSA-x7j3-cfgf-7mc4) + + + +### CVE-2026-44732 - IDOR on OpenProject through /api/v3/documents/{id} via PATCH parameter "project_id" leads to Unauthorized Modification of Resources + +OpenProject exposes a document update endpoint used to modify existing documents. The target document is loaded with visibility checks and then updated . + + + +During update, attacker-controlled attributes are applied to the persisted record before authorization is enforced. As a result, a user without `:manage_documents` in the source project can move and modify foreign project documents by setting `project_id` in a single PATCH request. + + + +This vulnerability was reported by sam91281 as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission. + + + +For more information, please see the [GitHub advisory #GHSA-mqvv-5mvc-7pg7](https://github.com/opf/openproject/security/advisories/GHSA-mqvv-5mvc-7pg7) + + + +### CVE-2026-44733 - Business Logic Error on OpenProject through PATCH request to /api/v3/users/me permits to bypass password requirements + +A password validation flaw in the change password behavior allows attackers to change a user's password only with an active session takeover. + + + +
+ + + +This vulnerability was reported by user herdiyanitdev as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission. + + + +For more information, please see the [GitHub advisory #GHSA-px7f-cj9f-7m4m](https://github.com/opf/openproject/security/advisories/GHSA-px7f-cj9f-7m4m) + + + +### CVE-2026-44734 - Improper Access Control on OpenProject through the POST request to /projects/[PROJECT_NAME]/cost_reports/[REPORT_ID]/rename + +A Missing Authorization vulnerability exists in OpenProject's CostReportsController. The rename and update actions allow any authenticated user to modify the name, filters, and grouping of any Public cost report in the system without verifying ownership or permission level. + +An attacker who discovers or guesses a public report's numeric ID can rename or overwrite its filter configuration without any warning to the report's owner. + + + +This vulnerability was reported by user herdiyanitdev as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission. + + + +For more information, please see the [GitHub advisory #GHSA-c767-34gh-gh2h](https://github.com/opf/openproject/security/advisories/GHSA-c767-34gh-gh2h) + + + +### CVE-2026-44735 - Shares API Information Disclosure + +The `GET /api/v3/shares` endpoint returns share details for ALL work packages in a project to any user with the `view_shared_work_packages` permission. The authorization check operates at the **project level** only — it does not verify the requesting user can actually view each individual shared work package. + + + +This vulnerability was reported by GitHub user [DAVIDAROCA27](https://github.com/DAVIDAROCA27). + + + +For more information, please see the [GitHub advisory #GHSA-cfg3-f34w-9xx5](https://github.com/opf/openproject/security/advisories/GHSA-cfg3-f34w-9xx5) + + + + + + +## Bug fixes and changes + + + + +- Bugfix: Performance impact of large Markdown/HTML templates caused by the tagfilter GFM extension \[[#74151](https://community.openproject.org/wp/74151)\] +- Bugfix: Budget widget breaks when lots of cost types defined \[[#74189](https://community.openproject.org/wp/74189)\] +- Bugfix: Direct login prevents authentication from mobile app \[[#74569](https://community.openproject.org/wp/74569)\] + + + diff --git a/docs/release-notes/17-4-0/README.md b/docs/release-notes/17-4-0/README.md index 6336823417b..9ec9a779c70 100644 --- a/docs/release-notes/17-4-0/README.md +++ b/docs/release-notes/17-4-0/README.md @@ -3,7 +3,7 @@ title: OpenProject 17.4.0 sidebar_navigation: title: 17.4.0 release_version: 17.4.0 -release_date: 2026-04-23 +release_date: 2026-05-13 --- # OpenProject 17.4.0 @@ -12,9 +12,8 @@ release_date: 2026-04-23 We released [OpenProject 17.4.0](https://community.openproject.org/versions/2267). The release contains several bug fixes and we recommend updating to the newest version. In these Release Notes, we will give an overview of important feature changes. At the end, you will find a complete list of all changes and bug fixes. - - - +> [!NOTE] +> This release includes several security fixes. [Click here to go directly to them](#security-fixes). ## Important feature changes @@ -26,6 +25,8 @@ Take a look at our release video showing the most important features introduced With the release of OpenProject 17.4, the Jira Migrator is now available without a feature flag and can be used directly. While the feature is not yet fully complete and still in Beta, it is ready to be tested – preferably first in a non productive environment. We encourage users to try the Jira Migrator and share their feedback. +![OpenProject Jira Migrator with note that it is a Beta version, listing supported data and what is coming soon or later](openproject-jira-migrator-beta.png) + > [!NOTE] > If you would like to share anonymized data from your Jira Migrator usage to support our development team, please [reach out to us](https://www.openproject.org/contact/). We are happy to sign an NDA to ensure confidentiality. @@ -33,7 +34,9 @@ It is now possible to migrate basic custom fields from Jira to OpenProject. This We will continue to expand support for additional custom field types in future releases to enable even more complete migrations. -Screenshot +![Work package in OpenProject with imported custom fields from Jira](openproject-17-4-jira-migrator-custom-fields-work-package.png) + +See our documentation to learn more about [the OpenProject Jira Migrator](../../installation-and-operations/jira-migration/). ### Backlog buckets in "Backlog and sprints" view @@ -43,10 +46,14 @@ Work packages can be moved between buckets, sorted within each bucket, and adjus ![Overlay to create a new backlog bucket in OpenProject, user can enter backlog bucket name](openproject-17-4-backlog-bucket-create.png) +[Learn more about Backlog and sprints with OpenProject](../../user-guide/backlogs-scrum/). + ### Backlog card draggable + one-click for side panel Backlog cards are now fully draggable, making it easier to move work packages during backlog refinement and sprint planning. At the same time, you can still open a work package in the side panel with a single click to quickly view and edit details without losing context. +![Gif showing Backlog and sprints in OpenProject, dragging a work package from Backlog bucket to Sprints and then opening it per click in the middle of the card](openproject-17-4-backlog-drag-drop.gif) + ### Sprint Start and Complete buttons in the sprint header You can now start and complete sprints directly from the sprint header by clicking the respective buttons. This makes these actions easier to access and provides a clearer overview of the sprint status. @@ -62,6 +69,8 @@ You can now copy workflow settings from one role to other roles, using a dedicat ![Overlay in the OpenProject workflow settings: Copy workflow of "Task" to other roles, user can select multiple roles](openproject-17-4-workflow-copy-to-roles.png) +[See our system admin guide to learn about work package workflows in OpenProject](../../system-admin-guide/manage-work-packages/work-package-workflows/). + ### New widget for upcoming meetings on Project Overview and Home page A new "My meetings" widget shows your upcoming meetings directly on the Home and Project Overview pages. It displays the most relevant information at a glance, helping you stay on top of your schedule and quickly access upcoming meetings. @@ -76,15 +85,17 @@ The default modules enabled in demo and trial projects have been updated. Budget ## Important technical updates -### Expose project-based semantic work package identifier on the API +### Developers of API V3 clients prepare for 17.5: use the new `displayId` field for rendering work package identifiers instead of `id` -Project-based work package identifiers are now exposed via the API. With upcoming support for semantic identifiers such as `#ABC-123`, a new field `displayId` is available in the API. This field returns the correct identifier format, depending on how the instance is configured. +As part of our efforts to simplify migrations from Jira to OpenProject, the next release (17.5) will introduce project-based work package identifiers such as `#ABC-123`. Developers of API V3 clients can and should already prepare for this change now. With the current release (17.4), the API V3 exposes a dedicated field, `displayId`, which contains the work package’s current identifier. API clients should use this field whenever rendering links or captions intended for human users. -If you are building or maintaining an application using the OpenProject API V3, we recommend using `displayId` instead of `id` when displaying work package identifiers. +Starting with 17.5, administrators will be able to switch from the current numeric identifiers, such as `#12345`, to project-based identifiers like `#ABC-123`. The `displayId` field will return the correct identifier format depending on how the instance is configured. + +If you are building or maintaining an application that uses the OpenProject API V3, we recommend using `displayId` instead of `id` when displaying work package identifiers. The `id` field will continue to return the internal database ID and should still be used for API requests such as filtering. -For more information on project-based work package identifiers in OpenProject, see the [Epic currently being developed by our team](https://community.openproject.org/wp/41855) +For more information about project-based work package identifiers in OpenProject, see the [Epic currently being developed by our team](https://community.openproject.org/wp/41855) ### Meetings and recurring meetings APIv3 endpoints @@ -94,6 +105,108 @@ New APIv3 endpoints are now available for meetings and recurring meetings. These You can now configure webhook secrets for GitHub and GitLab integrations. This improves the security of incoming webhook requests. + + +## Security fixes + +### CVE-2026-46386 - Docker Container starts with SECRET_KEY_BASE default value + +When an attacker knew the secret key base that the application used to derive internal keys from, they could construct encrypted cookies that on the server side were decoded using [Object Marshalling](https://docs.ruby-lang.org/en/4.0/Marshal.html) which allowed the attacker to execute almost arbitrary ruby code within the container, up to a complete remote code execution. This was especially present in Docker containers that shipped with a default value as the secret key base, when it was not manually overwritten, as mentioned in the documentation. + +As a fix, the docker containers now validate that a proper `SECRET_KEY_BASE` environment variable is set. Otherwise the application aborts the boot process with an error message. The documentation has been updated to make it even clearer that the `SECRET_KEY_BASE` env variable must be set. And the decoding of the encrypted cookies has been updated to use JSON encoding instead of Object Marshalling.  + +**Administrators that have not set a** `**SECRET_KEY_BASE**` **environment before need to set one now. Otherwise the application will not boot.** + +**This will force all users using 2 factor authentication to authenticate on their next login, even if they have saved a cookie to skip 2FA for the next 14 days.** + + +Guides to setting this for your installation method: + +* Packaged installations: This secret is already being generated automatically. You are not affected + +* Docker-compose: Add `SECRET_KEY_BASE=` to your .env file. See [docker-compose](https://www.openproject.org/docs/installation-and-operations/installation/docker-compose/) for more information. + +* Docker All-in-One: Add `SECRET_KEY_BASE=` to your docker run call. See [docker](../../installation-and-operations/installation/docker/) for more information. + +* Helm-charts: [Version 13.5.4](https://github.com/opf/helm-charts/releases/) and higher of the helm chart will automatically create a kubernetes secret using a random string. + + * If you have not used a `SECRET_KEY_BASE` env previously, we recommend updating to the newest helm version. + + * If you have an existing strong secret, you are safe already and nothing needs to be done. You _can optionally_ place it as the `existingSecret` as shown [in the Helm chart documentation](https://www.openproject.org/docs/installation-and-operations/installation/helm-chart/#secrets) to use the conventional secret to pass it into the specs. + +This vulnerability was responsibly reported by GitHub user [hkolvenbach](https://github.com/hkolvenbach). + +For more information, please see the [GitHub advisory #GHSA-r85r-gjq2-f83r](https://github.com/opf/openproject/security/advisories/GHSA-r85r-gjq2-f83r). + + + +### CVE-2026-44696 - Stored CSS injection via Sanitize::Config::RELAXED[:css] enables phishing overlays and data exfiltration + +OpenProject's rich text (markdown) rendering pipeline uses `Sanitize::Config::RELAXED[:css]` for inline style sanitization. This configuration permits essentially all CSS properties in `style` attributes on permitted HTML elements (`figure`, `img`, `table`, `th`, `tr`, `td`). + +This allows any authenticated user with write access to formattable text fields (work package descriptions, comments, project descriptions, news) to inject CSS that: + +This vulnerability was reported by GitHub user [NOTTIBOY137](https://github.com/NOTTIBOY137). + +For more information, please see the [GitHub advisory #GHSA-j9q2-49mp-hmq5](https://github.com/opf/openproject/security/advisories/GHSA-j9q2-49mp-hmq5). + +### CVE-2026-44731 - Improper Access Control on OpenProject through /projects/[projectName]/meetings via "invited_user_id" in GET parameter "filters" leads to user names disclosure + +The web application's meetings filter feature leaks whether a given user ID corresponds to a valid account and discloses the user's full name, allowing an attacker to enumerate all existing user accounts by probing user IDs and observing differences in the server response. + +This vulnerability was reported by user tuannq\_gg as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission. + +For more information, please see the [GitHub advisory #GHSA-x7j3-cfgf-7mc4](https://github.com/opf/openproject/security/advisories/GHSA-x7j3-cfgf-7mc4) + + +### CVE-2026-44732 - IDOR on OpenProject through /api/v3/documents/{id} via PATCH parameter "project_id" leads to Unauthorized Modification of Resources + +OpenProject exposes a document update endpoint used to modify existing documents. The target document is loaded with visibility checks and then updated. + +During update, attacker-controlled attributes are applied to the persisted record before authorization is enforced. As a result, a user without `:manage_documents` in the source project can move and modify foreign project documents by setting `project_id` in a single PATCH request. + +This vulnerability was reported by sam91281 as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission. + +For more information, please see the [GitHub advisory #GHSA-mqvv-5mvc-7pg7](https://github.com/opf/openproject/security/advisories/GHSA-mqvv-5mvc-7pg7) + + +### CVE-2026-44733 - Business Logic Error on OpenProject through PATCH request to /api/v3/users/me permits to bypass password requirements + +A password validation flaw in the change password behavior allows attackers to change a user's password only with an active session takeover. + +This vulnerability was reported by user herdiyanitdev as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission. + +For more information, please see the [GitHub advisory #GHSA-px7f-cj9f-7m4m](https://github.com/opf/openproject/security/advisories/GHSA-px7f-cj9f-7m4m) + + +### CVE-2026-44734 - Improper Access Control on OpenProject through the POST request to /projects/[PROJECT_NAME]/cost_reports/[REPORT_ID]/rename + +A Missing Authorization vulnerability exists in OpenProject's CostReportsController. The rename and update actions allow any authenticated user to modify the name, filters, and grouping of any Public cost report in the system without verifying ownership or permission level. + +An attacker who discovers or guesses a public report's numeric ID can rename or overwrite its filter configuration without any warning to the report's owner. + +This vulnerability was reported by user herdiyanitdev as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission. + +For more information, please see the [GitHub advisory #GHSA-c767-34gh-gh2h](https://github.com/opf/openproject/security/advisories/GHSA-c767-34gh-gh2h). + +### CVE-2026-44735 - Shares API Information Disclosure + +The `GET /api/v3/shares` endpoint returns share details for ALL work packages in a project to any user with the `view_shared_work_packages` permission. The authorization check operates at the **project level** only — it does not verify the requesting user can actually view each individual shared work package. + +This vulnerability was reported by GitHub user [DAVIDAROCA27](https://github.com/DAVIDAROCA27). + +For more information, please see the [GitHub advisory #GHSA-cfg3-f34w-9xx5](https://github.com/opf/openproject/security/advisories/GHSA-cfg3-f34w-9xx5). + +### CVE-2026-44736 - Relations API Filter Bypasses Visibility Scope, Leaking Cross-Project Work Package Subjects + +The `GET /api/v3/relations` endpoint allows any authenticated user to retrieve relations — and the **subject (title)** of work packages they have no permission to view — by supplying an arbitrary work package ID in the `involved`, `fromId`, or `toId` filter. This bypasses the `Relation.visible` scope due to a flawed performance optimization in `RelationQuery`. + +This vulnerability was reported by GitHub user [mlgzackfly](https://github.com/mlgzackfly). + +For more information, please see the [GitHub advisory #GHSA-p9gq-hrgh-2645](https://github.com/opf/openproject/security/advisories/GHSA-p9gq-hrgh-2645). + + + ## Bug fixes and changes @@ -109,7 +222,6 @@ You can now configure webhook secrets for GitHub and GitLab integrations. This i - Feature: Better UX for setting project identifiers during project copy \[[#72856](https://community.openproject.org/wp/72856)\] - Feature: Backlog buckets in "Backlog and sprints" view \[[#73081](https://community.openproject.org/wp/73081)\] - Feature: Sprint column, sort and group for work packages table \[[#73104](https://community.openproject.org/wp/73104)\] -- Feature: Support basic custom fields migration \[[#73147](https://community.openproject.org/wp/73147)\] - Feature: Replace danger zones in authentication module with danger dialogs \[[#73355](https://community.openproject.org/wp/73355)\] - Feature: Allow webhook secrets for GitHub and Gitlab integrations \[[#73387](https://community.openproject.org/wp/73387)\] - Feature: Have a sprint start/complete button in the sprint header \[[#73402](https://community.openproject.org/wp/73402)\] @@ -120,67 +232,76 @@ You can now configure webhook secrets for GitHub and GitLab integrations. This i - Feature: Limited move options for work packages in sprint \[[#73563](https://community.openproject.org/wp/73563)\] - Feature: Add widget for upcoming meetings on project overview and home page and remove users widget \[[#73684](https://community.openproject.org/wp/73684)\] - Feature: Expose project-based semantic work package identifier on the API \[[#73735](https://community.openproject.org/wp/73735)\] -- Feature: Add canonical URL meta tags to Project and WP pages for crawler optimization \[[#73926](https://community.openproject.org/wp/73926)\] - Feature: Multi-substring search in project/workspace selector \[[#74199](https://community.openproject.org/wp/74199)\] -- Bugfix: Toast not visible on mobile when page is scrolled down \[[#45673](https://community.openproject.org/wp/45673)\] - Bugfix: Default configuration for Work packages assigned to me on My Page is wrong \[[#57633](https://community.openproject.org/wp/57633)\] +- Bugfix: Form in copy workflow functionality does not render/work properly \[[#58776](https://community.openproject.org/wp/58776)\] - Bugfix: List is scrollable even if there is only 1 item \[[#59732](https://community.openproject.org/wp/59732)\] - Bugfix: Project selector does not read selected items in screenreader \[[#61405](https://community.openproject.org/wp/61405)\] +- Bugfix: Extra space in notification "X days ago by Y user" \[[#63647](https://community.openproject.org/wp/63647)\] +- Bugfix: Wiki menu visible when using the browser's print function/print dialog \[[#67643](https://community.openproject.org/wp/67643)\] - Bugfix: Blank page and error 404 when calendar, board, team planner, role is deleted \[[#68573](https://community.openproject.org/wp/68573)\] - Bugfix: User is redirected to Attribute help text admin after editing a help text from Project overview page \[[#69142](https://community.openproject.org/wp/69142)\] -- Bugfix: Clicking work package tabs triggers page reload and flickering \[[#69210](https://community.openproject.org/wp/69210)\] -- Bugfix: Infinite SAML Seeding Loop Causing Disk Space Exhaustion \[[#69339](https://community.openproject.org/wp/69339)\] -- Bugfix: Days label is cut off \[[#69504](https://community.openproject.org/wp/69504)\] - Bugfix: Work package search input of other user visible \[[#69706](https://community.openproject.org/wp/69706)\] -- Bugfix: Deleted Nextcloud storage stays selected in the PIR template \[[#69767](https://community.openproject.org/wp/69767)\] -- Bugfix: Fix accessibility errors found by ERB Lint \[[#70166](https://community.openproject.org/wp/70166)\] - Bugfix: Deep linking to a meeting outcome does not highlight it \[[#70319](https://community.openproject.org/wp/70319)\] - Bugfix: Helm-Chart: Allow user to provide service specific annotations \[[#71055](https://community.openproject.org/wp/71055)\] - Bugfix: External link capture not working in documents \[[#71111](https://community.openproject.org/wp/71111)\] - Bugfix: Backup: include attachments checkbox cannot be checked \[[#71237](https://community.openproject.org/wp/71237)\] -- Bugfix: Connection error on successive navigation to and from a document \[[#71901](https://community.openproject.org/wp/71901)\] -- Bugfix: Impossible to search for archived projects, page reverts to active projects list on its own \[[#71971](https://community.openproject.org/wp/71971)\] +- Bugfix: NoMethodError in Calendar::ICalController#show \[[#71354](https://community.openproject.org/wp/71354)\] - Bugfix: User cannot create a WP with auto generated subject \[[#72207](https://community.openproject.org/wp/72207)\] - Bugfix: Backlogs: Not able to navigate through the more menu with arrows \[[#72460](https://community.openproject.org/wp/72460)\] -- Bugfix: Click position is lost when activating an inline edit field \[[#72837](https://community.openproject.org/wp/72837)\] -- Bugfix: Incorrect confirmation message when deleting a OAuth token \[[#72958](https://community.openproject.org/wp/72958)\] -- Bugfix: Page loads twice after sprint creation \[[#73316](https://community.openproject.org/wp/73316)\] +- Bugfix: Missing feedback (sucess message) on deleting versions \[[#72719](https://community.openproject.org/wp/72719)\] +- Bugfix: Error 500 when trying to delete a work package with unit costs on a relative URL root \[[#72857](https://community.openproject.org/wp/72857)\] - Bugfix: SCIM User API returns duplicate records \[[#73431](https://community.openproject.org/wp/73431)\] -- Bugfix: POST/PATCH/DELETE requests to APIv3 return unauthorized \[[#73499](https://community.openproject.org/wp/73499)\] - Bugfix: FieldsetGroups are missing descriptions \[[#73501](https://community.openproject.org/wp/73501)\] -- Bugfix: Copy & Paste Loses Formatting in Documents \[[#73669](https://community.openproject.org/wp/73669)\] -- Bugfix: Not possible to follow link custom field from work package list view \[[#73673](https://community.openproject.org/wp/73673)\] - Bugfix: Make sharing options more understandable \[[#73706](https://community.openproject.org/wp/73706)\] - Bugfix: Doubled scrollbar on a Board \[[#73714](https://community.openproject.org/wp/73714)\] - Bugfix: Pressing "Load more" in the backlog, then drag n dropping a work package will restore the original backlog list \[[#73731](https://community.openproject.org/wp/73731)\] -- Bugfix: "Start sprint" button remains active after browser back button \[[#73749](https://community.openproject.org/wp/73749)\] - Bugfix: Work packages can be assigned to closed sprints \[[#73750](https://community.openproject.org/wp/73750)\] - Bugfix: Sprints column scroll bar overlaps content \[[#73831](https://community.openproject.org/wp/73831)\] - Bugfix: Projects filter does not work with project list export \[[#73841](https://community.openproject.org/wp/73841)\] - Bugfix: 401 error does not help user during jira import. \[[#73844](https://community.openproject.org/wp/73844)\] +- Bugfix: Plain text Password received on account creation \[[#73849](https://community.openproject.org/wp/73849)\] - Bugfix: Skip closed meetings when using "Move to next"/"Duplicate in next" for an agenda item \[[#73900](https://community.openproject.org/wp/73900)\] - Bugfix: Onboarding tour breaks at Team planner if EE is missing \[[#73910](https://community.openproject.org/wp/73910)\] - Bugfix: External links cause two blank tabs/windows \[[#73914](https://community.openproject.org/wp/73914)\] - Bugfix: Browser title is truncated after 70 characters \[[#73986](https://community.openproject.org/wp/73986)\] - Bugfix: Wrong timezone for timestamp of work package pdf export \[[#74117](https://community.openproject.org/wp/74117)\] - Bugfix: Trial instance seeded recurring meeting template is in draft mode \[[#74150](https://community.openproject.org/wp/74150)\] -- Bugfix: Automatically converting project identifiers should not lead to usage of reserved keywords \[[#74161](https://community.openproject.org/wp/74161)\] - Bugfix: Sprint Filter in work packages only loads 100 Sprints \[[#74166](https://community.openproject.org/wp/74166)\] -- Bugfix: Moving work packages after switching to semantic and back should not lead to errors \[[#74192](https://community.openproject.org/wp/74192)\] - Bugfix: Backlog card drag preview has incorrect padding \[[#74195](https://community.openproject.org/wp/74195)\] +- Bugfix: 500 Error on Save when 'Group' selected as User under "Planned Labor Costs" in Budget \[[#74197](https://community.openproject.org/wp/74197)\] - Bugfix: Having "Manage sprint items" without "Edit work packages" fails to move work packages in backlogs \[[#74201](https://community.openproject.org/wp/74201)\] - Bugfix: Users can execute custom actions despite conditions not applying \[[#74294](https://community.openproject.org/wp/74294)\] -- Bugfix: Cancel occurence action item is called 'Delete' on My Meetings page \[[#74303](https://community.openproject.org/wp/74303)\] -- Bugfix: User cannot restore a cancelled occurrence if series has a deleted WP on the agenda \[[#74304](https://community.openproject.org/wp/74304)\] -- Feature: UX improvements for workflow configuration - quick wins \[[#71530](https://community.openproject.org/wp/71530)\] -- Feature: Provide project templates within new OpenProject instances \[[#72778](https://community.openproject.org/wp/72778)\] +- Bugfix: "Load X more items" link is missing from the backlog \[[#74317](https://community.openproject.org/wp/74317)\] +- Bugfix: Details tab navigation arrow / symbol disappears after first selection \[[#74320](https://community.openproject.org/wp/74320)\] +- Bugfix: Replace user number with relevant information during project(s) migration \[[#74323](https://community.openproject.org/wp/74323)\] +- Bugfix: Issues in work package sharing notification email \[[#74342](https://community.openproject.org/wp/74342)\] +- Bugfix: Make it obvious that Jira Migrator is in Beta status. \[[#74343](https://community.openproject.org/wp/74343)\] +- Bugfix: Move down link shown incorrectly for 1 backlog bucket item \[[#74351](https://community.openproject.org/wp/74351)\] +- Bugfix: Backlogs all state is not preserved when creating a new sprint or a backlog bucket \[[#74352](https://community.openproject.org/wp/74352)\] +- Bugfix: Slack integration not properly listed in Integrations sublevel \[[#74355](https://community.openproject.org/wp/74355)\] +- Bugfix: All backlog state is not passed to backlog buckets \[[#74356](https://community.openproject.org/wp/74356)\] +- Bugfix: No specific validations present for closed meetings \[[#74372](https://community.openproject.org/wp/74372)\] +- Bugfix: No max limit for password length \[[#74399](https://community.openproject.org/wp/74399)\] +- Bugfix: Sprint filter in ticket detail only loads 25 sprints \[[#74516](https://community.openproject.org/wp/74516)\] +- Bugfix: Errors with "include project" work package list filter with a portfolio \[[#74536](https://community.openproject.org/wp/74536)\] +- Bugfix: Migration: 20250929070310 AddViewAllPrincipalsPermissionToExistingRoles failing on upgrade \[[#74539](https://community.openproject.org/wp/74539)\] +- Bugfix: The all parameter is lost when using the finish sprint dialog \[[#74636](https://community.openproject.org/wp/74636)\] +- Bugfix: "Subproject of" macro not working anymore \[[#74676](https://community.openproject.org/wp/74676)\] +- Bugfix: Copying a work package with instance wide sharing leads to two projects sharing instance wide \[[#74721](https://community.openproject.org/wp/74721)\] +- Bugfix: Meeting current\_schedule\_end is wrong \[[#74729](https://community.openproject.org/wp/74729)\] +- Bugfix: Configured redirect after log in (authentication) does not work \[[#74756](https://community.openproject.org/wp/74756)\] +- Feature: Jira Migrator imports custom fields \[[#73147](https://community.openproject.org/wp/73147)\] ## Contributions + A very special thank you goes to Helmholtz-Zentrum Berlin, City of Cologne, Deutsche Bahn and ZenDiS for sponsoring released or upcoming features. Your support, alongside the efforts of our amazing Community, helps drive these innovations. Also a big thanks to our Community members for reporting bugs and helping us identify and provide fixes. Special thanks for reporting and finding bugs go to Andreas H., Madhu Reddy, and Anna Mund. +We also want to thank Community contributor [K. Uihlein](https://github.com/kuihlein) for contributing to our documentation of the OpenProject GitLab Integration. This is much appreciated. + Last but not least, we are very grateful for our very engaged translation contributors on Crowdin, who translated quite a few OpenProject strings! This release we would like to particularly thank the following users: - [Samo](https://crowdin.com/profile/samoe), for a great number of translations into Turkish. diff --git a/docs/release-notes/17-4-0/openproject-17-4-backlog-drag-drop.gif b/docs/release-notes/17-4-0/openproject-17-4-backlog-drag-drop.gif new file mode 100644 index 00000000000..affa8f9b601 Binary files /dev/null and b/docs/release-notes/17-4-0/openproject-17-4-backlog-drag-drop.gif differ diff --git a/docs/release-notes/17-4-0/openproject-17-4-jira-migrator-custom-fields-work-package.png b/docs/release-notes/17-4-0/openproject-17-4-jira-migrator-custom-fields-work-package.png new file mode 100644 index 00000000000..fd20267cd2a Binary files /dev/null and b/docs/release-notes/17-4-0/openproject-17-4-jira-migrator-custom-fields-work-package.png differ diff --git a/docs/release-notes/17-4-0/openproject-jira-migrator-beta.png b/docs/release-notes/17-4-0/openproject-jira-migrator-beta.png new file mode 100644 index 00000000000..7498854ec44 Binary files /dev/null and b/docs/release-notes/17-4-0/openproject-jira-migrator-beta.png differ diff --git a/docs/release-notes/17-4-0/openproject-jira-migrator-custom-fields-comparison.png b/docs/release-notes/17-4-0/openproject-jira-migrator-custom-fields-comparison.png new file mode 100644 index 00000000000..c4f268966cb Binary files /dev/null and b/docs/release-notes/17-4-0/openproject-jira-migrator-custom-fields-comparison.png differ diff --git a/docs/release-notes/17-5-0/README.md b/docs/release-notes/17-5-0/README.md new file mode 100644 index 00000000000..d9baa5d7494 --- /dev/null +++ b/docs/release-notes/17-5-0/README.md @@ -0,0 +1,48 @@ +--- +title: OpenProject 17.5.0 +sidebar_navigation: + title: 17.5.0 +release_version: 17.5.0 +release_date: 2026-06-10 +--- + + # OpenProject 17.5.0 + + Release date: 2026-06-10 + + TODO + + + + + +## Important feature changes + +TODO + +## Important technical updates + +### Session authentication relies on new header for non-GET requests + +Previously when making session-authenticated requests to APIv3 endpoints, non-GET requests were only allowed when the +HTTP Header `X-Requested-With: XMLHttpRequest` was present. This header is usually associated with frameworks such as jQuery, +but is also added for all requests originating from the OpenProject frontend still. For session authentication it served the +purpose of preventing cross-site request forgery, e.g. through simple HTTP forms. + +The usage of this header has now been replaced with a check for `Sec-Fetch-Site: same-origin`, which is added by a browser automatically +to requests and also can't be added or altered through JavaScript. It's unlikely that this causes any disruptions, because session authentication +should only be used for browser-contexts, where the new header will still be present. Non-browser API-access should use different authentication +methods (e.g. OAuth or API tokens), which are not affected by this change. + +## Bug fixes and changes + + + + + + + +## Contributions + +TODO + diff --git a/docs/release-notes/README.md b/docs/release-notes/README.md index 3ec076a4427..4ea9dd89bdb 100644 --- a/docs/release-notes/README.md +++ b/docs/release-notes/README.md @@ -13,6 +13,27 @@ Stay up to date and get an overview of the new features included in the releases +## 17.4.0 + +Release date: 2026-05-13 + +[Release Notes](17-4-0/) + + +## 17.3.2 + +Release date: 2026-05-13 + +[Release Notes](17-3-2/) + + +## 17.2.4 + +Release date: 2026-05-13 + +[Release Notes](17-2-4/) + + ## 17.3.1 Release date: 2026-04-20 diff --git a/docs/user-guide/backlogs-scrum/README.md b/docs/user-guide/backlogs-scrum/README.md index 8ec545a3834..e308d646b14 100644 --- a/docs/user-guide/backlogs-scrum/README.md +++ b/docs/user-guide/backlogs-scrum/README.md @@ -3,46 +3,85 @@ sidebar_navigation: title: Backlogs (Scrum) priority: 850 description: Support your Scrum methodology with Backlogs -keywords: backlogs, scrum, backlog, agile, sprint, sprint bucket +keywords: backlogs, scrum, backlog, agile, sprint, sprint bucket, backlog bucket --- # Backlog and sprints > [!NOTE] -> The **Backlogs** module is getting a reset with OpenProject 17.3 release and will undergo further changes in the upcoming versions. We will keep updating the documentation over time to reflect these changes. +> The **Backlogs module** is actively being improved. This documentation is updated regularly to reflect the latest changes. -Working in agile project teams is becoming increasingly important, and with OpenProject, it is easier than ever. - -OpenProject supports your work with the Agile and Scrum methodology by providing a variety of improved functionalities. You can now create and manage sprints, record and prioritize work packages in sprints and the backlog, use automated sprint boards or burndown-charts, and much more. For more information, please refer to the OpenProject [agile and scrum features](https://www.openproject.org/collaboration-software-features/agile-project-management/) page. +Working in agile project teams is becoming increasingly important, and with OpenProject, it is easier than ever. OpenProject supports your work with the Agile and Scrum methodology by providing a variety of improved functionalities. You can now create and manage sprints, record and prioritize work packages in sprints and the backlog, use automated sprint boards or burndown-charts, and much more. For more information, please refer to the OpenProject [agile and scrum features](https://www.openproject.org/collaboration-software-features/agile-project-management/) page.
- - A **Backlog** is defined as a module that allows you to use the backlogs feature in OpenProject. In order to use backlogs in a project, the Backlogs module has to be activated in the project settings. -
Please note that this user guide does not represent an introduction to Scrum methodology, but merely explains the Scrum-related functionalities and user instructions in OpenProject. -![Backlogs module in OpenProject showing backlog items and multiple sprints with work packages](openproject_user_guide_backlogs_sprints.png) - ## Manage the backlog -The Backlog is automatically populated based on the work packages in a project that are not yet in sprints. When you add a work package to a sprint, or close it, the work package will no longer be visible in the backlog. +The Backlogs module is divided into two sides: on the left, you'll find the **Backlog**, consisting of the **Inbox backlog** at the bottom and any **Backlog buckets** above it (if created). On the right, **Sprints** are displayed. If no backlog buckets have been created, only the Inbox backlog is shown on the left. -When there are too many items in the Backlog, a **Show more items** link appears in the middle. This compacts the middle section so that you always see the top and the bottom of the backlog. +![Backlogs module in OpenProject showing backlog items and multiple sprints with work packages](openproject_user_guide_backlog_bucket.png) -![Backlog view with many items collapsed behind a “Show more items” link in the middle](openproject_user_guide_backlogs_show_more_items.png) +### Backlog buckets -Next to every work package listed in the backlog or sprint, you can access the **More (three dots)** menu, including the following options: +Backlog buckets help you organize and prioritize work packages within the backlog. A backlog bucket is a named list of work packages that can be refined independently from the Inbox backlog. The Backlog and sprints view displays all backlog buckets within the current project, including the bucket name and the number of contained work packages. -- Open **details view** or **fullscreen view** of a work package. These options allow you to choose how much information (about the backlog item) you'd like to be displayed. +> [!NOTE] +> Backlog buckets are project-specific and are not shared across projects. + +Backlog buckets are ordered alphabetically by name, while the **Inbox backlog** is always displayed at the bottom. + +A work package: + +- can only belong to one backlog bucket at a time. +- cannot belong to a sprint and a backlog bucket at the same time. +- cannot belong to a backlog bucket and the Inbox backlog at the same time. + +You can sort work packages within a backlog bucket via drag and drop or by using the **Move** option from the work package menu. + +#### Create a backlog bucket + +To create a backlog bucket, click the **+ Backlog bucket** button in the Backlog and sprints view. This will open a dialog where you can enter the bucket name. Click **Create** to proceed. + +> [!NOTE] +> Creating and deleting backlog buckets requires the appropriate project permissions. + +![Backlogs module in OpenProject showing backlog items and multiple sprints with work packages](openproject_user_guide_backlog_add_bucket.png) + +#### Rename or delete a backlog bucket + +Open the **More (three dots)** menu of a backlog bucket to: + +- Rename the backlog bucket +- Delete the backlog bucket + +When deleting a backlog bucket, all contained work packages are automatically moved to the bottom of the Inbox backlog. + +### Inbox backlog + +The Inbox backlog is automatically populated with all work packages in a project that are not yet assigned to a sprint or backlog bucket. When a work package is added to a sprint or bucket, or closed, it is removed from the Inbox. + +> [!NOTE] +> Closed work packages are removed from the Inbox backlog and backlog buckets, but continue to be visible in sprints. + +When there are too many items in the backlog, a **Show more items** link appears in the middle of the Inbox backlog. This collapses the middle section so that you always see the top and the bottom of the Inbox backlog. + +![Backlog view with many items collapsed behind a "Show more items" link in the middle](openproject_user_guide_backlogs_show_more_items.png) + +## Sort and move work packages + +Next to every work package listed in the Inbox backlog, backlog bucket, or sprint, you can access the **More (three dots)** menu, including the following options: + +- Open **details view** or **fullscreen view** of a work package. These options allow you to choose how much information (about the backlog item) you'd like to be displayed. - **Copy** the work package URL or ID to the clipboard. - **Move** a work package. ![Backlog work package menu with options like details view, copy link, and move](openproject_user_guide_backlogs_menu_items.png) -Details view opens the work package information on the right side, the same way as in the notifications center. +Details view opens the work package information on the right side, the same way as in the notifications center. You can open the details view with a single click on a work package card. ![Backlog item opened in details view on the right side panel](openproject_user_guide_backlogs_open_details_view.png) @@ -50,16 +89,23 @@ Opening the fullscreen view opens the work package in fullscreen. ![Work package opened in fullscreen in OpenProject](openproject_user_guide_backlogs_fullscreen_view.png) -With the **Move** option, you can order items according to your preference within the backlog or move them to a sprint. You can also drag and drop the work packages. +You can prioritize work packages within the Inbox backlog, a backlog bucket, or a sprint by dragging and dropping them or by using the **Move** option from the work package menu. The entire work package card can be used as a drag-and-drop area. + +Depending on the current location of the work package, you can move it: + +- within the current backlog bucket or sprint, +- into another backlog bucket, +- into another sprint, +- back to the Inbox backlog. ![Move options menu for a backlog item showing reorder and sprint assignment options](openproject_user_guide_backlog_move_options.png) ## Create and manage sprints -> [!IMPORTANT] -> Starting with the OpenProject 17.3 release, Sprints are new objects no longer linked to versions (as was the case with previous OpenProject versions). +> [!IMPORTANT] +> Starting with the OpenProject 17.3 release, Sprints are new objects no longer linked to versions (as was the case with previous OpenProject versions). -A **Sprint** is a planned and time-boxed period in which a Scrum team completes a defined set of tasks. They are containers or buckets where work packages can be manually added or removed from the Backlog via a drag-and-drop icon. +A **Sprint** is a planned and time-boxed period in which a Scrum team completes a defined set of tasks. They are containers where work packages can be manually added or removed from the Inbox backlog or backlog buckets via drag and drop or the menu. ### Create a sprint @@ -71,28 +117,39 @@ The naming of sprints is number-based by default (e.g. Sprint 1, Sprint 2). Thes ### Start or complete a sprint -Your sprint is set in motion by clicking the **Start sprint** button. Clicking it will open the sprint board. +Your sprint is set in motion by clicking the **Start** button in the sprint header. Clicking it will open the sprint board. > [!NOTE] -> A sprint cannot be started if another sprint is already in progress. In this case the button will be disabled. +> A sprint cannot be started if another sprint is already in progress. In this case, the button will be disabled. ![Start sprint button in the Backlogs module interface](openproject_user_guide_backlogs_start_sprint.png) -Once a sprint has started, it is considered active and can be managed through the **Sprint menu** options, which include: +Once a sprint has started, it is considered active. The sprint header displays the current sprint status and allows you to complete the sprint directly from the header. To complete a sprint, click the **Complete** button in the sprint header. + +![Complete sprint button in the Backlogs module interface](openproject_user_guide_backlogs_complete_sprint.png) + +If there are still unfinished work packages in the sprint, a dialog will open prompting you to decide what should happen to them. You can choose to: + +- Move them to the top of the Inbox backlog +- Move them to the bottom of the Inbox backlog +- Move them to another sprint + +If you choose to move work packages to another sprint, you will need to select the target sprint from the list. After making your selection, click the **Complete sprint** button to finish the sprint. The sprint will then be marked as completed, and other sprints can be started. + +![Form to select how to proceed with items in progress when completing a sprint in OpenProject](openproject_user_guide_backlogs_complete_sprint_wp_in_progress.png) + +Additional sprint actions are available through the **Sprint menu**, including: -- Complete sprint - Edit sprint - Add work package -- Open a Sprint board +- Sprint board - Burndown chart -![Sprint menu with options like edit sprint, add work package, board, and burndown chart](openproject_user_guide_backlog_sprint_menu_item.png) +![Sprint menu with options like edit sprint and add work package](openproject_user_guide_backlog_sprint_menu_item.png) ### Add a work package -In order to create a new work package in the Backlogs module, click on the More (three dots) icon in the top right corner of a Sprint and choose **+ Add work package** from the drop-down menu. - -A form dialog will appear to create a new work package. Here, you directly specify the work package type, subject, and description. Click **Create** to proceed. +In order to create a new work package in the Backlogs module, click on the More (three dots) icon in the top right corner of a Sprint and choose **+ Add work package** from the drop-down menu. A form dialog will appear to create a new work package. Here, you directly specify the work package type, subject, and description. Click **Create** to proceed. ![A new work package added to a sprint directly in OpenProject Backlogs module](openproject_user_guide_backlogs_new_wp_form.png) @@ -100,15 +157,16 @@ A new item will be added to the backlog to display the newly created story. ### Prioritize stories -You can prioritize different work packages within a backlog or sprint by either using the **Move** option or by dragging & dropping them. This allows you to assign work packages to a specific sprint, return to a backlog or re-order them within a sprint. +You can prioritize different work packages within the Inbox backlog, a backlog bucket, or a sprint by either using the **Move** option or by dragging & dropping them. This allows you to assign work packages to a specific sprint or backlog bucket, return them to the Inbox backlog, or re-order them within a sprint or bucket. ### Story points -In a sprint, you can directly document necessary effort as story points. The overall effort for a sprint is automatically calculated, and the sum of story points is displayed in the top row. +In a sprint, you can directly document necessary effort as story points.
Story points are defined as numbers assigned to a work package used to estimate (relatively) the size of the work.
+ ![Story points assigned to work packages in a sprint in OpenProject backlogs module](openproject_user_guide_backlogs_story_points.png) You can edit story points directly from the backlogs view. In order to do so, simply click the work package you want to edit and make the desired changes in the detailed view of the work package that will open on the right. @@ -117,9 +175,9 @@ You can edit story points directly from the backlogs view. In order to do so, si ### Sprint boards -Sprint boards are especially helpful for teams to track and visualize progress from the start. When you click the **Start sprint** within the Backlog, a dedicated sprint board is automatically created and you are forwarded to the active sprint board. +Sprint boards are especially helpful for teams to track and visualize progress from the start. Sprint boards replace the previous task boards used in earlier versions of the Backlogs module. -Boards are named using this pattern: [Project name: Sprint name]. As an example: **Scrum project: Sprint 1**. +When you click the **Start** button in a sprint header, a dedicated sprint board is automatically created and you are forwarded to the active sprint board. Boards are named using this pattern: [Project name: Sprint name]. As an example: **Scrum project: Sprint 1**. The sprint board inherits project permissions automatically, which means it is accessible to all project members by default. @@ -128,17 +186,26 @@ The sprint board inherits project permissions automatically, which means it is a > [!NOTE] > The sprint board and burndown chart are only visible on the menu when a sprint is active. +### Sprint field in work package tables + +The Sprint property can also be used in work package tables. You can: + +- display the Sprint column, +- sort by Sprint, +- group work packages by Sprint. + +> [!NOTE] +> Viewing Sprint information in work package tables requires the appropriate project permissions. + ### Burndown charts -**Burndown charts** are a helpful tool to visualize a sprint’s progress. With OpenProject, you can generate sprint and task burndown charts automatically. +**Burndown charts** are a helpful tool to visualize a sprint's progress. With OpenProject, you can generate sprint and task burndown charts automatically. > [!TIP] -> As a precondition, the sprint’s start and end date must be defined and the information on story points should be well maintained. +> As a precondition, the sprint's start and end date must be defined and the information on story points should be well maintained. -The sprint burndown is calculated from the sum of estimated story points. If a user story is set to “closed“ (or another status which is defined as closed (see admin settings)), it counts towards the burndown. - -The task burndown is calculated from the estimated number of hours necessary to complete a task. If a task is set to “closed“, the burndown is adjusted. +The sprint burndown is calculated from the sum of estimated story points. If a user story is set to "closed" (or another status which is defined as closed (see admin settings)), it counts towards the burndown. The task burndown is calculated from the estimated number of hours necessary to complete a task. If a task is set to "closed", the burndown is adjusted. The remaining story points per sprint are displayed in the chart. Optionally, the ideal burn-down can be displayed for reference. The ideal burndown assumes a linear completion of story points from the beginning to the end of a sprint. -![An example of a burndown chart in OpenProject](openproject_user_guide_backlogs_burndown_chart_example.png) \ No newline at end of file +![An example of a burndown chart in OpenProject](openproject_user_guide_backlogs_burndown_chart_example.png) diff --git a/docs/user-guide/backlogs-scrum/openproject_user_guide_backlog_add_bucket.png b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlog_add_bucket.png new file mode 100644 index 00000000000..5b490b4b164 Binary files /dev/null and b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlog_add_bucket.png differ diff --git a/docs/user-guide/backlogs-scrum/openproject_user_guide_backlog_bucket.png b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlog_bucket.png new file mode 100644 index 00000000000..96a7523d937 Binary files /dev/null and b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlog_bucket.png differ diff --git a/docs/user-guide/backlogs-scrum/openproject_user_guide_backlog_sprint_menu_item.png b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlog_sprint_menu_item.png index 54635fe3de9..8830774ebff 100644 Binary files a/docs/user-guide/backlogs-scrum/openproject_user_guide_backlog_sprint_menu_item.png and b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlog_sprint_menu_item.png differ diff --git a/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_complete_sprint.png b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_complete_sprint.png new file mode 100644 index 00000000000..e52952a1f98 Binary files /dev/null and b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_complete_sprint.png differ diff --git a/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_complete_sprint_wp_in_progress.png b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_complete_sprint_wp_in_progress.png new file mode 100644 index 00000000000..844ca2c074c Binary files /dev/null and b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_complete_sprint_wp_in_progress.png differ diff --git a/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_start_button_sprint.png b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_start_button_sprint.png new file mode 100644 index 00000000000..6dc764d9300 Binary files /dev/null and b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_start_button_sprint.png differ diff --git a/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_start_sprint.png b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_start_sprint.png index cb832841c2e..5d4d17bed75 100644 Binary files a/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_start_sprint.png and b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_start_sprint.png differ diff --git a/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_story_points.png b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_story_points.png index 4148a9cb7f5..d99e807b22b 100644 Binary files a/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_story_points.png and b/docs/user-guide/backlogs-scrum/openproject_user_guide_backlogs_story_points.png differ diff --git a/docs/user-guide/projects/project-home/README.md b/docs/user-guide/projects/project-home/README.md index 225e8d15817..1e5e63ff7f0 100644 --- a/docs/user-guide/projects/project-home/README.md +++ b/docs/user-guide/projects/project-home/README.md @@ -25,7 +25,7 @@ The project home page is a centralized overview page that displays all essential You can access the project overview by navigating to **Project home** in the project menu on the left. -![Project overview page for a specific project in OpenProject](openproject_user_guide_project_home_page.png) +![Project overview page for a specific project in OpenProject](openproject_user_guide_project_new_home_page.png) On the project overview page you will see the following sections: @@ -40,7 +40,7 @@ Here you can also [mark a project as favorite](#mark-a-project-as-favorite) or [ -![Pre-set project widgets on the overview tab of the project home page](openproject_user_guide_project_home_overview_tab.png) +![Pre-set project widgets on the overview tab of the project home page](openproject_user_guide_project_home_new_overview_tab.png) The **Overview** tab is a pre-configured to provide a concise summary of the project’s status. Its layout is fixed and cannot be modified. It includes the following widgets: @@ -49,11 +49,13 @@ The **Overview** tab is a pre-configured to provide a concise summary of the pro - [Status](./project-widgets/#status-widget) - [Subitems](./project-widgets/#subitems-widget) + - [Meetings](project-widgets/#meetings-widget) + > [!TIP] + > If you do not see the meeting widgets displayed, make sure the Meetings module is activated for the project. - [Members](./project-widgets/#members-widget) - [News](project-widgets/#news-widget) - - [Budgets](project-widgets/#budgets-widgets) > [!TIP] diff --git a/docs/user-guide/projects/project-home/openproject_user_guide_project_home_new_overview_tab.png b/docs/user-guide/projects/project-home/openproject_user_guide_project_home_new_overview_tab.png new file mode 100644 index 00000000000..a3df4b74d9a Binary files /dev/null and b/docs/user-guide/projects/project-home/openproject_user_guide_project_home_new_overview_tab.png differ diff --git a/docs/user-guide/projects/project-home/openproject_user_guide_project_new_home_page.png b/docs/user-guide/projects/project-home/openproject_user_guide_project_new_home_page.png new file mode 100644 index 00000000000..b48ee00636f Binary files /dev/null and b/docs/user-guide/projects/project-home/openproject_user_guide_project_new_home_page.png differ diff --git a/docs/user-guide/projects/project-home/project-widgets/README.md b/docs/user-guide/projects/project-home/project-widgets/README.md index 8e3f8fb6a28..1ef88538473 100644 --- a/docs/user-guide/projects/project-home/project-widgets/README.md +++ b/docs/user-guide/projects/project-home/project-widgets/README.md @@ -236,6 +236,18 @@ Actual costs include labor costs from logged time and unit costs from cost entri Click the **View actual cost details** link at the bottom of the widget to open the cost reports filtered for the current project. +## Meetings widget +This widget provides an overview of your relevant and upcoming meetings. +> [!NOTE] +> The Meetings widget is only displayed on the **Overview tab** of a project home page. + +For the meetings widget to be displayed on your project home page, the Meetings module has to be activated first. Once activated, all meetings you are participating in are displayed. + +![Meetings widget displayed under Over tab on Project home page in OpenProject](openproject_user_guide_project_overview_all_meetings_widget.png) + +If there are no meetings in your project yet and the Meetings module is activated, the widget allows you to create a new meeting directly by clicking the **+ Meeting** button. + +![Meetings widget displayed under Over tab on Project home page in OpenProject, with no meetings created yet](openproject_user_guide_project_create_meeting_widget.png) ## Resize and reorder widgets diff --git a/docs/user-guide/projects/project-home/project-widgets/openproject_user_guide_project_create_meeting_widget.png b/docs/user-guide/projects/project-home/project-widgets/openproject_user_guide_project_create_meeting_widget.png new file mode 100644 index 00000000000..088b494e581 Binary files /dev/null and b/docs/user-guide/projects/project-home/project-widgets/openproject_user_guide_project_create_meeting_widget.png differ diff --git a/docs/user-guide/projects/project-home/project-widgets/openproject_user_guide_project_overview_all_meetings_widget.png b/docs/user-guide/projects/project-home/project-widgets/openproject_user_guide_project_overview_all_meetings_widget.png new file mode 100644 index 00000000000..58fb048868e Binary files /dev/null and b/docs/user-guide/projects/project-home/project-widgets/openproject_user_guide_project_overview_all_meetings_widget.png differ diff --git a/docs/user-guide/projects/project-home/project-widgets/openproject_user_guide_project_overview_meetings_widget.png b/docs/user-guide/projects/project-home/project-widgets/openproject_user_guide_project_overview_meetings_widget.png new file mode 100644 index 00000000000..8654dfc2585 Binary files /dev/null and b/docs/user-guide/projects/project-home/project-widgets/openproject_user_guide_project_overview_meetings_widget.png differ diff --git a/docs/user-guide/wiki/wiki-faq/README.md b/docs/user-guide/wiki/wiki-faq/README.md index d039da24c7f..defb0585df6 100644 --- a/docs/user-guide/wiki/wiki-faq/README.md +++ b/docs/user-guide/wiki/wiki-faq/README.md @@ -34,7 +34,7 @@ You have probably unchecked the option “show as menu item in project navigatio ## What is the markup language of the wiki in OpenProject? -The wiki syntax used in OpenProject is Textile. +The wiki syntax that is used internally in OpenProject is GitHub-flavored Markdown (GFM) + HTML for specific widgets. Prior to OpenProject 8.0, the platform used Textile. However, any legacy Textile content is automatically migrated to the GFM Markdown format during system upgrades. ## I am not used to Textile - is there any documentation where I can get help? diff --git a/frontend/angular.json b/frontend/angular.json index 041ea30c195..0a5f664086c 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -151,7 +151,9 @@ "tsConfig": "tsconfig.spec.json", "buildTarget": "OpenProject:build", "providersFile": "src/test-providers.ts", - "setupFiles": ["src/test-setup.ts"] + "setupFiles": ["src/test-setup.ts"], + "browsers": ["chromium", "firefox", "webkit"], + "reporters": ["dot"] } }, "lint": { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f5b21a17811..1cf4c73c8a5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -139,6 +139,8 @@ "@html-eslint/eslint-plugin": "^0.59.0", "@html-eslint/parser": "^0.59.0", "@stylistic/eslint-plugin": "^5.7.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.3.2", "@types/codemirror": "5.60.5", "@types/dom-navigation": "^1.0.7", "@types/dragula": "^3.7.5", @@ -159,7 +161,8 @@ "@types/webpack-env": "^1.16.0", "@typescript-eslint/eslint-plugin": "8.53.0", "@typescript-eslint/parser": "8.53.1", - "@vitest/coverage-v8": "^4.1.5", + "@vitest/browser-playwright": "^4.1.6", + "@vitest/coverage-v8": "^4.1.6", "@vitest/eslint-plugin": "^1.6.16", "angular-eslint": "^21.1.0", "browserslist": "^4.28.1", @@ -170,11 +173,12 @@ "eslint-plugin-react-hooks": "^7.0.1", "globals": "^17.5.0", "jsdom": "^29.1.1", + "playwright": "^1.59.1", "source-map-explorer": "^2.5.2", "ts-node": "~10.9.2", "typescript": "^5.9.3", "typescript-eslint": "^8.56.1", - "vitest": "^4.1.5", + "vitest": "^4.1.6", "wscat": "^6.1.0" }, "optionalDependencies": { @@ -4028,6 +4032,13 @@ "node": ">=18" } }, + "node_modules/@blazediff/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz", + "integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==", + "dev": true, + "license": "MIT" + }, "node_modules/@blocknote/core": { "version": "0.44.2", "resolved": "https://registry.npmjs.org/@blocknote/core/-/core-0.44.2.tgz", @@ -8190,6 +8201,13 @@ "node": ">=20.0.0" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, "node_modules/@primer/behaviors": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.3.5.tgz", @@ -9240,6 +9258,64 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@tiptap/core": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.12.0.tgz", @@ -9583,6 +9659,13 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -10956,15 +11039,72 @@ "vite": "^6.0.0 || ^7.0.0" } }, + "node_modules/@vitest/browser": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.6.tgz", + "integrity": "sha512-ynsspTubXGSpa58JFJ24xIQt4z4A25epSbugEyaTmmrV1//Wec9EgE/LtoaC6yxUrXi5P7erGHRrkdZIHaVQuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@blazediff/core": "1.9.1", + "@vitest/mocker": "4.1.6", + "@vitest/utils": "4.1.6", + "magic-string": "^0.30.21", + "pngjs": "^7.0.0", + "sirv": "^3.0.2", + "tinyrainbow": "^3.1.0", + "ws": "^8.19.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.1.6" + } + }, + "node_modules/@vitest/browser-playwright": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.6.tgz", + "integrity": "sha512-4csoeyl/qwHyxU2zNL0++WaoDr8YJDXOQPwWPNJoTZ+QzcdO3INYKgF5Zfz730Io7zbkuv914aZmfQ+QE+1Hvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/browser": "4.1.6", + "@vitest/mocker": "4.1.6", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "4.1.6" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": false + } + } + }, + "node_modules/@vitest/browser/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz", - "integrity": "sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.6.tgz", + "integrity": "sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.5", + "@vitest/utils": "4.1.6", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -10978,8 +11118,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.5", - "vitest": "4.1.5" + "@vitest/browser": "4.1.6", + "vitest": "4.1.6" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -11230,16 +11370,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", + "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -11248,13 +11388,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz", + "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.5", + "@vitest/spy": "4.1.6", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -11285,9 +11425,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz", + "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==", "dev": true, "license": "MIT", "dependencies": { @@ -11298,13 +11438,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz", + "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.5", + "@vitest/utils": "4.1.6", "pathe": "^2.0.3" }, "funding": { @@ -11312,14 +11452,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz", + "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/pretty-format": "4.1.6", + "@vitest/utils": "4.1.6", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -11338,9 +11478,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz", + "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==", "dev": true, "license": "MIT", "funding": { @@ -11348,13 +11488,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz", + "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", + "@vitest/pretty-format": "4.1.6", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -14053,6 +14193,13 @@ "node": ">=6" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/dom-autoscroller": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/dom-autoscroller/-/dom-autoscroller-2.3.4.tgz", @@ -18481,6 +18628,16 @@ "node": ">=12" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -21019,6 +21176,63 @@ "node": ">=16.0.0" } }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/portfinder": { "version": "1.0.32", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", @@ -21258,6 +21472,34 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -21744,6 +21986,13 @@ "react": "*" } }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/react-number-format": { "version": "5.4.4", "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.4.tgz", @@ -23044,6 +23293,21 @@ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", "license": "MIT" }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/slice-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", @@ -24049,6 +24313,16 @@ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tough-cookie": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", @@ -25235,19 +25509,19 @@ } }, "node_modules/vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz", + "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/expect": "4.1.6", + "@vitest/mocker": "4.1.6", + "@vitest/pretty-format": "4.1.6", + "@vitest/runner": "4.1.6", + "@vitest/snapshot": "4.1.6", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -25275,12 +25549,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.5", - "@vitest/browser-preview": "4.1.5", - "@vitest/browser-webdriverio": "4.1.5", - "@vitest/coverage-istanbul": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "@vitest/ui": "4.1.5", + "@vitest/browser-playwright": "4.1.6", + "@vitest/browser-preview": "4.1.6", + "@vitest/browser-webdriverio": "4.1.6", + "@vitest/coverage-istanbul": "4.1.6", + "@vitest/coverage-v8": "4.1.6", + "@vitest/ui": "4.1.6", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -25702,27 +25976,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/webpack-merge": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", @@ -26030,9 +26283,9 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -28724,6 +28977,12 @@ "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true }, + "@blazediff/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz", + "integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==", + "dev": true + }, "@blocknote/core": { "version": "0.44.2", "resolved": "https://registry.npmjs.org/@blocknote/core/-/core-0.44.2.tgz", @@ -31209,6 +31468,12 @@ "tsyringe": "^4.10.0" } }, + "@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true + }, "@primer/behaviors": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.3.5.tgz", @@ -31828,6 +32093,42 @@ "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.7.7.tgz", "integrity": "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==" }, + "@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "dependencies": { + "aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "requires": { + "dequal": "^2.0.3" + } + } + } + }, + "@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + } + }, "@tiptap/core": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.12.0.tgz", @@ -32014,6 +32315,12 @@ "tslib": "^2.4.0" } }, + "@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true + }, "@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -32984,14 +33291,52 @@ "integrity": "sha512-HXciTXN/sDBYWgeAD4V4s0DN0g72x5mlxQhHxtYu3Tt8BLa6MzcJZUyDVFCdtjNs3bfENVHVzOsmooTVuNgAAw==", "dev": true }, + "@vitest/browser": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.6.tgz", + "integrity": "sha512-ynsspTubXGSpa58JFJ24xIQt4z4A25epSbugEyaTmmrV1//Wec9EgE/LtoaC6yxUrXi5P7erGHRrkdZIHaVQuA==", + "dev": true, + "requires": { + "@blazediff/core": "1.9.1", + "@vitest/mocker": "4.1.6", + "@vitest/utils": "4.1.6", + "magic-string": "^0.30.21", + "pngjs": "^7.0.0", + "sirv": "^3.0.2", + "tinyrainbow": "^3.1.0", + "ws": "^8.19.0" + }, + "dependencies": { + "magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + } + } + }, + "@vitest/browser-playwright": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.6.tgz", + "integrity": "sha512-4csoeyl/qwHyxU2zNL0++WaoDr8YJDXOQPwWPNJoTZ+QzcdO3INYKgF5Zfz730Io7zbkuv914aZmfQ+QE+1Hvw==", + "dev": true, + "requires": { + "@vitest/browser": "4.1.6", + "@vitest/mocker": "4.1.6", + "tinyrainbow": "^3.1.0" + } + }, "@vitest/coverage-v8": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz", - "integrity": "sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.6.tgz", + "integrity": "sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==", "dev": true, "requires": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.5", + "@vitest/utils": "4.1.6", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -33126,26 +33471,26 @@ } }, "@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", + "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==", "dev": true, "requires": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz", + "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==", "dev": true, "requires": { - "@vitest/spy": "4.1.5", + "@vitest/spy": "4.1.6", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -33162,32 +33507,32 @@ } }, "@vitest/pretty-format": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz", + "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==", "dev": true, "requires": { "tinyrainbow": "^3.1.0" } }, "@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz", + "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==", "dev": true, "requires": { - "@vitest/utils": "4.1.5", + "@vitest/utils": "4.1.6", "pathe": "^2.0.3" } }, "@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz", + "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==", "dev": true, "requires": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/pretty-format": "4.1.6", + "@vitest/utils": "4.1.6", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -33204,18 +33549,18 @@ } }, "@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz", + "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==", "dev": true }, "@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz", + "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==", "dev": true, "requires": { - "@vitest/pretty-format": "4.1.5", + "@vitest/pretty-format": "4.1.6", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -35089,6 +35434,12 @@ "@leichtgewicht/ip-codec": "^2.0.1" } }, + "dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, "dom-autoscroller": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/dom-autoscroller/-/dom-autoscroller-2.3.4.tgz", @@ -38214,6 +38565,12 @@ "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==" }, + "lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true + }, "magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -39937,6 +40294,37 @@ "tslib": "^2.8.1" } }, + "playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.60.0" + }, + "dependencies": { + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + } + } + }, + "playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true + }, + "pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true + }, "portfinder": { "version": "1.0.32", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", @@ -40072,6 +40460,25 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, "prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -40414,6 +40821,12 @@ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==" }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, "react-number-format": { "version": "5.4.4", "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.4.tgz", @@ -41305,6 +41718,17 @@ } } }, + "sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "requires": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + } + }, "slice-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", @@ -42018,6 +42442,12 @@ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" }, + "totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true + }, "tough-cookie": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", @@ -42716,18 +43146,18 @@ } }, "vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz", + "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==", "dev": true, "requires": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/expect": "4.1.6", + "@vitest/mocker": "4.1.6", + "@vitest/pretty-format": "4.1.6", + "@vitest/runner": "4.1.6", + "@vitest/snapshot": "4.1.6", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -43046,12 +43476,6 @@ "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } - }, - "ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "dev": true } } }, @@ -43223,9 +43647,9 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==" + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==" }, "wscat": { "version": "6.1.0", diff --git a/frontend/package.json b/frontend/package.json index a6f73b71065..fb87931e3c7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,8 @@ "@html-eslint/eslint-plugin": "^0.59.0", "@html-eslint/parser": "^0.59.0", "@stylistic/eslint-plugin": "^5.7.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.3.2", "@types/codemirror": "5.60.5", "@types/dom-navigation": "^1.0.7", "@types/dragula": "^3.7.5", @@ -37,7 +39,8 @@ "@types/webpack-env": "^1.16.0", "@typescript-eslint/eslint-plugin": "8.53.0", "@typescript-eslint/parser": "8.53.1", - "@vitest/coverage-v8": "^4.1.5", + "@vitest/browser-playwright": "^4.1.6", + "@vitest/coverage-v8": "^4.1.6", "@vitest/eslint-plugin": "^1.6.16", "angular-eslint": "^21.1.0", "browserslist": "^4.28.1", @@ -48,11 +51,12 @@ "eslint-plugin-react-hooks": "^7.0.1", "globals": "^17.5.0", "jsdom": "^29.1.1", + "playwright": "^1.59.1", "source-map-explorer": "^2.5.2", "ts-node": "~10.9.2", "typescript": "^5.9.3", "typescript-eslint": "^8.56.1", - "vitest": "^4.1.5", + "vitest": "^4.1.6", "wscat": "^6.1.0" }, "dependencies": { diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 1bca3a907a7..b3de94b03e8 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -181,7 +181,6 @@ import { OpNonWorkingDaysListComponent, } from 'core-app/shared/components/op-non-working-days-list/op-non-working-days-list.component'; import { PersistentToggleComponent } from 'core-app/shared/components/persistent-toggle/persistent-toggle.component'; -import { TypeFormConfigurationComponent } from 'core-app/features/admin/types/type-form-configuration.component'; import { ToastsContainerComponent } from 'core-app/shared/components/toaster/toasts-container.component'; import { GlobalSearchWorkPackagesComponent } from 'core-app/core/global_search/global-search-work-packages.component'; import { @@ -423,7 +422,6 @@ export class OpenProjectModule implements DoBootstrap { registerCustomElement('opce-non-working-days-list', OpNonWorkingDaysListComponent, { injector }); registerCustomElement('opce-main-menu-resizer', MainMenuResizerComponent, { injector }); registerCustomElement('opce-persistent-toggle', PersistentToggleComponent, { injector }); - registerCustomElement('opce-admin-type-form-configuration', TypeFormConfigurationComponent, { injector }); registerCustomElement('opce-toasts-container', ToastsContainerComponent, { injector }); registerCustomElement('opce-global-search-work-packages', GlobalSearchWorkPackagesComponent, { injector }); registerCustomElement('opce-custom-date-action-admin', CustomDateActionAdminComponent, { injector }); diff --git a/frontend/src/app/core/active-window/active-window.service.ts b/frontend/src/app/core/active-window/active-window.service.ts index b5c17117c13..d16d35c3e66 100644 --- a/frontend/src/app/core/active-window/active-window.service.ts +++ b/frontend/src/app/core/active-window/active-window.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, DOCUMENT } from '@angular/core'; +import { Injectable, DOCUMENT, inject } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; import { debugLog } from 'core-app/shared/helpers/debug_output'; @@ -6,7 +6,9 @@ import { debugLog } from 'core-app/shared/helpers/debug_output'; export class ActiveWindowService { private activeState$ = new BehaviorSubject(true); - constructor(@Inject(DOCUMENT) document:Document) { + constructor() { + const document = inject(DOCUMENT); + document.addEventListener('visibilitychange', () => { if (document.visibilityState) { debugLog(`Browser window has visibility state changed to ${document.visibilityState}`); diff --git a/frontend/src/app/core/apiv3/api-v3.service.ts b/frontend/src/app/core/apiv3/api-v3.service.ts index cf6aa288ead..dc39e5742c3 100644 --- a/frontend/src/app/core/apiv3/api-v3.service.ts +++ b/frontend/src/app/core/apiv3/api-v3.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { ApiV3GettableResource, ApiV3ResourceCollection } from 'core-app/core/apiv3/paths/apiv3-resource'; import { Constructor } from 'core-app/core/util-types'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; @@ -65,6 +65,9 @@ import { @Injectable({ providedIn: 'root' }) export class ApiV3Service { + readonly injector = inject(Injector); + readonly pathHelper = inject(PathHelperService); + // /api/v3/attachments public readonly attachments = this.apiV3CollectionEndpoint('attachments'); @@ -173,11 +176,6 @@ export class ApiV3Service { // VIRTUAL boards are /api/v3/grids + a scope filter public readonly boards = this.apiV3CustomEndpoint(ApiV3BoardsPaths); - constructor( - readonly injector:Injector, - readonly pathHelper:PathHelperService, - ) { } - /** * Returns the part of the API that exists both * - WITHIN a project scope /api/v3/projects/* diff --git a/frontend/src/app/core/apiv3/endpoints/projects/project.cache.ts b/frontend/src/app/core/apiv3/endpoints/projects/project.cache.ts index bf76e19acd4..30f5f97fd2e 100644 --- a/frontend/src/app/core/apiv3/endpoints/projects/project.cache.ts +++ b/frontend/src/app/core/apiv3/endpoints/projects/project.cache.ts @@ -37,8 +37,8 @@ import { ProjectResource } from 'core-app/features/hal/resources/project-resourc export class ProjectCache extends StateCacheService { @InjectField() private schemaCacheService:SchemaCacheService; - constructor(readonly injector:Injector, - state:MultiInputState) { + // eslint-disable-next-line @angular-eslint/prefer-inject -- manually instantiated, not DI-resolved + constructor(readonly injector:Injector, state:MultiInputState) { super(state); } diff --git a/frontend/src/app/core/augmenting/openproject-augmenting.module.ts b/frontend/src/app/core/augmenting/openproject-augmenting.module.ts index 31e4c868487..5f9c0c242a8 100644 --- a/frontend/src/app/core/augmenting/openproject-augmenting.module.ts +++ b/frontend/src/app/core/augmenting/openproject-augmenting.module.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { NgModule } from '@angular/core'; +import { NgModule, inject } from '@angular/core'; import { OpenprojectModalModule } from 'core-app/shared/components/modal/modal.module'; import { OpModalWrapperAugmentService } from 'core-app/shared/components/modal/modal-wrapper-augment.service'; @@ -34,7 +34,9 @@ import { OpModalWrapperAugmentService } from 'core-app/shared/components/modal/m imports: [OpenprojectModalModule], }) export class OpenprojectAugmentingModule { - constructor(modalWrapper:OpModalWrapperAugmentService) { + constructor() { + const modalWrapper = inject(OpModalWrapperAugmentService); + // Setup augmenting services modalWrapper.setupListener(); diff --git a/frontend/src/app/core/backup/op-backup.service.ts b/frontend/src/app/core/backup/op-backup.service.ts index e3183398230..2e5d636cdf7 100644 --- a/frontend/src/app/core/backup/op-backup.service.ts +++ b/frontend/src/app/core/backup/op-backup.service.ts @@ -26,17 +26,15 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { Observable } from 'rxjs'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; @Injectable({ providedIn: 'root' }) export class OpenProjectBackupService { - constructor( - protected apiV3Service:ApiV3Service, - ) { - } + protected apiV3Service = inject(ApiV3Service); + public triggerBackup(backupToken:string, includeAttachments = true):Observable { return this diff --git a/frontend/src/app/core/browser/browser-detector.service.ts b/frontend/src/app/core/browser/browser-detector.service.ts index 711cff51561..9076045dbbc 100644 --- a/frontend/src/app/core/browser/browser-detector.service.ts +++ b/frontend/src/app/core/browser/browser-detector.service.ts @@ -1,9 +1,9 @@ -import { Inject, Injectable, DOCUMENT } from '@angular/core'; +import { Injectable, DOCUMENT, inject } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class BrowserDetector { - constructor(@Inject(DOCUMENT) private documentElement:Document) { - } + private documentElement = inject(DOCUMENT); + /** * Detect mobile browser based on the Rails determined UA diff --git a/frontend/src/app/core/config/configuration.service.ts b/frontend/src/app/core/config/configuration.service.ts index ee186227657..e80fd50b9db 100644 --- a/frontend/src/app/core/config/configuration.service.ts +++ b/frontend/src/app/core/config/configuration.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import moment from 'moment'; import { ConfigurationResource } from 'core-app/features/hal/resources/configuration-resource'; @@ -35,15 +35,13 @@ import { type DurationFormat } from 'core-app/shared/helpers/chronic_duration'; @Injectable({ providedIn: 'root' }) export class ConfigurationService { + private readonly apiV3Service = inject(ApiV3Service); + // fetches configuration from the ApiV3 endpoint // TODO: this currently saves the request between page reloads, // but could easily be stored in localStorage private configuration:ConfigurationResource; - public constructor( - private readonly apiV3Service:ApiV3Service, - ) { } - public initialize():Promise { return this.loadConfiguration(); } diff --git a/frontend/src/app/core/current-project/current-project.service.spec.ts b/frontend/src/app/core/current-project/current-project.service.spec.ts index 59ada2ac281..7b8875827b1 100644 --- a/frontend/src/app/core/current-project/current-project.service.spec.ts +++ b/frontend/src/app/core/current-project/current-project.service.spec.ts @@ -26,7 +26,9 @@ // See COPYRIGHT and LICENSE files for more details. //++ +import { TestBed } from '@angular/core/testing'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; +import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { CurrentProjectService } from './current-project.service'; describe('currentProject service', () => { @@ -40,7 +42,15 @@ describe('currentProject service', () => { }; beforeEach(() => { - currentProject = new CurrentProjectService(new PathHelperService(), apiV3Stub); + TestBed.configureTestingModule({ + providers: [ + CurrentProjectService, + PathHelperService, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + { provide: ApiV3Service, useValue: apiV3Stub }, + ], + }); + currentProject = TestBed.inject(CurrentProjectService); }); describe('with no meta present', () => { diff --git a/frontend/src/app/core/current-project/current-project.service.ts b/frontend/src/app/core/current-project/current-project.service.ts index da1425eda32..ad54323f4e6 100644 --- a/frontend/src/app/core/current-project/current-project.service.ts +++ b/frontend/src/app/core/current-project/current-project.service.ts @@ -26,21 +26,21 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { getMetaElement } from '../setup/globals/global-helpers'; @Injectable({ providedIn: 'root' }) export class CurrentProjectService { + private PathHelper = inject(PathHelperService); + private apiV3Service = inject(ApiV3Service); + private currentId:string|null = null; private currentName:string|null = null; private currentIdentifier:string|null = null; - constructor( - private PathHelper:PathHelperService, - private apiV3Service:ApiV3Service, - ) { + constructor() { this.detect(); } diff --git a/frontend/src/app/core/current-user/current-user.module.ts b/frontend/src/app/core/current-user/current-user.module.ts index 892368c3814..a26c8abb089 100644 --- a/frontend/src/app/core/current-user/current-user.module.ts +++ b/frontend/src/app/core/current-user/current-user.module.ts @@ -1,4 +1,4 @@ -import { Injector, NgModule } from '@angular/core'; +import { Injector, NgModule, inject } from '@angular/core'; import { CurrentUserService } from './current-user.service'; import { CurrentUserStore } from './current-user.store'; @@ -35,7 +35,9 @@ export function bootstrapModule(injector:Injector):void { ], }) export class CurrentUserModule { - constructor(injector:Injector) { + constructor() { + const injector = inject(Injector); + bootstrapModule(injector); } } diff --git a/frontend/src/app/core/current-user/current-user.query.ts b/frontend/src/app/core/current-user/current-user.query.ts index dee37f6f36a..48e4bbddb6f 100644 --- a/frontend/src/app/core/current-user/current-user.query.ts +++ b/frontend/src/app/core/current-user/current-user.query.ts @@ -1,11 +1,17 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { Query } from '@datorama/akita'; import { CurrentUserState, CurrentUserStore } from './current-user.store'; @Injectable() export class CurrentUserQuery extends Query { - constructor(protected store:CurrentUserStore) { + protected store:CurrentUserStore; + + constructor() { + const store = inject(CurrentUserStore); + super(store); + + this.store = store; } isLoggedIn$ = this.select((state) => !!state.loggedIn); diff --git a/frontend/src/app/core/current-user/current-user.service.ts b/frontend/src/app/core/current-user/current-user.service.ts index af5eeb3781a..ccdad8f88dc 100644 --- a/frontend/src/app/core/current-user/current-user.service.ts +++ b/frontend/src/app/core/current-user/current-user.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { ApiV3ListFilter } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface'; import { CapabilitiesResourceService } from 'core-app/core/state/capabilities/capabilities.service'; @@ -38,12 +38,12 @@ import { CurrentUser, CurrentUserStore } from './current-user.store'; @Injectable({ providedIn: 'root' }) export class CurrentUserService { - constructor( - private apiV3Service:ApiV3Service, - private currentUserStore:CurrentUserStore, - private currentUserQuery:CurrentUserQuery, - private capabilitiesService:CapabilitiesResourceService, - ) { + private apiV3Service = inject(ApiV3Service); + private currentUserStore = inject(CurrentUserStore); + private currentUserQuery = inject(CurrentUserQuery); + private capabilitiesService = inject(CapabilitiesResourceService); + + constructor() { this.setupLegacyDataListeners(); } diff --git a/frontend/src/app/core/datetime/timezone.service.ts b/frontend/src/app/core/datetime/timezone.service.ts index a703ce1c616..d8c4b2bb398 100644 --- a/frontend/src/app/core/datetime/timezone.service.ts +++ b/frontend/src/app/core/datetime/timezone.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { ConfigurationService } from 'core-app/core/config/configuration.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import moment, { Moment } from 'moment-timezone'; @@ -34,10 +34,9 @@ import { outputChronicDuration } from '../../shared/helpers/chronic_duration'; @Injectable({ providedIn: 'root' }) export class TimezoneService { - constructor( - readonly configurationService:ConfigurationService, - readonly I18n:I18nService, - ) { } + readonly configurationService = inject(ConfigurationService); + readonly I18n = inject(I18nService); + /** * Returns the user's configured timezone or guesses it through moment diff --git a/frontend/src/app/core/days/weekday.service.ts b/frontend/src/app/core/days/weekday.service.ts index ab91eb3cde5..2f5643b1fdb 100644 --- a/frontend/src/app/core/days/weekday.service.ts +++ b/frontend/src/app/core/days/weekday.service.ts @@ -26,10 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - Injectable, - Injector, -} from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import moment, { Moment } from 'moment'; import { take, @@ -45,14 +42,12 @@ import { @Injectable({ providedIn: 'root' }) export class WeekdayService { + readonly injector = inject(Injector); + @InjectField() weekdaysService:WeekdayResourceService; private weekdays:IWeekday[]; - constructor( - readonly injector:Injector, - ) {} - /** * @param date The iso day number (1-7) or a date instance * @return {boolean} whether the given iso day is working or not diff --git a/frontend/src/app/core/enterprise/banners.service.ts b/frontend/src/app/core/enterprise/banners.service.ts index a8c39e57b33..92c06a214b5 100644 --- a/frontend/src/app/core/enterprise/banners.service.ts +++ b/frontend/src/app/core/enterprise/banners.service.ts @@ -26,18 +26,20 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Inject, Injectable, DOCUMENT } from '@angular/core'; +import { Injectable, DOCUMENT, inject } from '@angular/core'; import { enterpriseEditionUrl } from 'core-app/core/setup/globals/constants.const'; import { ConfigurationService } from 'core-app/core/config/configuration.service'; @Injectable({ providedIn: 'root' }) export class BannersService { + protected documentElement = inject(DOCUMENT); + protected configuration = inject(ConfigurationService); + private readonly _bannersHidden:boolean = true; - constructor( - @Inject(DOCUMENT) protected documentElement:Document, - protected configuration:ConfigurationService, - ) { + constructor() { + const documentElement = this.documentElement; + this._bannersHidden = documentElement.body.classList.contains('ee-banners-hidden'); } diff --git a/frontend/src/app/core/global_search/input/global-search-input.component.ts b/frontend/src/app/core/global_search/input/global-search-input.component.ts index 83c2b43deeb..5f693bd1728 100644 --- a/frontend/src/app/core/global_search/input/global-search-input.component.ts +++ b/frontend/src/app/core/global_search/input/global-search-input.component.ts @@ -1,16 +1,5 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - HostListener, - Input, - OnDestroy, - ViewChild, - ViewEncapsulation, -} from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostListener, Input, OnDestroy, ViewChild, ViewEncapsulation, inject } from '@angular/core'; import { BehaviorSubject, Observable, of } from 'rxjs'; import { first, map, switchMap, tap } from 'rxjs/operators'; import { GlobalSearchService } from 'core-app/core/global_search/services/global-search.service'; @@ -74,6 +63,18 @@ interface SearchResultItems { standalone: false, }) export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy { + readonly elementRef = inject(ElementRef); + readonly I18n = inject(I18nService); + readonly apiV3Service = inject(ApiV3Service); + readonly pathHelperService = inject(PathHelperService); + readonly halResourceService = inject(HalResourceService); + readonly globalSearchService = inject(GlobalSearchService); + readonly currentProjectService = inject(CurrentProjectService); + readonly deviceService = inject(DeviceService); + readonly cdRef = inject(ChangeDetectorRef); + readonly halNotification = inject(HalResourceNotificationService); + readonly recentItemsService = inject(RecentItemsService); + @Input() public placeholder:string; @ViewChild('btn', { static: true }) btn:ElementRef; @@ -131,19 +132,7 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy { search: this.I18n.t('js.autocompleter.search'), }; - constructor( - readonly elementRef:ElementRef, - readonly I18n:I18nService, - readonly apiV3Service:ApiV3Service, - readonly pathHelperService:PathHelperService, - readonly halResourceService:HalResourceService, - readonly globalSearchService:GlobalSearchService, - readonly currentProjectService:CurrentProjectService, - readonly deviceService:DeviceService, - readonly cdRef:ChangeDetectorRef, - readonly halNotification:HalResourceNotificationService, - readonly recentItemsService:RecentItemsService, - ) { + constructor() { populateInputsFromDataset(this); } diff --git a/frontend/src/app/core/global_search/openproject-global-search.module.ts b/frontend/src/app/core/global_search/openproject-global-search.module.ts index f8b8ebbfd56..259e7394642 100644 --- a/frontend/src/app/core/global_search/openproject-global-search.module.ts +++ b/frontend/src/app/core/global_search/openproject-global-search.module.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injector, NgModule } from '@angular/core'; +import { Injector, NgModule, inject } from '@angular/core'; import { OpenprojectWorkPackagesModule } from 'core-app/features/work-packages/openproject-work-packages.module'; import { GlobalSearchInputComponent } from 'core-app/core/global_search/input/global-search-input.component'; import { GlobalSearchWorkPackagesComponent } from 'core-app/core/global_search/global-search-work-packages.component'; @@ -53,6 +53,5 @@ import { RecentItemsService } from 'core-app/core/recent-items.service'; ], }) export class OpenprojectGlobalSearchModule { - constructor(readonly injector:Injector) { - } + readonly injector = inject(Injector); } diff --git a/frontend/src/app/core/global_search/services/global-search.service.ts b/frontend/src/app/core/global_search/services/global-search.service.ts index 38f17e17697..7ded727590b 100644 --- a/frontend/src/app/core/global_search/services/global-search.service.ts +++ b/frontend/src/app/core/global_search/services/global-search.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; @@ -34,13 +34,11 @@ import { PathHelperService } from 'core-app/core/path-helper/path-helper.service @Injectable() export class GlobalSearchService { - constructor( - protected I18n:I18nService, - protected injector:Injector, - protected PathHelper:PathHelperService, - protected currentProjectService:CurrentProjectService, - ) { - } + protected I18n = inject(I18nService); + protected injector = inject(Injector); + protected PathHelper = inject(PathHelperService); + protected currentProjectService = inject(CurrentProjectService); + public submitSearch(query:string, scope:string):void { const path = this.searchPath(scope); diff --git a/frontend/src/app/core/global_search/tabs/global-search-tabs.component.ts b/frontend/src/app/core/global_search/tabs/global-search-tabs.component.ts index 4c1392c5143..ee600bddab1 100644 --- a/frontend/src/app/core/global_search/tabs/global-search-tabs.component.ts +++ b/frontend/src/app/core/global_search/tabs/global-search-tabs.component.ts @@ -26,8 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnDestroy, OnInit } from '@angular/core'; -import { StateService } from '@uirouter/core'; +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject } from '@angular/core'; import { Subscription } from 'rxjs'; import { GlobalSearchService } from 'core-app/core/global_search/services/global-search.service'; import { ScrollableTabsComponent } from 'core-app/shared/components/tabs/scrollable-tabs/scrollable-tabs.component'; @@ -40,20 +39,13 @@ import { TabDefinition } from 'core-app/shared/components/tabs/tab.interface'; standalone: false, }) export class GlobalSearchTabsComponent extends ScrollableTabsComponent implements OnInit, OnDestroy { + readonly globalSearchService = inject(GlobalSearchService); + private currentTabSub:Subscription; private tabsSub:Subscription; - public classes:string[] = ['global-search--tabs', 'scrollable-tabs']; - - constructor( - readonly globalSearchService:GlobalSearchService, - protected readonly $state:StateService, - public injector:Injector, - cdRef:ChangeDetectorRef, - ) { - super($state, cdRef, injector); - } + public override classes:string[] = ['global-search--tabs', 'scrollable-tabs']; ngOnInit():void { this.currentTabSub = this.globalSearchService diff --git a/frontend/src/app/core/html-sanitize/html-sanitize.service.ts b/frontend/src/app/core/html-sanitize/html-sanitize.service.ts index f6007f4e02b..237539d18f8 100644 --- a/frontend/src/app/core/html-sanitize/html-sanitize.service.ts +++ b/frontend/src/app/core/html-sanitize/html-sanitize.service.ts @@ -26,12 +26,13 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, SecurityContext } from '@angular/core'; +import { Injectable, SecurityContext, inject } from '@angular/core'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; @Injectable({ providedIn: 'root' }) export class HTMLSanitizeService { - public constructor(readonly sanitizer:DomSanitizer) { } + readonly sanitizer = inject(DomSanitizer); + public sanitize(string:string):SafeHtml { return this.sanitizer.sanitize(SecurityContext.HTML, string) || ''; diff --git a/frontend/src/app/core/html/op-title.service.ts b/frontend/src/app/core/html/op-title.service.ts index fc6a500ecb2..5c56c9b920d 100644 --- a/frontend/src/app/core/html/op-title.service.ts +++ b/frontend/src/app/core/html/op-title.service.ts @@ -1,13 +1,13 @@ import { Title } from '@angular/platform-browser'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { getMetaContent } from '../setup/globals/global-helpers'; const titlePartsSeparator = ' | '; @Injectable({ providedIn: 'root' }) export class OpTitleService { - constructor(private titleService:Title) { - } + private titleService = inject(Title); + public get current():string { return this.titleService.getTitle(); diff --git a/frontend/src/app/core/i18n/i18n.service.ts b/frontend/src/app/core/i18n/i18n.service.ts index 76cdf244165..5c97407f543 100644 --- a/frontend/src/app/core/i18n/i18n.service.ts +++ b/frontend/src/app/core/i18n/i18n.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { NgSelectConfig } from '@ng-select/ng-select'; import { I18n } from 'i18n-js'; import { FormatNumberOptions, TranslateOptions } from 'i18n-js/src/typing'; @@ -6,12 +6,12 @@ import { getMetaValue } from '../setup/globals/global-helpers'; @Injectable({ providedIn: 'root' }) export class I18nService { + private config = inject(NgSelectConfig); + private i18n:I18n = window.I18n; private instanceLocale:string; - constructor( - private config:NgSelectConfig, - ) { + constructor() { this.instanceLocale = getMetaValue('openproject_initializer', 'instanceLocale', 'en'); this.config.addTagText = this.t('js.autocomplete_ng_select.add_tag'); diff --git a/frontend/src/app/core/main-menu/main-menu-toggle.service.ts b/frontend/src/app/core/main-menu/main-menu-toggle.service.ts index 3e7fbee0bce..4c17bc260eb 100644 --- a/frontend/src/app/core/main-menu/main-menu-toggle.service.ts +++ b/frontend/src/app/core/main-menu/main-menu-toggle.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; import { DeviceService } from 'core-app/core/browser/device.service'; @@ -35,6 +35,9 @@ import { queryVisible } from 'core-app/shared/helpers/dom-helpers'; @Injectable({ providedIn: 'root' }) export class MainMenuToggleService { + injector = inject(Injector); + readonly deviceService = inject(DeviceService); + private elementWidth:number; private elementMinWidth = 11; @@ -61,10 +64,7 @@ export class MainMenuToggleService { private wasCollapsedByUser = false; - constructor( - public injector:Injector, - readonly deviceService:DeviceService, - ) { + constructor() { this.initializeMenu(); // Add resize event listener window.addEventListener('resize', this.onWindowResize.bind(this)); diff --git a/frontend/src/app/core/main-menu/submenu.service.ts b/frontend/src/app/core/main-menu/submenu.service.ts index 5ad6678b260..ce4f97f081b 100644 --- a/frontend/src/app/core/main-menu/submenu.service.ts +++ b/frontend/src/app/core/main-menu/submenu.service.ts @@ -1,10 +1,11 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { type FrameElement } from '@hotwired/turbo'; import { StateService } from '@uirouter/core'; @Injectable({ providedIn: 'root' }) export class SubmenuService { - constructor(protected $state:StateService) {} + protected $state = inject(StateService); + reloadSubmenu(selectedQueryId:string|null, sidemenuId?:string):void { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment diff --git a/frontend/src/app/core/navigation/url-params.service.ts b/frontend/src/app/core/navigation/url-params.service.ts index 7cf07168c82..007a6d0b696 100644 --- a/frontend/src/app/core/navigation/url-params.service.ts +++ b/frontend/src/app/core/navigation/url-params.service.ts @@ -26,15 +26,15 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { Observable } from 'rxjs'; import { map, shareReplay, startWith } from 'rxjs/operators'; import { NavigationService } from 'core-app/core/navigation/navigation.service'; @Injectable({ providedIn: 'root' }) export class UrlParamsService { - constructor(private navigation:NavigationService) { - } + private navigation = inject(NavigationService); + public get(key:string):string|null { return this.searchParams.get(key); diff --git a/frontend/src/app/core/routing/openproject-router.module.ts b/frontend/src/app/core/routing/openproject-router.module.ts index 23e76ba35f7..33c6024ce70 100644 --- a/frontend/src/app/core/routing/openproject-router.module.ts +++ b/frontend/src/app/core/routing/openproject-router.module.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injector, NgModule } from '@angular/core'; +import { Injector, NgModule, inject } from '@angular/core'; import { FirstRouteService } from 'core-app/core/routing/first-route-service'; import { UIRouterModule } from '@uirouter/angular'; import { ApplicationBaseComponent } from 'core-app/core/routing/base/application-base.component'; @@ -52,7 +52,9 @@ import { ], }) export class OpenprojectRouterModule { - constructor(injector:Injector) { + constructor() { + const injector = inject(Injector); + initializeUiRouterListeners(injector); } } diff --git a/frontend/src/app/core/schemas/schema-cache.service.ts b/frontend/src/app/core/schemas/schema-cache.service.ts index c7583d096d7..6bd5a4e6be9 100644 --- a/frontend/src/app/core/schemas/schema-cache.service.ts +++ b/frontend/src/app/core/schemas/schema-cache.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ import { State } from '@openproject/reactivestates'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service'; import { firstValueFrom, Observable } from 'rxjs'; import { take } from 'rxjs/operators'; @@ -41,13 +41,17 @@ export const fallbackSchemaId = '__fallback'; @Injectable() export class SchemaCacheService extends StateCacheService { + readonly states:States; + readonly halResourceService = inject(HalResourceService); + fallbackSchema = this.halResourceService.createHalResourceOfClass(SchemaResource, {}, true); - constructor( - readonly states:States, - readonly halResourceService:HalResourceService, - ) { + constructor() { + const states = inject(States); + super(states.schemas); + this.states = states; + this.putValue(fallbackSchemaId, this.fallbackSchema); } diff --git a/frontend/src/app/core/setup/globals/components/admin/backup.component.ts b/frontend/src/app/core/setup/globals/components/admin/backup.component.ts index 46526cbc238..2120d61fcf2 100644 --- a/frontend/src/app/core/setup/globals/components/admin/backup.component.ts +++ b/frontend/src/app/core/setup/globals/components/admin/backup.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Injector, ViewChild } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Injector, ViewChild, inject } from '@angular/core'; import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { ToastService } from 'core-app/shared/components/toaster/toast.service'; @@ -43,6 +43,13 @@ import { JobStatusModalService } from 'core-app/features/job-status/job-status-m standalone: false, }) export class BackupComponent implements AfterViewInit { + readonly elementRef = inject>(ElementRef); + injector = inject(Injector); + protected i18n = inject(I18nService); + protected toastService = inject(ToastService); + protected pathHelper = inject(PathHelperService); + protected jobStatusModalService = inject(JobStatusModalService); + public text = { info: this.i18n.t('js.backup.info'), note: this.i18n.t('js.backup.note'), @@ -72,14 +79,7 @@ export class BackupComponent implements AfterViewInit { @ViewChild('backupTokenInput') backupTokenInput:ElementRef; - constructor( - readonly elementRef:ElementRef, - public injector:Injector, - protected i18n:I18nService, - protected toastService:ToastService, - protected pathHelper:PathHelperService, - protected jobStatusModalService:JobStatusModalService, - ) { + constructor() { this.includeAttachments = this.mayIncludeAttachments; } diff --git a/frontend/src/app/core/state/resource-store.service.ts b/frontend/src/app/core/state/resource-store.service.ts index 144beb68281..629a745cd21 100644 --- a/frontend/src/app/core/state/resource-store.service.ts +++ b/frontend/src/app/core/state/resource-store.service.ts @@ -58,10 +58,7 @@ import { HttpClient, HttpErrorResponse, } from '@angular/common/http'; -import { - Injectable, - Injector, -} from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { ToastService } from 'core-app/shared/components/toaster/toast.service'; import idFromLink from 'core-app/features/hal/helpers/id-from-link'; @@ -76,18 +73,15 @@ export type ResourceKeyInput = ApiV3ListParameters|string; @Injectable() export abstract class ResourceStoreService { + readonly injector = inject(Injector); + readonly http = inject(HttpClient); + readonly apiV3Service = inject(ApiV3Service); + readonly toastService = inject(ToastService); + protected store:ResourceStore = this.createStore(); protected query = new QueryEntity(this.store); - constructor( - readonly injector:Injector, - readonly http:HttpClient, - readonly apiV3Service:ApiV3Service, - readonly toastService:ToastService, - ) { - } - /** * Require the results for the given filter params * Returns a cached set if it was loaded already. diff --git a/frontend/src/app/core/state/storage-files/storage-files.service.ts b/frontend/src/app/core/state/storage-files/storage-files.service.ts index dd8718b7bde..15c65224362 100644 --- a/frontend/src/app/core/state/storage-files/storage-files.service.ts +++ b/frontend/src/app/core/state/storage-files/storage-files.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { combineLatest, Observable } from 'rxjs'; import { filter, map, take, tap, @@ -44,15 +44,13 @@ import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; @Injectable() export class StorageFilesResourceService { + private readonly httpClient = inject(HttpClient); + private readonly apiV3Service = inject(ApiV3Service); + private readonly store:StorageFilesStore = new StorageFilesStore(); private readonly query = new QueryEntity(this.store); - constructor( - private readonly httpClient:HttpClient, - private readonly apiV3Service:ApiV3Service, - ) {} - files(link:IHalResourceLink):Observable { const value = this.store.getValue().files[link.href]; if (value !== undefined) { diff --git a/frontend/src/app/core/top-menu/top-menu.service.ts b/frontend/src/app/core/top-menu/top-menu.service.ts index d05a13645aa..283fcc34f26 100644 --- a/frontend/src/app/core/top-menu/top-menu.service.ts +++ b/frontend/src/app/core/top-menu/top-menu.service.ts @@ -26,10 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - Inject, - Injectable, -} from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { DOCUMENT } from '@angular/common'; import { isVisible } from 'core-app/shared/helpers/dom-helpers'; @@ -37,8 +34,8 @@ export const ANIMATION_RATE_MS = 100; @Injectable({ providedIn: 'root' }) export class TopMenuService { - constructor(@Inject(DOCUMENT) private document:Document) { - } + private document = inject(DOCUMENT); + register():void { this.skipContentClickListener(); diff --git a/frontend/src/app/core/turbo/turbo-requests.service.ts b/frontend/src/app/core/turbo/turbo-requests.service.ts index a1a40087ddc..5afe6bbd1f0 100644 --- a/frontend/src/app/core/turbo/turbo-requests.service.ts +++ b/frontend/src/app/core/turbo/turbo-requests.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { renderStreamMessage } from '@hotwired/turbo'; import { ToastService } from 'core-app/shared/components/toaster/toast.service'; import { debugLog } from 'core-app/shared/helpers/debug_output'; @@ -7,14 +7,10 @@ import { getMetaContent } from '../setup/globals/global-helpers'; @Injectable({ providedIn: 'root' }) export class TurboRequestsService { + private toast = inject(ToastService); + #controllers = new Map(); - constructor( - private toast:ToastService, - ) { - - } - public request( url:string, init:RequestInit = {}, diff --git a/frontend/src/app/features/admin/editable-query-props/editable-query-props.component.ts b/frontend/src/app/features/admin/editable-query-props/editable-query-props.component.ts index 07f0895d492..18bc34b5649 100644 --- a/frontend/src/app/features/admin/editable-query-props/editable-query-props.component.ts +++ b/frontend/src/app/features/admin/editable-query-props/editable-query-props.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { ExternalQueryConfigurationService, @@ -11,6 +11,11 @@ import { standalone: false, }) export class EditableQueryPropsComponent implements OnInit { + private elementRef = inject>(ElementRef); + private I18n = inject(I18nService); + private cdRef = inject(ChangeDetectorRef); + private externalQuery = inject(ExternalQueryConfigurationService); + id:string|null; name:string|null; @@ -23,14 +28,6 @@ export class EditableQueryPropsComponent implements OnInit { edit_query: this.I18n.t('js.admin.type_form.edit_query'), }; - constructor( - private elementRef:ElementRef, - private I18n:I18nService, - private cdRef:ChangeDetectorRef, - private externalQuery:ExternalQueryConfigurationService, - ) { - } - ngOnInit() { const element = this.elementRef.nativeElement; this.id = element.dataset.id!; diff --git a/frontend/src/app/features/admin/openproject-admin.module.ts b/frontend/src/app/features/admin/openproject-admin.module.ts index db7c1865fb0..1406b31c49f 100644 --- a/frontend/src/app/features/admin/openproject-admin.module.ts +++ b/frontend/src/app/features/admin/openproject-admin.module.ts @@ -28,25 +28,15 @@ import { NgModule } from '@angular/core'; import { OpSharedModule } from 'core-app/shared/shared.module'; -import { DragulaModule } from 'ng2-dragula'; -import { TypeFormAttributeGroupComponent } from 'core-app/features/admin/types/attribute-group.component'; -import { TypeFormConfigurationComponent } from 'core-app/features/admin/types/type-form-configuration.component'; -import { TypeFormQueryGroupComponent } from 'core-app/features/admin/types/query-group.component'; -import { GroupEditInPlaceComponent } from 'core-app/features/admin/types/group-edit-in-place.component'; import { EditableQueryPropsComponent } from 'core-app/features/admin/editable-query-props/editable-query-props.component'; @NgModule({ imports: [ - DragulaModule.forRoot(), OpSharedModule, ], providers: [ ], declarations: [ - TypeFormAttributeGroupComponent, - TypeFormQueryGroupComponent, - TypeFormConfigurationComponent, - GroupEditInPlaceComponent, EditableQueryPropsComponent, ], }) diff --git a/frontend/src/app/features/admin/types/attribute-group.component.html b/frontend/src/app/features/admin/types/attribute-group.component.html deleted file mode 100644 index db088a299e8..00000000000 --- a/frontend/src/app/features/admin/types/attribute-group.component.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
- - - -
-
- @for (attribute of group.attributes; track attribute) { -
- - - {{ attribute.translation }} - @if (attribute.is_cf) { - - - } - - -
- } -
-
diff --git a/frontend/src/app/features/admin/types/attribute-group.component.ts b/frontend/src/app/features/admin/types/attribute-group.component.ts deleted file mode 100644 index 69d2aa29828..00000000000 --- a/frontend/src/app/features/admin/types/attribute-group.component.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, -} from '@angular/core'; -import { I18nService } from 'core-app/core/i18n/i18n.service'; -import { TypeFormAttribute, TypeGroup } from 'core-app/features/admin/types/type-form-configuration.component'; - -@Component({ - selector: 'op-type-form-attribute-group', - changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './attribute-group.component.html', - standalone: false, -}) -export class TypeFormAttributeGroupComponent { - @Input() public group:TypeGroup; - - @Output() public deleteGroup = new EventEmitter(); - - @Output() public removeAttribute = new EventEmitter(); - - text = { - custom_field: this.I18n.t('js.admin.type_form.custom_field'), - delete_group: this.I18n.t('js.admin.type_form.delete_group'), - remove_attribute: this.I18n.t('js.admin.type_form.remove_attribute') - }; - - constructor(private I18n:I18nService, - private cdRef:ChangeDetectorRef) { - } - - rename(newValue:string) { - this.group.name = newValue; - delete this.group.key; - this.cdRef.detectChanges(); - } - - removeFromGroup(attribute:TypeFormAttribute) { - this.group.attributes = this.group.attributes.filter((a) => a !== attribute); - this.removeAttribute.emit(attribute); - } -} diff --git a/frontend/src/app/features/admin/types/group-edit-in-place.component.ts b/frontend/src/app/features/admin/types/group-edit-in-place.component.ts deleted file mode 100644 index fd0bcdad58d..00000000000 --- a/frontend/src/app/features/admin/types/group-edit-in-place.component.ts +++ /dev/null @@ -1,108 +0,0 @@ -//-- copyright -// OpenProject is an open source project management software. -// Copyright (C) the OpenProject GmbH -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License version 3. -// -// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -// Copyright (C) 2006-2013 Jean-Philippe Lang -// Copyright (C) 2010-2013 the ChiliProject Team -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// See COPYRIGHT and LICENSE files for more details. -//++ - -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnInit, - Output, -} from '@angular/core'; -import { TypeBannerService } from 'core-app/features/admin/types/type-banner.service'; - -@Component({ - selector: 'op-group-edit-in-place', - changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './group-edit-in-place.html', - standalone: false, -}) -export class GroupEditInPlaceComponent implements OnInit { - @Input() public placeholder = ''; - - @Input() public name:string; - - @Output() public onValueChange = new EventEmitter(); - - public editing = false; - - public editedName:string; - - constructor(private bannerService:TypeBannerService, - protected readonly cdRef:ChangeDetectorRef) { - } - - ngOnInit():void { - this.editedName = this.name; - - if (!this.name || this.name.length === 0) { - // Group name is empty so open in editing mode straight away. - this.startEditing(); - } - } - - startEditing():void { - void this.bannerService.conditional( - 'edit_attribute_groups', - () => this.bannerService.showEEOnlyHint(), - () => { - this.editing = true; - this.cdRef.detectChanges(); - }, - ); - } - - saveEdition(event:FocusEvent):boolean { - this.leaveEditingMode(); - this.name = this.editedName.trim(); - - this.cdRef.detectChanges(); - - if (this.name !== '') { - this.onValueChange.emit(this.name); - } - - // Ensure form is not submitted. - event.preventDefault(); - event.stopPropagation(); - return false; - } - - reset():void { - this.editing = false; - this.editedName = this.name; - } - - leaveEditingMode():void { - // Only leave Editing mode if name not empty. - if (this.editedName != null && this.editedName.trim().length > 0) { - this.editing = false; - } - } -} diff --git a/frontend/src/app/features/admin/types/group-edit-in-place.html b/frontend/src/app/features/admin/types/group-edit-in-place.html deleted file mode 100644 index 6049b310fd3..00000000000 --- a/frontend/src/app/features/admin/types/group-edit-in-place.html +++ /dev/null @@ -1,23 +0,0 @@ -@if (!editing) { -
-
-} -@if (editing) { - -} diff --git a/frontend/src/app/features/admin/types/query-group.component.html b/frontend/src/app/features/admin/types/query-group.component.html deleted file mode 100644 index 47e6afddaa3..00000000000 --- a/frontend/src/app/features/admin/types/query-group.component.html +++ /dev/null @@ -1,28 +0,0 @@ -
-
- - - -
-
- - - {{ text.edit_query }} - -
-
diff --git a/frontend/src/app/features/admin/types/query-group.component.ts b/frontend/src/app/features/admin/types/query-group.component.ts deleted file mode 100644 index b88cc3f8327..00000000000 --- a/frontend/src/app/features/admin/types/query-group.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, -} from '@angular/core'; -import { I18nService } from 'core-app/core/i18n/i18n.service'; -import { TypeGroup } from 'core-app/features/admin/types/type-form-configuration.component'; - -@Component({ - selector: 'op-type-form-query-group', - changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './query-group.component.html', - standalone: false, -}) -export class TypeFormQueryGroupComponent { - text = { - edit_query: this.I18n.t('js.admin.type_form.edit_query'), - delete_group: this.I18n.t('js.admin.type_form.delete_group') - }; - - @Input() public group:TypeGroup; - - @Output() public editQuery = new EventEmitter(); - - @Output() public deleteGroup = new EventEmitter(); - - constructor(private I18n:I18nService, - private cdRef:ChangeDetectorRef) { - } - - rename(newValue:string):void { - this.group.name = newValue; - this.cdRef.detectChanges(); - } -} diff --git a/frontend/src/app/features/admin/types/type-banner.service.ts b/frontend/src/app/features/admin/types/type-banner.service.ts deleted file mode 100644 index 9878c8a155d..00000000000 --- a/frontend/src/app/features/admin/types/type-banner.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { I18nService } from 'core-app/core/i18n/i18n.service'; -import { BannersService } from 'core-app/core/enterprise/banners.service'; -import { Inject, Injectable, DOCUMENT } from '@angular/core'; -import { ConfirmDialogService } from 'core-app/shared/components/modals/confirm-dialog/confirm-dialog.service'; -import { ConfigurationService } from 'core-app/core/config/configuration.service'; - -@Injectable() -export class TypeBannerService extends BannersService { - eeAvailable = this.allowsTo('edit_attribute_groups'); - - constructor( - @Inject(DOCUMENT) protected documentElement:Document, - protected confirmDialog:ConfirmDialogService, - protected I18n:I18nService, - protected configuration:ConfigurationService, - ) { - super(documentElement, configuration); - } - - showEEOnlyHint():void { - this.confirmDialog.confirm({ - text: { - title: this.I18n.t('js.types.attribute_groups.upgrade_to_ee'), - text: this.I18n.t('js.types.attribute_groups.upgrade_to_ee_text'), - button_continue: this.I18n.t('js.types.attribute_groups.more_information'), - button_cancel: this.I18n.t('js.types.attribute_groups.nevermind'), - }, - }).then(() => { - window.location.href = 'https://www.openproject.org/enterprise-edition/?utm_source=unknown&utm_medium=community-edition&utm_campaign=form-configuration'; - }) - .catch(() => { - }); - } -} diff --git a/frontend/src/app/features/admin/types/type-form-configuration.component.ts b/frontend/src/app/features/admin/types/type-form-configuration.component.ts deleted file mode 100644 index f361ec1c45c..00000000000 --- a/frontend/src/app/features/admin/types/type-form-configuration.component.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - Component, - ElementRef, - OnDestroy, - OnInit -} from '@angular/core'; -import { I18nService } from 'core-app/core/i18n/i18n.service'; -import { - ExternalRelationQueryConfigurationService, -} from 'core-app/features/work-packages/components/wp-table/external-configuration/external-relation-query-configuration.service'; -import { DomAutoscrollService } from 'core-app/shared/helpers/drag-and-drop/dom-autoscroll.service'; -import { DragulaService, DrakeWithModels } from 'ng2-dragula'; -import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; -import { installMenuLogic } from 'core-app/core/setup/globals/global-listeners/action-menu'; -import { ConfirmDialogService } from 'core-app/shared/components/modals/confirm-dialog/confirm-dialog.service'; -import { TypeBannerService } from 'core-app/features/admin/types/type-banner.service'; - -export type TypeGroupType = 'attribute'|'query'; - -export interface TypeFormAttribute { - key:string; - translation:string; - is_cf:boolean; -} - -export interface TypeGroup { - /** original internal key, if any */ - key:string|null|undefined; - /** Localized / given name */ - name:string; - attributes:TypeFormAttribute[]; - query?:any; - type:TypeGroupType; -} - -export const emptyTypeGroup = '__empty'; - -@Component({ - selector: 'opce-admin-type-form-configuration', - changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './type-form-configuration.html', - providers: [ - TypeBannerService, - DragulaService - ], - standalone: false, -}) -export class TypeFormConfigurationComponent extends UntilDestroyedMixin implements OnInit, AfterViewInit, OnDestroy { - public text = { - drag_to_activate: this.I18n.t('js.admin.type_form.drag_to_activate'), - reset: this.I18n.t('js.admin.type_form.reset_to_defaults'), - label_group: this.I18n.t('js.label_group'), - new_group: this.I18n.t('js.admin.type_form.new_group'), - label_inactive: this.I18n.t('js.admin.type_form.inactive'), - custom_field: this.I18n.t('js.admin.type_form.custom_field'), - add_group: this.I18n.t('js.admin.type_form.add_group'), - add_table: this.I18n.t('js.admin.type_form.add_table'), - }; - - private autoscroll:any; - - private element:HTMLElement; - - private form:HTMLFormElement; - - private submit:HTMLButtonElement; - - public groups:TypeGroup[] = []; - - public inactives:TypeFormAttribute[] = []; - - private attributeDrake:DrakeWithModels; - - private groupsDrake:DrakeWithModels; - - private no_filter_query:string; - - private eventListeners = { - typeFormUpdater: () => { - this.updateHiddenFields(); - } - }; - - constructor( - private elementRef:ElementRef, - private I18n:I18nService, - private dragula:DragulaService, - private confirmDialog:ConfirmDialogService, - private externalRelationQuery:ExternalRelationQueryConfigurationService, - readonly typeBanner:TypeBannerService, - ) { - super(); - } - - ngOnInit():void { - // For unclear reasons, this component is initialized twice if used in conjunction with - // turbo drive. This then leads to the groups defined in this component being duplicated. - // It does not harm to remove them if they exist but it would of course be better if that hack - // would not be necessary. The functionality should all be handled in ngOnDestroy. - this.dragula.destroy('groups'); - this.dragula.destroy('attributes'); - - // Hook on form submit - this.element = this.elementRef.nativeElement; - this.no_filter_query = this.element.dataset.noFilterQuery!; - this.form = this.element.closest('form')!; - this.submit = this.form.querySelector('.form-configuration--save')!; - - // Capture regular form submit - this.form.addEventListener('submit', this.eventListeners.typeFormUpdater); - - // Setup groups - this.groupsDrake = this - .dragula - .createGroup('groups', { - moves: (el, source, handle:HTMLElement) => handle.classList.contains('group-handle'), - }) - .drake; - - // Setup attributes - this.attributeDrake = this - .dragula - .createGroup('attributes', { - moves: (el, source, handle:HTMLElement) => handle.classList.contains('attribute-handle'), - }) - .drake; - - // Get attribute id - this.groups = JSON - .parse(this.element.dataset.activeGroups!) - .filter((group:TypeGroup) => group?.key !== emptyTypeGroup); - this.inactives = JSON.parse(this.element.dataset.inactiveAttributes!); - - // Setup autoscroll - const that = this; - this.autoscroll = new DomAutoscrollService( - [ - document.getElementById('content-body')!, - ], - { - margin: 25, - maxSpeed: 10, - scrollWhenOutside: true, - autoScroll(this:any) { - const groups = that.groupsDrake && that.groupsDrake.dragging; - const attributes = that.attributeDrake && that.attributeDrake.dragging; - - return groups || attributes; - }, - }, - ); - } - - ngAfterViewInit():void { - const menu = this.elementRef.nativeElement.querySelector('.toolbar-items')!; - installMenuLogic(menu); - } - - ngOnDestroy():void { - this.dragula.destroy('groups'); - this.dragula.destroy('attributes'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call - this.autoscroll.destroy(); - } - - deactivateAttribute(attribute:TypeFormAttribute):void { - this.updateInactives(this.inactives.concat(attribute)); - } - - addGroupAndOpenQuery():void { - const newGroup = this.createGroup('query'); - this.editQuery(newGroup); - } - - editQuery(group:TypeGroup):void { - void this.typeBanner.conditional( - 'edit_attribute_groups', - () => this.typeBanner.showEEOnlyHint(), - () => { - // Disable display mode and timeline for now since we don't want users to enable it - const disabledTabs = { - 'display-settings': I18n.t('js.work_packages.table_configuration.embedded_tab_disabled'), - timelines: I18n.t('js.work_packages.table_configuration.embedded_tab_disabled'), - }; - - this.externalRelationQuery.show({ - currentQuery: JSON.parse(group.query), - callback: (queryProps:any) => (group.query = JSON.stringify(queryProps)), - disabledTabs, - }); - }, - ); - } - - deleteGroup(group:TypeGroup):void { - void this.typeBanner.conditional( - 'edit_attribute_groups', - () => this.typeBanner.showEEOnlyHint(), - () => { - if (group.type === 'attribute') { - this.updateInactives(this.inactives.concat(group.attributes)); - } - - this.groups = this.groups.filter((el) => el !== group); - - return group; - }, - ); - } - - createGroup(type:TypeGroupType, groupName = ''):TypeGroup { - const group:TypeGroup = { - type, - name: groupName, - key: null, - query: this.no_filter_query, - attributes: [], - }; - - this.groups.unshift(group); - return group; - } - - resetToDefault($event:Event):boolean { - this.confirmDialog - .confirm({ - text: { - title: this.I18n.t('js.types.attribute_groups.reset_title'), - text: this.I18n.t('js.types.attribute_groups.confirm_reset'), - button_continue: this.I18n.t('js.label_reset'), - }, - }) - .then(() => { - const input = this.form.querySelector('input#type_attribute_groups')!; - input.value = JSON.stringify([]); - - // Disable our form handler that updates the attribute groups - this.form.removeEventListener('submit', this.eventListeners.typeFormUpdater); - this.form.requestSubmit(); - }) - .catch(() => { - }); - - $event.preventDefault(); - return false; - } - - private updateInactives(newValue:TypeFormAttribute[]):void { - this.inactives = [...newValue].sort((a, b) => a.translation.localeCompare(b.translation)); - } - - // We maintain an empty group - // that gets hidden in the frontend in case the user - // decides to remove all groups - // This was necessary since the "default" is actually an empty array of groups - private get emptyGroup():TypeGroup { - return { - type: 'attribute', key: emptyTypeGroup, name: 'empty', attributes: [], - }; - } - - private updateHiddenFields():void { - const hiddenField = this.form.querySelector('.admin-type-form--hidden-field')!; - if (this.groups.length === 0) { - // Ensure we're adding an empty group if deliberately removing - // all values. - hiddenField.value = JSON.stringify([this.emptyGroup]); - } else { - hiddenField.value = JSON.stringify(this.groups); - } - } -} diff --git a/frontend/src/app/features/admin/types/type-form-configuration.html b/frontend/src/app/features/admin/types/type-form-configuration.html deleted file mode 100644 index 824d39d8eb6..00000000000 --- a/frontend/src/app/features/admin/types/type-form-configuration.html +++ /dev/null @@ -1,90 +0,0 @@ -
-
-
    -
  • - -
  • - @if (typeBanner.eeAvailable) { - - } -
-
-
- -
-
-
- @for (group of groups; track group) { - @if (group.type === 'attribute') { - - } - @if (group.type === 'query') { - - } - } -
-
-
-
-
- - &ngsp; - -
-
- @for (inactive_attribute of inactives; track inactive_attribute) { -
- - - {{ inactive_attribute.translation }} - @if (inactive_attribute.is_cf) { - - - } - -
- } -
-
-
-
diff --git a/frontend/src/app/features/bim/bcf/api/bcf-api.service.ts b/frontend/src/app/features/bim/bcf/api/bcf-api.service.ts index e2a4240ec59..0d07696892a 100644 --- a/frontend/src/app/features/bim/bcf/api/bcf-api.service.ts +++ b/frontend/src/app/features/bim/bcf/api/bcf-api.service.ts @@ -26,12 +26,14 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { BcfResourceCollectionPath } from 'core-app/features/bim/bcf/api/bcf-path-resources'; import { BcfProjectPaths } from 'core-app/features/bim/bcf/api/projects/bcf-project.paths'; @Injectable({ providedIn: 'root' }) export class BcfApiService { + readonly injector = inject(Injector); + public readonly bcfApiVersion = '2.1'; public readonly appBasePath = window.appBasePath || ''; @@ -41,9 +43,6 @@ export class BcfApiService { // /api/bcf/:version/projects public readonly projects = new BcfResourceCollectionPath(this.injector, this.bcfApiBase, 'projects', BcfProjectPaths); - constructor(readonly injector:Injector) { - } - /** * Parse the given string into a BCF resource path * diff --git a/frontend/src/app/features/bim/bcf/api/bcf-authorization.service.ts b/frontend/src/app/features/bim/bcf/api/bcf-authorization.service.ts index 9c61b30fbfa..aa35331f88b 100644 --- a/frontend/src/app/features/bim/bcf/api/bcf-authorization.service.ts +++ b/frontend/src/app/features/bim/bcf/api/bcf-authorization.service.ts @@ -6,18 +6,17 @@ import { Observable, } from 'rxjs'; import { map } from 'rxjs/operators'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; export type AllowedExtensionKey = keyof BcfExtensionResource; @Injectable({ providedIn: 'root' }) export class BcfAuthorizationService { + readonly bcfApi = inject(BcfApiService); + // Poor mans caching to avoid repeatedly fetching from the backend. protected authorizationMap = multiInput(); - constructor(readonly bcfApi:BcfApiService) { - } - /** * Returns an observable boolean whether the given action * is authorized in the project by using the project extensions. diff --git a/frontend/src/app/features/bim/bcf/bcf-viewer-bridge/viewer-bridge.service.ts b/frontend/src/app/features/bim/bcf/bcf-viewer-bridge/viewer-bridge.service.ts index 8bf75d3e58b..959341f3653 100644 --- a/frontend/src/app/features/bim/bcf/bcf-viewer-bridge/viewer-bridge.service.ts +++ b/frontend/src/app/features/bim/bcf/bcf-viewer-bridge/viewer-bridge.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { Observable } from 'rxjs'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator'; @@ -7,6 +7,8 @@ import { CreateBcfViewpointData } from 'core-app/features/bim/bcf/api/bcf-api.mo @Injectable() export abstract class ViewerBridgeService { + readonly injector = inject(Injector); + @InjectField() state:StateService; /** @@ -14,8 +16,6 @@ export abstract class ViewerBridgeService { */ abstract shouldShowViewer:boolean; - protected constructor(readonly injector:Injector) {} - /** * Get a viewpoint from the viewer */ diff --git a/frontend/src/app/features/bim/bcf/bcf-wp-attribute-group/bcf-wp-attribute-group.component.ts b/frontend/src/app/features/bim/bcf/bcf-wp-attribute-group/bcf-wp-attribute-group.component.ts index 063c98c7dbd..96f0a1bec56 100644 --- a/frontend/src/app/features/bim/bcf/bcf-wp-attribute-group/bcf-wp-attribute-group.component.ts +++ b/frontend/src/app/features/bim/bcf/bcf-wp-attribute-group/bcf-wp-attribute-group.component.ts @@ -26,17 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnDestroy, - OnInit, - Optional, - ViewChild, -} from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild, inject } from '@angular/core'; import { StateService } from '@uirouter/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { NgxGalleryComponent, NgxGalleryOptions } from '@kolkov/ngx-gallery'; @@ -62,6 +52,17 @@ import { filter, take } from 'rxjs/operators'; standalone: false, }) export class BcfWpAttributeGroupComponent extends UntilDestroyedMixin implements AfterViewInit, OnDestroy, OnInit { + readonly state = inject(StateService); + readonly bcfAuthorization = inject(BcfAuthorizationService); + readonly viewerBridge = inject(ViewerBridgeService); + readonly apiV3Service = inject(ApiV3Service); + readonly wpCreate = inject(WorkPackageCreateService); + readonly toastService = inject(ToastService); + readonly bcfViewer = inject(BcfViewService, { optional: true }); + readonly cdRef = inject(ChangeDetectorRef); + readonly I18n = inject(I18nService); + readonly viewpointsService = inject(ViewpointsService); + @Input() workPackage:WorkPackageResource; @ViewChild(NgxGalleryComponent) gallery:NgxGalleryComponent; @@ -146,19 +147,6 @@ export class BcfWpAttributeGroupComponent extends UntilDestroyedMixin implements projectId:string; - constructor(readonly state:StateService, - readonly bcfAuthorization:BcfAuthorizationService, - readonly viewerBridge:ViewerBridgeService, - readonly apiV3Service:ApiV3Service, - readonly wpCreate:WorkPackageCreateService, - readonly toastService:ToastService, - @Optional() readonly bcfViewer:BcfViewService, - readonly cdRef:ChangeDetectorRef, - readonly I18n:I18nService, - readonly viewpointsService:ViewpointsService) { - super(); - } - ngAfterViewInit():void { // Observe changes on the work package to update the viewpoints this.observeChanges(); diff --git a/frontend/src/app/features/bim/bcf/helper/bcf-detector.service.ts b/frontend/src/app/features/bim/bcf/helper/bcf-detector.service.ts index 145649f21db..5132a8a122d 100644 --- a/frontend/src/app/features/bim/bcf/helper/bcf-detector.service.ts +++ b/frontend/src/app/features/bim/bcf/helper/bcf-detector.service.ts @@ -1,9 +1,9 @@ -import { Inject, Injectable, DOCUMENT } from '@angular/core'; +import { Injectable, DOCUMENT, inject } from '@angular/core'; @Injectable() export class BcfDetectorService { - constructor(@Inject(DOCUMENT) private documentElement:Document) { - } + private documentElement = inject(DOCUMENT); + /** * Detect whether the BCF module was activated, diff --git a/frontend/src/app/features/bim/bcf/helper/bcf-path-helper.service.ts b/frontend/src/app/features/bim/bcf/helper/bcf-path-helper.service.ts index dbfb644e2cc..48df8499008 100644 --- a/frontend/src/app/features/bim/bcf/helper/bcf-path-helper.service.ts +++ b/frontend/src/app/features/bim/bcf/helper/bcf-path-helper.service.ts @@ -26,14 +26,14 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { HalLink } from 'core-app/features/hal/hal-link/hal-link'; @Injectable() export class BcfPathHelperService { - constructor(readonly pathHelper:PathHelperService) { - } + readonly pathHelper = inject(PathHelperService); + public projectImportIssuePath(projectIdentifier:string) { return `${this.pathHelper.projectPath(projectIdentifier)}/issues/upload`; diff --git a/frontend/src/app/features/bim/bcf/helper/viewpoints.service.ts b/frontend/src/app/features/bim/bcf/helper/viewpoints.service.ts index 1abc9b947ff..0710f819261 100644 --- a/frontend/src/app/features/bim/bcf/helper/viewpoints.service.ts +++ b/frontend/src/app/features/bim/bcf/helper/viewpoints.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator'; import { BcfApiService } from 'core-app/features/bim/bcf/api/bcf-api.service'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; @@ -42,6 +42,8 @@ import { HalResource } from 'core-app/features/hal/resources/hal-resource'; @Injectable() export class ViewpointsService { + readonly injector = inject(Injector); + topicUUID:string|number|null = null; @InjectField() bcfApi:BcfApiService; @@ -50,8 +52,6 @@ export class ViewpointsService { @InjectField() apiV3Service:ApiV3Service; - constructor(readonly injector:Injector) { } - public getViewPointResource(workPackage:WorkPackageResource, index:number):BcfViewpointPaths { const viewpointHref = (workPackage.bcfViewpoints as HalResource[])[index].href!; diff --git a/app/components/open_project/common/work_package_card_list_component/header.sass b/frontend/src/app/features/bim/bcf/openproject-bcf.module.spec.ts similarity index 69% rename from app/components/open_project/common/work_package_card_list_component/header.sass rename to frontend/src/app/features/bim/bcf/openproject-bcf.module.spec.ts index 0e618e18a1a..03da3a55714 100644 --- a/app/components/open_project/common/work_package_card_list_component/header.sass +++ b/frontend/src/app/features/bim/bcf/openproject-bcf.module.spec.ts @@ -26,15 +26,15 @@ // See COPYRIGHT and LICENSE files for more details. //++ -.op-work-package-card-list-header - display: grid - grid-template-columns: 1fr minmax(5rem, max-content) auto - grid-template-areas: "collapsible actions menu" - align-items: center +import { Injector } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { IFCViewerService } from 'core-app/features/bim/ifc_models/ifc-viewer/ifc-viewer.service'; +import { viewerBridgeServiceFactory } from 'core-app/features/bim/bcf/openproject-bcf.module'; - &--actions, - &--menu - margin-left: var(--stack-gap-normal) - align-self: flex-start - // Unfortunately, the invisible button style bites us here again. - margin-top: -6px +describe('viewerBridgeServiceFactory', () => { + it('falls back to an IFC viewer service when none is registered', () => { + const injector = TestBed.inject(Injector); + + expect(viewerBridgeServiceFactory(injector)).toBeInstanceOf(IFCViewerService); + }); +}); diff --git a/frontend/src/app/features/bim/bcf/openproject-bcf.module.ts b/frontend/src/app/features/bim/bcf/openproject-bcf.module.ts index 970eff0859e..a9b06546912 100644 --- a/frontend/src/app/features/bim/bcf/openproject-bcf.module.ts +++ b/frontend/src/app/features/bim/bcf/openproject-bcf.module.ts @@ -29,6 +29,8 @@ import { Injector, NgModule, + inject, + runInInjectionContext, } from '@angular/core'; import { OpSharedModule } from 'core-app/shared/shared.module'; import { NgxGalleryModule } from '@kolkov/ngx-gallery'; @@ -57,9 +59,12 @@ import { RefreshButtonComponent } from 'core-app/features/bim/ifc_models/toolbar */ export const viewerBridgeServiceFactory = (injector:Injector) => { if (window.navigator.userAgent.search('Revit') > -1) { - return new RevitBridgeService(injector); + return runInInjectionContext(injector, () => new RevitBridgeService()); } - return injector.get(IFCViewerService, new IFCViewerService(injector)); + + const ifcViewerService = injector.get(IFCViewerService, null); + + return ifcViewerService ?? runInInjectionContext(injector, () => new IFCViewerService()); }; @NgModule({ @@ -93,7 +98,9 @@ export const viewerBridgeServiceFactory = (injector:Injector) => { export class OpenprojectBcfModule { static bootstrapCalled = false; - constructor(injector:Injector) { + constructor() { + const injector = inject(Injector); + OpenprojectBcfModule.bootstrap(injector); } diff --git a/frontend/src/app/features/bim/ifc_models/bcf/split/left/bcf-split-left.component.ts b/frontend/src/app/features/bim/ifc_models/bcf/split/left/bcf-split-left.component.ts index 1e4323d9d6b..b7d7090cb83 100644 --- a/frontend/src/app/features/bim/ifc_models/bcf/split/left/bcf-split-left.component.ts +++ b/frontend/src/app/features/bim/ifc_models/bcf/split/left/bcf-split-left.component.ts @@ -26,11 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit, inject } from '@angular/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { BcfViewService } from 'core-app/features/bim/ifc_models/pages/viewer/bcf-view.service'; @@ -42,9 +38,9 @@ import { BcfViewService } from 'core-app/features/bim/ifc_models/pages/viewer/bc standalone: false, }) export class BcfSplitLeftComponent implements OnInit { - showViewer$:Observable; + private readonly bcfView = inject(BcfViewService); - constructor(private readonly bcfView:BcfViewService) {} + showViewer$:Observable; ngOnInit():void { this.showViewer$ = this.bcfView.live$() diff --git a/frontend/src/app/features/bim/ifc_models/bcf/split/right/bcf-split-right.component.ts b/frontend/src/app/features/bim/ifc_models/bcf/split/right/bcf-split-right.component.ts index 9efdbacf640..3e589d9ca3f 100644 --- a/frontend/src/app/features/bim/ifc_models/bcf/split/right/bcf-split-right.component.ts +++ b/frontend/src/app/features/bim/ifc_models/bcf/split/right/bcf-split-right.component.ts @@ -26,11 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit, inject } from '@angular/core'; import { Observable } from 'rxjs'; import { BcfViewService } from 'core-app/features/bim/ifc_models/pages/viewer/bcf-view.service'; import { map } from 'rxjs/operators'; @@ -42,9 +38,9 @@ import { map } from 'rxjs/operators'; standalone: false, }) export class BcfSplitRightComponent implements OnInit { - showWorkPackages$:Observable; + private readonly bcfView = inject(BcfViewService); - constructor(private readonly bcfView:BcfViewService) {} + showWorkPackages$:Observable; ngOnInit():void { this.showWorkPackages$ = this.bcfView.live$() diff --git a/frontend/src/app/features/bim/ifc_models/ifc-viewer/ifc-viewer.component.ts b/frontend/src/app/features/bim/ifc_models/ifc-viewer/ifc-viewer.component.ts index 30ec7a409f1..a09018887e1 100644 --- a/frontend/src/app/features/bim/ifc_models/ifc-viewer/ifc-viewer.component.ts +++ b/frontend/src/app/features/bim/ifc_models/ifc-viewer/ifc-viewer.component.ts @@ -26,16 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - AfterViewInit, - ChangeDetectionStrategy, - Component, - ElementRef, - HostListener, - OnDestroy, - OnInit, - ViewChild, -} from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild, inject } from '@angular/core'; import { IFCViewerService } from 'core-app/features/bim/ifc_models/ifc-viewer/ifc-viewer.service'; import { IfcModelsDataService } from 'core-app/features/bim/ifc_models/pages/viewer/ifc-models-data.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @@ -56,6 +47,12 @@ import { filter, take } from 'rxjs/operators'; standalone: false, }) export class IFCViewerComponent implements OnInit, OnDestroy, AfterViewInit { + ifcData = inject(IfcModelsDataService); + private I18n = inject(I18nService); + private ifcViewerService = inject(IFCViewerService); + private currentUserService = inject(CurrentUserService); + private currentProjectService = inject(CurrentProjectService); + private viewInitialized$ = new Subject(); modelCount:number = this.ifcData.models.length; @@ -86,14 +83,6 @@ export class IFCViewerComponent implements OnInit, OnDestroy, AfterViewInit { @ViewChild('xeokitToolbarIcons') xeokitToolbarIcons:ElementRef; - constructor( - public ifcData:IfcModelsDataService, - private I18n:I18nService, - private ifcViewerService:IFCViewerService, - private currentUserService:CurrentUserService, - private currentProjectService:CurrentProjectService, - ) { } - ngOnInit():void { if (this.modelCount === 0) { return; diff --git a/frontend/src/app/features/bim/ifc_models/ifc-viewer/ifc-viewer.service.ts b/frontend/src/app/features/bim/ifc_models/ifc-viewer/ifc-viewer.service.ts index e7823f88ccc..cc5fb8360ca 100644 --- a/frontend/src/app/features/bim/ifc_models/ifc-viewer/ifc-viewer.service.ts +++ b/frontend/src/app/features/bim/ifc_models/ifc-viewer/ifc-viewer.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector } from '@angular/core'; +import { Injectable } from '@angular/core'; import { XeokitServer } from 'core-app/features/bim/ifc_models/xeokit/xeokit-server'; import { ViewerBridgeService } from 'core-app/features/bim/bcf/bcf-viewer-bridge/viewer-bridge.service'; import { BehaviorSubject, Observable, of } from 'rxjs'; @@ -123,10 +123,6 @@ export class IFCViewerService extends ViewerBridgeService { @InjectField() httpClient:HttpClient; - constructor(readonly injector:Injector) { - super(injector); - } - public newViewer(elements:XeokitElements, projects:IfcProjectDefinition[]):void { const server = new XeokitServer(this.pathHelper, this.ifcModelsDataService); // eslint-disable-next-line @typescript-eslint/no-unsafe-call diff --git a/frontend/src/app/features/bim/ifc_models/pages/viewer/bcf-view.service.ts b/frontend/src/app/features/bim/ifc_models/pages/viewer/bcf-view.service.ts index 296358ff443..119c1f63b64 100644 --- a/frontend/src/app/features/bim/ifc_models/pages/viewer/bcf-view.service.ts +++ b/frontend/src/app/features/bim/ifc_models/pages/viewer/bcf-view.service.ts @@ -26,10 +26,9 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageQueryStateService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-base.service'; -import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space'; import { QueryResource } from 'core-app/features/hal/resources/query-resource'; import { ViewerBridgeService } from 'core-app/features/bim/bcf/bcf-viewer-bridge/viewer-bridge.service'; @@ -43,6 +42,9 @@ export type BcfViewState = 'cards'|'viewer'|'splitTable'|'splitCards'|'table'; @Injectable() export class BcfViewService extends WorkPackageQueryStateService { + private readonly I18n = inject(I18nService); + private readonly viewerBridgeService = inject(ViewerBridgeService); + public text:Record = { cards: this.I18n.t('js.views.card'), viewer: this.I18n.t('js.ifc_models.views.viewer'), @@ -59,14 +61,6 @@ export class BcfViewService extends WorkPackageQueryStateService { table: 'icon-view-list', }; - constructor( - private readonly I18n:I18nService, - private readonly viewerBridgeService:ViewerBridgeService, - protected readonly querySpace:IsolatedQuerySpace, - ) { - super(querySpace); - } - hasChanged(query:QueryResource):boolean { return this.current !== query.displayRepresentation; } diff --git a/frontend/src/app/features/bim/ifc_models/pages/viewer/ifc-viewer-page.component.ts b/frontend/src/app/features/bim/ifc_models/pages/viewer/ifc-viewer-page.component.ts index 1353dca3c8e..d0ad8893cdb 100644 --- a/frontend/src/app/features/bim/ifc_models/pages/viewer/ifc-viewer-page.component.ts +++ b/frontend/src/app/features/bim/ifc_models/pages/viewer/ifc-viewer-page.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Injector, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit, OnDestroy, ViewEncapsulation, inject } from '@angular/core'; import { PartitionedQuerySpacePageComponent, @@ -94,6 +94,10 @@ import { export class IFCViewerPageComponent extends PartitionedQuerySpacePageComponent implements UntilDestroyedMixin, OnInit, OnDestroy { + readonly ifcData = inject(IfcModelsDataService); + readonly bcfView = inject(BcfViewService); + readonly viewerBridgeService = inject(ViewerBridgeService); + text = { title: this.I18n.t('js.bcf.management'), delete: this.I18n.t('js.button_delete'), @@ -157,15 +161,6 @@ export class IFCViewerPageComponent // eslint-disable-next-line @typescript-eslint/ban-types private removeSubscription:Function; - constructor( - readonly ifcData:IfcModelsDataService, - readonly bcfView:BcfViewService, - readonly injector:Injector, - readonly viewerBridgeService:ViewerBridgeService, - ) { - super(injector); - } - ngOnInit():void { super.ngOnInit(); diff --git a/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-export-button.component.ts b/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-export-button.component.ts index 1bbb9f85189..192ceef3c21 100644 --- a/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-export-button.component.ts +++ b/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-export-button.component.ts @@ -26,9 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnDestroy, OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnDestroy, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; import { BcfPathHelperService } from 'core-app/features/bim/bcf/helper/bcf-path-helper.service'; @@ -59,6 +57,18 @@ import { JobStatusModalService } from 'core-app/features/job-status/job-status-m changeDetection: ChangeDetectionStrategy.Default, }) export class BcfExportButtonComponent extends UntilDestroyedMixin implements OnInit, OnDestroy { + readonly I18n = inject(I18nService); + readonly currentProject = inject(CurrentProjectService); + readonly bcfPathHelper = inject(BcfPathHelperService); + readonly querySpace = inject(IsolatedQuerySpace); + readonly queryUrlParamsHelper = inject(UrlParamsHelperService); + readonly jobStatusModalService = inject(JobStatusModalService); + readonly httpClient = inject(HttpClient); + readonly injector = inject(Injector); + readonly toastService = inject(ToastService); + readonly state = inject(StateService); + readonly cdRef = inject(ChangeDetectorRef); + public text = { export: this.I18n.t('js.bcf.export'), export_hover: this.I18n.t('js.bcf.export_bcf_xml_file'), @@ -68,20 +78,6 @@ export class BcfExportButtonComponent extends UntilDestroyedMixin implements OnI public exportLink:string; - constructor(readonly I18n:I18nService, - readonly currentProject:CurrentProjectService, - readonly bcfPathHelper:BcfPathHelperService, - readonly querySpace:IsolatedQuerySpace, - readonly queryUrlParamsHelper:UrlParamsHelperService, - readonly jobStatusModalService:JobStatusModalService, - readonly httpClient:HttpClient, - readonly injector:Injector, - readonly toastService:ToastService, - readonly state:StateService, - readonly cdRef:ChangeDetectorRef) { - super(); - } - ngOnInit() { this.querySpace.query .values$() diff --git a/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-import-button.component.ts b/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-import-button.component.ts index b320357f6ba..fcc7f775053 100644 --- a/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-import-button.component.ts +++ b/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-import-button.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; import { BcfPathHelperService } from 'core-app/features/bim/bcf/helper/bcf-path-helper.service'; @@ -48,16 +48,15 @@ import { BcfPathHelperService } from 'core-app/features/bim/bcf/helper/bcf-path- changeDetection: ChangeDetectionStrategy.Default, }) export class BcfImportButtonComponent { + readonly I18n = inject(I18nService); + readonly currentProject = inject(CurrentProjectService); + readonly bcfPathHelper = inject(BcfPathHelperService); + public text = { import: this.I18n.t('js.bcf.import'), import_hover: this.I18n.t('js.bcf.import_bcf_xml_file'), }; - constructor(readonly I18n:I18nService, - readonly currentProject:CurrentProjectService, - readonly bcfPathHelper:BcfPathHelperService) { - } - public handleClick() { const projectIdentifier = this.currentProject.identifier; if (projectIdentifier) { diff --git a/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/refresh-button.component.ts b/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/refresh-button.component.ts index daee1e4ca5b..668d5baaae7 100644 --- a/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/refresh-button.component.ts +++ b/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/refresh-button.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { StateService } from '@uirouter/core'; @@ -43,15 +43,14 @@ import { StateService } from '@uirouter/core'; standalone: false, }) export class RefreshButtonComponent { + readonly I18n = inject(I18nService); + readonly state = inject(StateService); + public text = { refresh: this.I18n.t('js.bcf.refresh'), refresh_hover: this.I18n.t('js.bcf.refresh_work_package'), }; - constructor(readonly I18n:I18nService, - readonly state:StateService) { - } - refresh() { void this.state.go('.', {}, { reload: true }); } diff --git a/frontend/src/app/features/bim/ifc_models/toolbar/manage-ifc-models-button/bim-manage-ifc-models-button.component.ts b/frontend/src/app/features/bim/ifc_models/toolbar/manage-ifc-models-button/bim-manage-ifc-models-button.component.ts index e030998bc45..dbd75aed06c 100644 --- a/frontend/src/app/features/bim/ifc_models/toolbar/manage-ifc-models-button/bim-manage-ifc-models-button.component.ts +++ b/frontend/src/app/features/bim/ifc_models/toolbar/manage-ifc-models-button/bim-manage-ifc-models-button.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { IfcModelsDataService } from 'core-app/features/bim/ifc_models/pages/viewer/ifc-models-data.service'; @@ -49,6 +49,9 @@ import { IfcModelsDataService } from 'core-app/features/bim/ifc_models/pages/vie standalone: false, }) export class BimManageIfcModelsButtonComponent { + readonly I18n = inject(I18nService); + readonly ifcData = inject(IfcModelsDataService); + text = { manage: this.I18n.t('js.ifc_models.models.ifc_models'), }; @@ -56,8 +59,4 @@ export class BimManageIfcModelsButtonComponent { manageAllowed = this.ifcData.allowed('manage_ifc_models'); manageIFCPath = this.ifcData.manageIFCPath; - - constructor(readonly I18n:I18nService, - readonly ifcData:IfcModelsDataService) { - } } diff --git a/frontend/src/app/features/bim/ifc_models/toolbar/view-toggle/bcf-view-toggle-button.component.ts b/frontend/src/app/features/bim/ifc_models/toolbar/view-toggle/bcf-view-toggle-button.component.ts index 30dc8d31100..511675ecaf2 100644 --- a/frontend/src/app/features/bim/ifc_models/toolbar/view-toggle/bcf-view-toggle-button.component.ts +++ b/frontend/src/app/features/bim/ifc_models/toolbar/view-toggle/bcf-view-toggle-button.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { BcfViewService } from 'core-app/features/bim/ifc_models/pages/viewer/bcf-view.service'; @@ -50,7 +50,8 @@ import { BcfViewService } from 'core-app/features/bim/ifc_models/pages/viewer/bc standalone: false, }) export class BcfViewToggleButtonComponent { - view$ = this.bcfView.live$(); + readonly I18n = inject(I18nService); + readonly bcfView = inject(BcfViewService); - constructor(readonly I18n:I18nService, readonly bcfView:BcfViewService) { } + view$ = this.bcfView.live$(); } diff --git a/frontend/src/app/features/bim/ifc_models/toolbar/view-toggle/bcf-view-toggle-dropdown.directive.ts b/frontend/src/app/features/bim/ifc_models/toolbar/view-toggle/bcf-view-toggle-dropdown.directive.ts index 3778371b688..c67ea712bdd 100644 --- a/frontend/src/app/features/bim/ifc_models/toolbar/view-toggle/bcf-view-toggle-dropdown.directive.ts +++ b/frontend/src/app/features/bim/ifc_models/toolbar/view-toggle/bcf-view-toggle-dropdown.directive.ts @@ -26,8 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { OPContextMenuService } from 'core-app/shared/components/op-context-menu/op-context-menu.service'; -import { Directive, ElementRef } from '@angular/core'; +import { Directive, inject } from '@angular/core'; import { OpContextMenuTrigger } from 'core-app/shared/components/op-context-menu/handlers/op-context-menu-trigger.directive'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { StateService } from '@uirouter/core'; @@ -48,15 +47,11 @@ import { OpContextMenuItem } from 'core-app/shared/components/op-context-menu/op standalone: false, }) export class BcfViewToggleDropdownDirective extends OpContextMenuTrigger { - constructor(readonly elementRef:ElementRef, - readonly opContextMenu:OPContextMenuService, - readonly bcfView:BcfViewService, - readonly I18n:I18nService, - readonly state:StateService, - readonly wpFiltersService:WorkPackageFiltersService, - readonly viewerBridgeService:ViewerBridgeService) { - super(elementRef, opContextMenu); - } + readonly bcfView = inject(BcfViewService); + readonly I18n = inject(I18nService); + readonly state = inject(StateService); + readonly wpFiltersService = inject(WorkPackageFiltersService); + readonly viewerBridgeService = inject(ViewerBridgeService); protected open(evt:Event):void { this.buildItems(); diff --git a/frontend/src/app/features/bim/revit_add_in/revit-add-in-settings-button.service.ts b/frontend/src/app/features/bim/revit_add_in/revit-add-in-settings-button.service.ts index 82622e630fc..1c4ca9dc4f1 100644 --- a/frontend/src/app/features/bim/revit_add_in/revit-add-in-settings-button.service.ts +++ b/frontend/src/app/features/bim/revit_add_in/revit-add-in-settings-button.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @@ -36,13 +36,15 @@ import { I18nService } from 'core-app/core/i18n/i18n.service'; */ @Injectable() export class RevitAddInSettingsButtonService { + private readonly i18n = inject(I18nService); + private readonly labelText:string; private readonly groupLabelText:string; - constructor( - private readonly i18n:I18nService, - ) { + constructor() { + const i18n = this.i18n; + const onRevitAddInEnvironment = window.navigator.userAgent.search('Revit') > -1; if (onRevitAddInEnvironment) { diff --git a/frontend/src/app/features/bim/revit_add_in/revit-bridge.service.ts b/frontend/src/app/features/bim/revit_add_in/revit-bridge.service.ts index 554797b49d8..0af2f06a801 100644 --- a/frontend/src/app/features/bim/revit_add_in/revit-bridge.service.ts +++ b/frontend/src/app/features/bim/revit_add_in/revit-bridge.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector } from '@angular/core'; +import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { distinctUntilChanged, filter, first, map, @@ -66,8 +66,8 @@ export class RevitBridgeService extends ViewerBridgeService { revitMessageReceived$ = this.revitMessageReceivedSource.asObservable(); - constructor(readonly injector:Injector) { - super(injector); + constructor() { + super(); if (window.RevitBridge) { this.hookUpRevitListener(); diff --git a/frontend/src/app/features/boards/board/add-card-dropdown/add-card-dropdown-menu.directive.ts b/frontend/src/app/features/boards/board/add-card-dropdown/add-card-dropdown-menu.directive.ts index 7ca7cdeac5c..db05068e686 100644 --- a/frontend/src/app/features/boards/board/add-card-dropdown/add-card-dropdown-menu.directive.ts +++ b/frontend/src/app/features/boards/board/add-card-dropdown/add-card-dropdown-menu.directive.ts @@ -26,13 +26,10 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectorRef, Directive, ElementRef, Injector, -} from '@angular/core'; +import { ChangeDetectorRef, Directive, Injector, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service'; import { OpContextMenuTrigger } from 'core-app/shared/components/op-context-menu/handlers/op-context-menu-trigger.directive'; -import { OPContextMenuService } from 'core-app/shared/components/op-context-menu/op-context-menu.service'; import { OpModalService } from 'core-app/shared/components/modal/modal.service'; import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space'; import { WorkPackageInlineCreateService } from 'core-app/features/work-packages/components/wp-inline-create/wp-inline-create.service'; @@ -43,18 +40,14 @@ import { BoardListComponent } from 'core-app/features/boards/board/board-list/bo standalone: false, }) export class AddCardDropdownMenuDirective extends OpContextMenuTrigger { - constructor(readonly elementRef:ElementRef, - readonly opContextMenu:OPContextMenuService, - readonly opModalService:OpModalService, - readonly authorisationService:AuthorisationService, - readonly wpInlineCreate:WorkPackageInlineCreateService, - readonly boardList:BoardListComponent, - readonly injector:Injector, - readonly querySpace:IsolatedQuerySpace, - readonly cdRef:ChangeDetectorRef, - readonly I18n:I18nService) { - super(elementRef, opContextMenu); - } + readonly opModalService = inject(OpModalService); + readonly authorisationService = inject(AuthorisationService); + readonly wpInlineCreate = inject(WorkPackageInlineCreateService); + readonly boardList = inject(BoardListComponent); + readonly injector = inject(Injector); + readonly querySpace = inject(IsolatedQuerySpace); + readonly cdRef = inject(ChangeDetectorRef); + readonly I18n = inject(I18nService); protected open(evt:Event) { this.items = this.buildItems(); diff --git a/frontend/src/app/features/boards/board/add-list-modal/add-list-modal.component.ts b/frontend/src/app/features/boards/board/add-list-modal/add-list-modal.component.ts index 5c477b04f12..96d9a9d900c 100644 --- a/frontend/src/app/features/boards/board/add-list-modal/add-list-modal.component.ts +++ b/frontend/src/app/features/boards/board/add-list-modal/add-list-modal.component.ts @@ -26,12 +26,8 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Inject, OnInit, ViewChild, -} from '@angular/core'; -import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types'; +import { ChangeDetectionStrategy, Component, OnInit, ViewChild, inject } from '@angular/core'; import { OpModalComponent } from 'core-app/shared/components/modal/modal.component'; -import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { Board } from 'core-app/features/boards/board/board'; import { BoardService } from 'core-app/features/boards/board/board.service'; @@ -58,6 +54,14 @@ import { HalResource } from 'core-app/features/hal/resources/hal-resource'; changeDetection: ChangeDetectionStrategy.Default, }) export class AddListModalComponent extends OpModalComponent implements OnInit { + readonly boardActions = inject(BoardActionsRegistryService); + readonly halNotification = inject(HalResourceNotificationService); + readonly boardService = inject(BoardService); + readonly I18n = inject(I18nService); + readonly apiV3Service = inject(ApiV3Service); + readonly currentProject = inject(CurrentProjectService); + readonly pathHelper = inject(PathHelperService); + @ViewChild(OpAutocompleterComponent, { static: true }) public ngSelectComponent:OpAutocompleterComponent; getAutocompleterData = (searchTerm:string):Observable => { @@ -112,19 +116,6 @@ export class AddListModalComponent extends OpModalComponent implements OnInit { /** Whether the no results warning is displayed */ showWarning = false; - constructor(readonly elementRef:ElementRef, - @Inject(OpModalLocalsToken) public locals:OpModalLocalsMap, - readonly cdRef:ChangeDetectorRef, - readonly boardActions:BoardActionsRegistryService, - readonly halNotification:HalResourceNotificationService, - readonly boardService:BoardService, - readonly I18n:I18nService, - readonly apiV3Service:ApiV3Service, - readonly currentProject:CurrentProjectService, - readonly pathHelper:PathHelperService) { - super(locals, cdRef, elementRef); - } - ngOnInit() { super.ngOnInit(); this.board = this.locals.board; diff --git a/frontend/src/app/features/boards/board/board-actions/assignee/assignee-board-header.component.ts b/frontend/src/app/features/boards/board/board-actions/assignee/assignee-board-header.component.ts index 7321bcf61b4..710fa05af14 100644 --- a/frontend/src/app/features/boards/board/board-actions/assignee/assignee-board-header.component.ts +++ b/frontend/src/app/features/boards/board/board-actions/assignee/assignee-board-header.component.ts @@ -25,7 +25,7 @@ // // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { UserResource } from 'core-app/features/hal/resources/user-resource'; @@ -41,13 +41,12 @@ import { UserResource } from 'core-app/features/hal/resources/user-resource'; changeDetection: ChangeDetectionStrategy.Default, }) export class AssigneeBoardHeaderComponent { + readonly pathHelper = inject(PathHelperService); + readonly I18n = inject(I18nService); + @Input('resource') public user:UserResource; text = { assignee: this.I18n.t('js.work_packages.properties.assignee'), }; - - constructor(readonly pathHelper:PathHelperService, - readonly I18n:I18nService) { - } } diff --git a/frontend/src/app/features/boards/board/board-actions/board-action.service.ts b/frontend/src/app/features/boards/board/board-actions/board-action.service.ts index ef1e4f0e361..1d285a6db2d 100644 --- a/frontend/src/app/features/boards/board/board-actions/board-action.service.ts +++ b/frontend/src/app/features/boards/board/board-actions/board-action.service.ts @@ -7,7 +7,7 @@ import { BoardListsService } from 'core-app/features/boards/board/board-list/boa import { I18nService } from 'core-app/core/i18n/i18n.service'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { map } from 'rxjs/operators'; import { IFieldSchema } from 'core-app/shared/components/fields/field.base'; import { WorkPackageChangeset } from 'core-app/features/work-packages/components/wp-edit/work-package-changeset'; @@ -25,15 +25,15 @@ import idFromLink from 'core-app/features/hal/helpers/id-from-link'; @Injectable() export abstract class BoardActionService { - constructor(readonly injector:Injector, - protected boardListsService:BoardListsService, - protected I18n:I18nService, - protected halResourceService:HalResourceService, - protected pathHelper:PathHelperService, - protected currentProject:CurrentProjectService, - protected apiV3Service:ApiV3Service, - protected schemaCache:SchemaCacheService) { - } + readonly injector = inject(Injector); + protected boardListsService = inject(BoardListsService); + protected I18n = inject(I18nService); + protected halResourceService = inject(HalResourceService); + protected pathHelper = inject(PathHelperService); + protected currentProject = inject(CurrentProjectService); + protected apiV3Service = inject(ApiV3Service); + protected schemaCache = inject(SchemaCacheService); + /** * Get the attribute name diff --git a/frontend/src/app/features/boards/board/board-actions/status/status-board-header.component.ts b/frontend/src/app/features/boards/board/board-actions/status/status-board-header.component.ts index cfdab8c28fc..ff98979b178 100644 --- a/frontend/src/app/features/boards/board/board-actions/status/status-board-header.component.ts +++ b/frontend/src/app/features/boards/board/board-actions/status/status-board-header.component.ts @@ -25,7 +25,7 @@ // // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { StatusResource } from 'core-app/features/hal/resources/status-resource'; @@ -40,12 +40,11 @@ import { StatusResource } from 'core-app/features/hal/resources/status-resource' changeDetection: ChangeDetectionStrategy.Default, }) export class StatusBoardHeaderComponent { + readonly I18n = inject(I18nService); + @Input('resource') public status:StatusResource; text = { status: this.I18n.t('js.work_packages.properties.status'), }; - - constructor(readonly I18n:I18nService) { - } } diff --git a/frontend/src/app/features/boards/board/board-actions/subproject/subproject-board-header.component.ts b/frontend/src/app/features/boards/board/board-actions/subproject/subproject-board-header.component.ts index 27fe9a979fc..3ca13c191ae 100644 --- a/frontend/src/app/features/boards/board/board-actions/subproject/subproject-board-header.component.ts +++ b/frontend/src/app/features/boards/board/board-actions/subproject/subproject-board-header.component.ts @@ -25,7 +25,7 @@ // // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; @@ -42,6 +42,9 @@ import idFromLink from 'core-app/features/hal/helpers/id-from-link'; changeDetection: ChangeDetectionStrategy.Default, }) export class SubprojectBoardHeaderComponent { + readonly pathHelper = inject(PathHelperService); + readonly I18n = inject(I18nService); + @Input() public resource:HalResource; idFromLink = idFromLink; @@ -49,8 +52,4 @@ export class SubprojectBoardHeaderComponent { text = { project: this.I18n.t('js.label_project'), }; - - constructor(readonly pathHelper:PathHelperService, - readonly I18n:I18nService) { - } } diff --git a/frontend/src/app/features/boards/board/board-actions/subtasks/subtasks-board-header.component.ts b/frontend/src/app/features/boards/board/board-actions/subtasks/subtasks-board-header.component.ts index 04040a17623..52253bd4414 100644 --- a/frontend/src/app/features/boards/board/board-actions/subtasks/subtasks-board-header.component.ts +++ b/frontend/src/app/features/boards/board/board-actions/subtasks/subtasks-board-header.component.ts @@ -25,7 +25,7 @@ // // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit, inject } from '@angular/core'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; @@ -43,6 +43,9 @@ import idFromLink from 'core-app/features/hal/helpers/id-from-link'; changeDetection: ChangeDetectionStrategy.Default, }) export class SubtasksBoardHeaderComponent implements OnInit { + readonly pathHelper = inject(PathHelperService); + readonly I18n = inject(I18nService); + @Input() public resource:WorkPackageResource; idFromLink = idFromLink; @@ -53,10 +56,6 @@ export class SubtasksBoardHeaderComponent implements OnInit { typeHighlightingClass:string; - constructor(readonly pathHelper:PathHelperService, - readonly I18n:I18nService) { - } - ngOnInit() { this.typeHighlightingClass = Highlighting.inlineClass('type', this.resource.type.id!); } diff --git a/frontend/src/app/features/boards/board/board-actions/version/version-board-header.component.ts b/frontend/src/app/features/boards/board/board-actions/version/version-board-header.component.ts index d57d88bc732..5c1a79bf5a0 100644 --- a/frontend/src/app/features/boards/board/board-actions/version/version-board-header.component.ts +++ b/frontend/src/app/features/boards/board/board-actions/version/version-board-header.component.ts @@ -25,7 +25,7 @@ // // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; import { VersionResource } from 'core-app/features/hal/resources/version-resource'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; @@ -41,11 +41,11 @@ import { PathHelperService } from 'core-app/core/path-helper/path-helper.service changeDetection: ChangeDetectionStrategy.Default, }) export class VersionBoardHeaderComponent { - @Input('resource') public version:VersionResource; + readonly I18n = inject(I18nService); + readonly pathHelper = inject(PathHelperService); - constructor(readonly I18n:I18nService, - readonly pathHelper:PathHelperService) { - } + // eslint-disable-next-line @angular-eslint/no-input-rename + @Input('resource') public version:VersionResource; public text = { isLocked: this.I18n.t('js.boards.version.is_locked'), diff --git a/frontend/src/app/features/boards/board/board-filter/board-filter.component.ts b/frontend/src/app/features/boards/board/board-filter/board-filter.component.ts index 0a3ec34be8f..1bae1325dfb 100644 --- a/frontend/src/app/features/boards/board/board-filter/board-filter.component.ts +++ b/frontend/src/app/features/boards/board/board-filter/board-filter.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; import { Board } from 'core-app/features/boards/board/board'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; import { WorkPackageStatesInitializationService } from 'core-app/features/work-packages/components/wp-list/wp-states-initialization.service'; @@ -23,22 +23,20 @@ import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; changeDetection: ChangeDetectionStrategy.Default, }) export class BoardFilterComponent extends UntilDestroyedMixin implements AfterViewInit { + private readonly currentProjectService = inject(CurrentProjectService); + private readonly querySpace = inject(IsolatedQuerySpace); + private readonly apiV3Service = inject(ApiV3Service); + private readonly halResourceService = inject(HalResourceService); + private readonly wpStatesInitialization = inject(WorkPackageStatesInitializationService); + private readonly wpTableFilters = inject(WorkPackageViewFiltersService); + private readonly urlParamsHelper = inject(UrlParamsHelperService); + private readonly boardFilters = inject(BoardFiltersService); + /** Current active */ @Input() public board$:Observable; initialized = false; - constructor(private readonly currentProjectService:CurrentProjectService, - private readonly querySpace:IsolatedQuerySpace, - private readonly apiV3Service:ApiV3Service, - private readonly halResourceService:HalResourceService, - private readonly wpStatesInitialization:WorkPackageStatesInitializationService, - private readonly wpTableFilters:WorkPackageViewFiltersService, - private readonly urlParamsHelper:UrlParamsHelperService, - private readonly boardFilters:BoardFiltersService) { - super(); - } - ngAfterViewInit():void { if (!this.board$) { return; diff --git a/frontend/src/app/features/boards/board/board-list/board-inline-create.service.ts b/frontend/src/app/features/boards/board/board-list/board-inline-create.service.ts index cd2d0d53ffe..410ff8ebefa 100644 --- a/frontend/src/app/features/boards/board/board-list/board-inline-create.service.ts +++ b/frontend/src/app/features/boards/board/board-list/board-inline-create.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { WorkPackageInlineCreateService, @@ -40,13 +40,8 @@ import { Observable } from 'rxjs'; @Injectable() export class BoardInlineCreateService extends WorkPackageInlineCreateService { - constructor( - readonly injector:Injector, - protected readonly querySpace:IsolatedQuerySpace, - protected readonly halResourceService:HalResourceService, - ) { - super(injector); - } + protected readonly querySpace = inject(IsolatedQuerySpace); + protected readonly halResourceService = inject(HalResourceService); /** * A separate reference pane for the inline create component diff --git a/frontend/src/app/features/boards/board/board-list/board-list-menu.component.ts b/frontend/src/app/features/boards/board/board-list/board-list-menu.component.ts index 6c1ee33907b..cb83ba894c2 100644 --- a/frontend/src/app/features/boards/board/board-list/board-list-menu.component.ts +++ b/frontend/src/app/features/boards/board/board-list/board-list-menu.component.ts @@ -26,9 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, Component, EventEmitter, Input, Output, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service'; import { OpModalService } from 'core-app/shared/components/modal/modal.service'; @@ -49,18 +47,17 @@ import { BoardActionService } from 'core-app/features/boards/board/board-actions changeDetection: ChangeDetectionStrategy.Default, }) export class BoardListMenuComponent { + readonly opModalService = inject(OpModalService); + readonly authorisationService = inject(AuthorisationService); + private readonly querySpace = inject(IsolatedQuerySpace); + private readonly boardService = inject(BoardService); + private readonly boardActionRegistry = inject(BoardActionsRegistryService); + readonly I18n = inject(I18nService); + @Input() board:Board; @Output() onRemove = new EventEmitter(); - constructor(readonly opModalService:OpModalService, - readonly authorisationService:AuthorisationService, - private readonly querySpace:IsolatedQuerySpace, - private readonly boardService:BoardService, - private readonly boardActionRegistry:BoardActionsRegistryService, - readonly I18n:I18nService) { - } - public get menuItems() { return async () => { const items:OpContextMenuItem[] = [ diff --git a/frontend/src/app/features/boards/board/board-list/board-list.component.ts b/frontend/src/app/features/boards/board/board-list/board-list.component.ts index cf85cae64e2..6b3df5dc4f6 100644 --- a/frontend/src/app/features/boards/board/board-list/board-list.component.ts +++ b/frontend/src/app/features/boards/board/board-list/board-list.component.ts @@ -5,7 +5,6 @@ import { ElementRef, EventEmitter, inject, - Injector, Input, OnDestroy, OnInit, @@ -19,7 +18,6 @@ import { import { WorkPackageInlineCreateService } from 'core-app/features/work-packages/components/wp-inline-create/wp-inline-create.service'; import { BoardInlineCreateService } from 'core-app/features/boards/board/board-list/board-inline-create.service'; import { AbstractWidgetComponent } from 'core-app/shared/components/grids/widgets/abstract-widget.component'; -import { I18nService } from 'core-app/core/i18n/i18n.service'; import { ToastService } from 'core-app/shared/components/toaster/toast.service'; import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space'; import { Board } from 'core-app/features/boards/board/board'; @@ -89,6 +87,31 @@ export interface DisabledButtonPlaceholder { standalone: false, }) export class BoardListComponent extends AbstractWidgetComponent implements OnInit, OnDestroy { + readonly apiv3Service = inject(ApiV3Service); + readonly state = inject(StateService); + readonly cdRef = inject(ChangeDetectorRef); + readonly transitions = inject(TransitionService); + readonly boardFilters = inject(BoardFiltersService); + readonly toastService = inject(ToastService); + readonly querySpace = inject(IsolatedQuerySpace); + readonly halNotification = inject(HalResourceNotificationService); + readonly halEvents = inject(HalEventsService); + readonly wpStatesInitialization = inject(WorkPackageStatesInitializationService); + readonly wpViewFocusService = inject(WorkPackageViewFocusService); + readonly wpViewSelectionService = inject(WorkPackageViewSelectionService); + readonly boardListCrossSelectionService = inject(BoardListCrossSelectionService); + readonly authorisationService = inject(AuthorisationService); + readonly wpInlineCreate = inject(WorkPackageInlineCreateService); + readonly halEditing = inject(HalResourceEditingService); + readonly loadingIndicator = inject(LoadingIndicatorService); + readonly schemaCache = inject(SchemaCacheService); + readonly boardService = inject(BoardService); + readonly boardActionRegistry = inject(BoardActionsRegistryService); + readonly causedUpdates = inject(CausedUpdatesService); + readonly keepTab = inject(KeepTabService); + readonly currentProject = inject(CurrentProjectService); + readonly pathHelper = inject(PathHelperService); + /** Output fired upon query removal */ @Output() onRemove = new EventEmitter(); @@ -127,13 +150,15 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni public columnsQueryProps:any; - public text = { - addCard: this.I18n.t('js.boards.add_card'), - updateSuccessful: this.I18n.t('js.notice_successful_update'), - areYouSure: this.I18n.t('js.text_are_you_sure'), - unnamed_list: this.I18n.t('js.boards.label_unnamed_list'), - click_to_remove: this.I18n.t('js.boards.click_to_remove_list'), - }; + public get text() { + return { + addCard: this.i18n.t('js.boards.add_card'), + updateSuccessful: this.i18n.t('js.notice_successful_update'), + areYouSure: this.i18n.t('js.text_are_you_sure'), + unnamed_list: this.i18n.t('js.boards.label_unnamed_list'), + click_to_remove: this.i18n.t('js.boards.click_to_remove_list'), + }; + } /** Are we allowed to remove and drag & drop elements ? */ public canDragInto = false; @@ -153,37 +178,6 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni private readonly states = inject(States); - constructor( - readonly apiv3Service:ApiV3Service, - readonly I18n:I18nService, - readonly state:StateService, - readonly cdRef:ChangeDetectorRef, - readonly transitions:TransitionService, - readonly boardFilters:BoardFiltersService, - readonly toastService:ToastService, - readonly querySpace:IsolatedQuerySpace, - readonly halNotification:HalResourceNotificationService, - readonly halEvents:HalEventsService, - readonly wpStatesInitialization:WorkPackageStatesInitializationService, - readonly wpViewFocusService:WorkPackageViewFocusService, - readonly wpViewSelectionService:WorkPackageViewSelectionService, - readonly boardListCrossSelectionService:BoardListCrossSelectionService, - readonly authorisationService:AuthorisationService, - readonly wpInlineCreate:WorkPackageInlineCreateService, - readonly injector:Injector, - readonly halEditing:HalResourceEditingService, - readonly loadingIndicator:LoadingIndicatorService, - readonly schemaCache:SchemaCacheService, - readonly boardService:BoardService, - readonly boardActionRegistry:BoardActionsRegistryService, - readonly causedUpdates:CausedUpdatesService, - readonly keepTab:KeepTabService, - readonly currentProject:CurrentProjectService, - readonly pathHelper:PathHelperService, - ) { - super(I18n, injector); - } - ngOnInit():void { // Unset the isNew flag this.initiallyFocused = this.resource.isNewWidget; @@ -257,7 +251,7 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni } public get errorMessage() { - return this.I18n.t('js.boards.error_loading_the_list', { error_message: this.loadingError }); + return this.i18n.t('js.boards.error_loading_the_list', { error_message: this.loadingError }); } public canMove(workPackage:WorkPackageResource) { diff --git a/frontend/src/app/features/boards/board/board-list/board-lists.service.ts b/frontend/src/app/features/boards/board/board-list/board-lists.service.ts index 86bbbd2a378..8c6c63adec1 100644 --- a/frontend/src/app/features/boards/board/board-list/board-lists.service.ts +++ b/frontend/src/app/features/boards/board/board-list/board-lists.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { QueryResource } from 'core-app/features/hal/resources/query-resource'; @@ -19,18 +19,15 @@ import { IOPFieldSchema } from 'core-app/features/hal/interfaces'; @Injectable({ providedIn: 'root' }) export class BoardListsService { + private readonly CurrentProject = inject(CurrentProjectService); + private readonly pathHelper = inject(PathHelperService); + private readonly apiV3Service = inject(ApiV3Service); + private readonly halResourceService = inject(HalResourceService); + private readonly toastService = inject(ToastService); + private readonly I18n = inject(I18nService); + private v3 = this.pathHelper.api.v3; - constructor( - private readonly CurrentProject:CurrentProjectService, - private readonly pathHelper:PathHelperService, - private readonly apiV3Service:ApiV3Service, - private readonly halResourceService:HalResourceService, - private readonly toastService:ToastService, - private readonly I18n:I18nService) { - - } - private create(params:object, filters:ApiV3Filter[]):Observable { const filterJson = JSON.stringify(filters); diff --git a/frontend/src/app/features/boards/board/board-partitioned-page/board-entry.component.ts b/frontend/src/app/features/boards/board/board-partitioned-page/board-entry.component.ts index 1fe536a15eb..e0ef5fdbc67 100644 --- a/frontend/src/app/features/boards/board/board-partitioned-page/board-entry.component.ts +++ b/frontend/src/app/features/boards/board/board-partitioned-page/board-entry.component.ts @@ -26,14 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - Injector, - Input, - OnDestroy, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, Injector, Input, OnDestroy, inject } from '@angular/core'; import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs'; import { WorkPackageIsolatedQuerySpaceDirective, @@ -64,12 +57,14 @@ import { QueryUpdatedService } from 'core-app/features/boards/board/query-update standalone: false, }) export class BoardEntryComponent implements OnDestroy { + readonly elementRef = inject(ElementRef); + readonly injector = inject(Injector); + @Input() boardId:string; - constructor( - readonly elementRef:ElementRef, - readonly injector:Injector, - ) { + constructor() { + const injector = this.injector; + populateInputsFromDataset(this); document.body.classList.add('router--boards-full-view'); diff --git a/frontend/src/app/features/boards/board/board-partitioned-page/board-list-container.component.ts b/frontend/src/app/features/boards/board/board-partitioned-page/board-list-container.component.ts index d033ea67e5e..2fd95d3ef04 100644 --- a/frontend/src/app/features/boards/board/board-partitioned-page/board-list-container.component.ts +++ b/frontend/src/app/features/boards/board/board-partitioned-page/board-list-container.component.ts @@ -54,6 +54,24 @@ import { resolveRoutingId } from 'core-app/features/work-packages/helpers/work-p standalone: false, }) export class BoardListContainerComponent extends UntilDestroyedMixin implements OnInit { + readonly I18n = inject(I18nService); + readonly state = inject(StateService); + readonly toastService = inject(ToastService); + readonly halNotification = inject(HalResourceNotificationService); + readonly boardComponent = inject(BoardPartitionedPageComponent); + readonly BoardList = inject(BoardListsService); + readonly boardActionRegistry = inject(BoardActionsRegistryService); + readonly opModalService = inject(OpModalService); + readonly injector = inject(Injector); + readonly apiV3Service = inject(ApiV3Service); + readonly Boards = inject(BoardService); + readonly boardListCrossSelectionService = inject(BoardListCrossSelectionService); + readonly Drag = inject(DragAndDropService); + readonly apiv3Service = inject(ApiV3Service); + readonly QueryUpdated = inject(QueryUpdatedService); + readonly pathHelper = inject(PathHelperService); + readonly currentProject = inject(CurrentProjectService); + @Input() boardId:string; text = { delete: this.I18n.t('js.button_delete'), @@ -96,28 +114,6 @@ export class BoardListContainerComponent extends UntilDestroyedMixin implements private readonly wpStates = inject(States); - constructor( - readonly I18n:I18nService, - readonly state:StateService, - readonly toastService:ToastService, - readonly halNotification:HalResourceNotificationService, - readonly boardComponent:BoardPartitionedPageComponent, - readonly BoardList:BoardListsService, - readonly boardActionRegistry:BoardActionsRegistryService, - readonly opModalService:OpModalService, - readonly injector:Injector, - readonly apiV3Service:ApiV3Service, - readonly Boards:BoardService, - readonly boardListCrossSelectionService:BoardListCrossSelectionService, - readonly Drag:DragAndDropService, - readonly apiv3Service:ApiV3Service, - readonly QueryUpdated:QueryUpdatedService, - readonly pathHelper:PathHelperService, - readonly currentProject:CurrentProjectService, -) { - super(); - } - ngOnInit():void { const id:string = this.boardId || this.state.params.board_id?.toString(); this.board$ = this diff --git a/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts b/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts index 464c05c4a7a..4d127e62e7e 100644 --- a/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts +++ b/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts @@ -1,11 +1,4 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Injector, - Input, - OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, Input, OnInit, inject } from '@angular/core'; import { DynamicComponentDefinition, ToolbarButtonComponentDefinition, @@ -60,6 +53,20 @@ export function boardCardViewHandlerFactory(injector:Injector) { standalone: false, }) export class BoardPartitionedPageComponent extends UntilDestroyedMixin implements OnInit { + readonly I18n = inject(I18nService); + readonly cdRef = inject(ChangeDetectorRef); + readonly state = inject(StateService); + readonly toastService = inject(ToastService); + readonly halNotification = inject(HalResourceNotificationService); + readonly injector = inject(Injector); + readonly apiV3Service = inject(ApiV3Service); + readonly boardFilters = inject(BoardFiltersService); + readonly Boards = inject(BoardService); + readonly titleService = inject(OpTitleService); + readonly submenuService = inject(SubmenuService); + readonly pathHelperService = inject(PathHelperService); + readonly currentProject = inject(CurrentProjectService); + @Input() boardId:string; text = { button_more: this.I18n.t('js.button_more'), @@ -129,24 +136,6 @@ export class BoardPartitionedPageComponent extends UntilDestroyedMixin implement }, ]; - constructor( - readonly I18n:I18nService, - readonly cdRef:ChangeDetectorRef, - readonly state:StateService, - readonly toastService:ToastService, - readonly halNotification:HalResourceNotificationService, - readonly injector:Injector, - readonly apiV3Service:ApiV3Service, - readonly boardFilters:BoardFiltersService, - readonly Boards:BoardService, - readonly titleService:OpTitleService, - readonly submenuService:SubmenuService, - readonly pathHelperService:PathHelperService, - readonly currentProject:CurrentProjectService, - ) { - super(); - } - ngOnInit():void { // Ensure board is being loaded this.Boards.loadAllBoards(); diff --git a/frontend/src/app/features/boards/board/board.service.ts b/frontend/src/app/features/boards/board/board.service.ts index 143b2dd1a6d..11eda6477d9 100644 --- a/frontend/src/app/features/boards/board/board.service.ts +++ b/frontend/src/app/features/boards/board/board.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; @@ -10,6 +10,12 @@ import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; @Injectable({ providedIn: 'root' }) export class BoardService { + protected apiV3Service = inject(ApiV3Service); + protected PathHelper = inject(PathHelperService); + protected CurrentProject = inject(CurrentProjectService); + protected halResourceService = inject(HalResourceService); + protected I18n = inject(I18nService); + public currentBoard$:BehaviorSubject = new BehaviorSubject(null); private loadAllPromise:Promise|undefined; @@ -21,15 +27,6 @@ export class BoardService { unnamed_list: this.I18n.t('js.boards.label_unnamed_list'), }; - constructor( - protected apiV3Service:ApiV3Service, - protected PathHelper:PathHelperService, - protected CurrentProject:CurrentProjectService, - protected halResourceService:HalResourceService, - protected I18n:I18nService, - ) { - } - /** * Return all boards in the current scope of the project * diff --git a/frontend/src/app/features/boards/board/configuration-modal/board-configuration.modal.ts b/frontend/src/app/features/boards/board/configuration-modal/board-configuration.modal.ts index a24aee77456..503bcbdc247 100644 --- a/frontend/src/app/features/boards/board/configuration-modal/board-configuration.modal.ts +++ b/frontend/src/app/features/boards/board/configuration-modal/board-configuration.modal.ts @@ -1,19 +1,5 @@ -import { - ApplicationRef, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ComponentFactoryResolver, - ElementRef, - Inject, - Injector, - OnDestroy, - OnInit, - ViewChild, -} from '@angular/core'; -import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types'; +import { ApplicationRef, ChangeDetectionStrategy, Component, ComponentFactoryResolver, ElementRef, Injector, OnDestroy, OnInit, ViewChild, inject } from '@angular/core'; import { OpModalComponent } from 'core-app/shared/components/modal/modal.component'; -import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service'; import { ActiveTabInterface, TabComponent, @@ -34,6 +20,13 @@ import { Board } from 'core-app/features/boards/board/board'; changeDetection: ChangeDetectionStrategy.Default, }) export class BoardConfigurationModalComponent extends OpModalComponent implements OnInit, OnDestroy { + readonly I18n = inject(I18nService); + readonly boardService = inject(BoardService); + readonly boardConfigurationService = inject(BoardConfigurationService); + readonly injector = inject(Injector); + readonly appRef = inject(ApplicationRef); + readonly componentFactoryResolver = inject(ComponentFactoryResolver); + public text = { title: this.I18n.t('js.boards.configuration_modal.title'), closePopup: this.I18n.t('js.close_popup_title'), @@ -48,18 +41,6 @@ export class BoardConfigurationModalComponent extends OpModalComponent implement // And a reference to the actual portal host interface public tabPortalHost:TabPortalOutlet; - constructor(@Inject(OpModalLocalsToken) public locals:OpModalLocalsMap, - readonly I18n:I18nService, - readonly boardService:BoardService, - readonly boardConfigurationService:BoardConfigurationService, - readonly injector:Injector, - readonly appRef:ApplicationRef, - readonly componentFactoryResolver:ComponentFactoryResolver, - readonly cdRef:ChangeDetectorRef, - readonly elementRef:ElementRef) { - super(locals, cdRef, elementRef); - } - ngOnInit() { this.element = this.elementRef.nativeElement as HTMLElement; diff --git a/frontend/src/app/features/boards/board/configuration-modal/board-configuration.service.ts b/frontend/src/app/features/boards/board/configuration-modal/board-configuration.service.ts index 6cbd5f3ef95..933b78ce798 100644 --- a/frontend/src/app/features/boards/board/configuration-modal/board-configuration.service.ts +++ b/frontend/src/app/features/boards/board/configuration-modal/board-configuration.service.ts @@ -1,10 +1,12 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { TabInterface } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tab-portal-outlet'; import { BoardHighlightingTabComponent } from 'core-app/features/boards/board/configuration-modal/tabs/highlighting-tab.component'; @Injectable() export class BoardConfigurationService { + readonly I18n = inject(I18nService); + protected _tabs:TabInterface[] = [ { id: 'highlighting', @@ -13,9 +15,6 @@ export class BoardConfigurationService { }, ]; - constructor(readonly I18n:I18nService) { - } - public get tabs() { return this._tabs; } diff --git a/frontend/src/app/features/boards/board/configuration-modal/tabs/highlighting-tab.component.ts b/frontend/src/app/features/boards/board/configuration-modal/tabs/highlighting-tab.component.ts index 489277262d9..e37f4ef97d2 100644 --- a/frontend/src/app/features/boards/board/configuration-modal/tabs/highlighting-tab.component.ts +++ b/frontend/src/app/features/boards/board/configuration-modal/tabs/highlighting-tab.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Inject, Injector, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Injector, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { TabComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tab-portal-outlet'; import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types'; @@ -15,6 +15,10 @@ import { CardHighlightingMode } from 'core-app/features/work-packages/components changeDetection: ChangeDetectionStrategy.Default, }) export class BoardHighlightingTabComponent implements TabComponent, OnInit { + readonly injector = inject(Injector); + locals = inject(OpModalLocalsToken); + readonly I18n = inject(I18nService); + // Highlighting mode public highlightingMode:CardHighlightingMode = 'none'; @@ -35,11 +39,6 @@ export class BoardHighlightingTabComponent implements TabComponent, OnInit { }, }; - constructor(readonly injector:Injector, - @Inject(OpModalLocalsToken) public locals:OpModalLocalsMap, - readonly I18n:I18nService) { - } - public onSave() { this.updateMode(this.highlightingMode); this.board.highlightingMode = this.highlightingMode; diff --git a/frontend/src/app/features/boards/board/inline-add/board-inline-add-autocompleter.component.ts b/frontend/src/app/features/boards/board/inline-add/board-inline-add-autocompleter.component.ts index 540b2176523..4476279bb3f 100644 --- a/frontend/src/app/features/boards/board/inline-add/board-inline-add-autocompleter.component.ts +++ b/frontend/src/app/features/boards/board/inline-add/board-inline-add-autocompleter.component.ts @@ -26,17 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - Output, - ViewChild, - ViewEncapsulation, -} from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewChild, ViewEncapsulation, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { Observable, of } from 'rxjs'; import { catchError, map, tap } from 'rxjs/operators'; @@ -66,6 +56,18 @@ import { HalResourceService } from 'core-app/features/hal/services/hal-resource. changeDetection: ChangeDetectionStrategy.Default, }) export class BoardInlineAddAutocompleterComponent implements AfterViewInit { + private readonly querySpace = inject(IsolatedQuerySpace); + private readonly pathHelper = inject(PathHelperService); + private readonly apiV3Service = inject(ApiV3Service); + private readonly urlParamsHelper = inject(UrlParamsHelperService); + private readonly notificationService = inject(WorkPackageNotificationService); + private readonly CurrentProject = inject(CurrentProjectService); + private readonly halResourceService = inject(HalResourceService); + private readonly schemaCacheService = inject(SchemaCacheService); + private readonly cdRef = inject(ChangeDetectorRef); + private readonly I18n = inject(I18nService); + private readonly wpCardDragDrop = inject(WorkPackageCardDragAndDropService); + readonly text = { placeholder: this.I18n.t('js.relations_autocomplete.placeholder'), }; @@ -119,19 +121,6 @@ export class BoardInlineAddAutocompleterComponent implements AfterViewInit { @Output() onReferenced = new EventEmitter(); - constructor(private readonly querySpace:IsolatedQuerySpace, - private readonly pathHelper:PathHelperService, - private readonly apiV3Service:ApiV3Service, - private readonly urlParamsHelper:UrlParamsHelperService, - private readonly notificationService:WorkPackageNotificationService, - private readonly CurrentProject:CurrentProjectService, - private readonly halResourceService:HalResourceService, - private readonly schemaCacheService:SchemaCacheService, - private readonly cdRef:ChangeDetectorRef, - private readonly I18n:I18nService, - private readonly wpCardDragDrop:WorkPackageCardDragAndDropService) { - } - ngAfterViewInit():void { if (!this.ngSelectComponent.ngSelectInstance) { return; diff --git a/frontend/src/app/features/boards/board/query-updated/query-updated.service.ts b/frontend/src/app/features/boards/board/query-updated/query-updated.service.ts index 9fedd7401cb..fa2770625bd 100644 --- a/frontend/src/app/features/boards/board/query-updated/query-updated.service.ts +++ b/frontend/src/app/features/boards/board/query-updated/query-updated.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { interval } from 'rxjs'; import { filter, startWith, switchMap } from 'rxjs/operators'; import { ActiveWindowService } from 'core-app/core/active-window/active-window.service'; @@ -8,9 +8,9 @@ const POLLING_INTERVAL = 2000; @Injectable() export class QueryUpdatedService { - constructor(readonly activeWindow:ActiveWindowService, - readonly apiV3Service:ApiV3Service) { - } + readonly activeWindow = inject(ActiveWindowService); + readonly apiV3Service = inject(ApiV3Service); + public monitor(ids:string[]) { let time = new Date(); diff --git a/frontend/src/app/features/boards/board/toolbar-menu/boards-menu-button.component.ts b/frontend/src/app/features/boards/board/toolbar-menu/boards-menu-button.component.ts index 758fd6ad1d7..714c5c79adb 100644 --- a/frontend/src/app/features/boards/board/toolbar-menu/boards-menu-button.component.ts +++ b/frontend/src/app/features/boards/board/toolbar-menu/boards-menu-button.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { Board } from 'core-app/features/boards/board/board'; import { Observable } from 'rxjs'; @@ -19,12 +19,11 @@ import { Observable } from 'rxjs'; changeDetection: ChangeDetectionStrategy.Default, }) export class BoardsMenuButtonComponent { + readonly I18n = inject(I18nService); + @Input() board$:Observable; text = { button_more: this.I18n.t('js.button_more'), }; - - constructor(readonly I18n:I18nService) { - } } diff --git a/frontend/src/app/features/boards/board/toolbar-menu/boards-toolbar-menu.directive.ts b/frontend/src/app/features/boards/board/toolbar-menu/boards-toolbar-menu.directive.ts index ce45ca6d7ae..156f48fb0af 100644 --- a/frontend/src/app/features/boards/board/toolbar-menu/boards-toolbar-menu.directive.ts +++ b/frontend/src/app/features/boards/board/toolbar-menu/boards-toolbar-menu.directive.ts @@ -26,13 +26,10 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - Directive, ElementRef, Injector, Input, -} from '@angular/core'; +import { Directive, Injector, Input, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { OpContextMenuTrigger } from 'core-app/shared/components/op-context-menu/handlers/op-context-menu-trigger.directive'; -import { OPContextMenuService } from 'core-app/shared/components/op-context-menu/op-context-menu.service'; import { OpModalService } from 'core-app/shared/components/modal/modal.service'; import { Board } from 'core-app/features/boards/board/board'; import { BoardConfigurationModalComponent } from 'core-app/features/boards/board/configuration-modal/board-configuration.modal'; @@ -46,21 +43,16 @@ import { selectableTitleIdentifier, triggerEditingEvent } from 'core-app/shared/ standalone: false, }) export class BoardsToolbarMenuDirective extends OpContextMenuTrigger { - @Input('boardsToolbarMenu-resource') public board:Board; + readonly opModalService = inject(OpModalService); + readonly boardService = inject(BoardService); + readonly Notifications = inject(ToastService); + readonly State = inject(StateService); + readonly injector = inject(Injector); + readonly I18n = inject(I18nService); + readonly http = inject(HttpClient); - constructor( - readonly elementRef:ElementRef, - readonly opContextMenu:OPContextMenuService, - readonly opModalService:OpModalService, - readonly boardService:BoardService, - readonly Notifications:ToastService, - readonly State:StateService, - readonly injector:Injector, - readonly I18n:I18nService, - readonly http:HttpClient, - ) { - super(elementRef, opContextMenu); - } + // eslint-disable-next-line @angular-eslint/no-input-rename + @Input('boardsToolbarMenu-resource') public board:Board; public get locals() { return { diff --git a/frontend/src/app/features/calendar/calendar-entry.component.ts b/frontend/src/app/features/calendar/calendar-entry.component.ts index 645a7efd35d..6d7343e01a8 100644 --- a/frontend/src/app/features/calendar/calendar-entry.component.ts +++ b/frontend/src/app/features/calendar/calendar-entry.component.ts @@ -26,13 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - Input, - OnDestroy, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, Input, OnDestroy, inject } from '@angular/core'; import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs'; import { WorkPackageIsolatedQuerySpaceDirective, @@ -45,9 +39,11 @@ import { standalone: false, }) export class CalendarEntryComponent implements OnDestroy { + readonly elementRef = inject(ElementRef); + @Input() queryId:string; - constructor(readonly elementRef:ElementRef) { + constructor() { populateInputsFromDataset(this); document.body.classList.add('router--calendar'); } diff --git a/frontend/src/app/features/calendar/op-calendar.service.spec.ts b/frontend/src/app/features/calendar/op-calendar.service.spec.ts index 23ccec1742c..cd7cf47143e 100644 --- a/frontend/src/app/features/calendar/op-calendar.service.spec.ts +++ b/frontend/src/app/features/calendar/op-calendar.service.spec.ts @@ -26,16 +26,25 @@ // See COPYRIGHT and LICENSE files for more details. //++ +import { TestBed } from '@angular/core/testing'; import { OpCalendarService } from 'core-app/features/calendar/op-calendar.service'; +import { WeekdayService } from 'core-app/core/days/weekday.service'; +import { DayResourceService } from 'core-app/core/state/days/day.service'; +import { ConfigurationService } from 'core-app/core/config/configuration.service'; describe('OP calendar service', () => { let service:OpCalendarService; beforeEach(() => { - // This is not a valid constructor call, but since we only want to test a helper method that does not - // depend on injected services, we can pass null values here. - // @ts-expect-error ignore invalid constructor call since we don't need a completely valid instance - service = new OpCalendarService(null, null, null); + TestBed.configureTestingModule({ + providers: [ + OpCalendarService, + { provide: WeekdayService, useValue: {} }, + { provide: DayResourceService, useValue: {} }, + { provide: ConfigurationService, useValue: {} }, + ], + }); + service = TestBed.inject(OpCalendarService); }); describe('stripYearFromDateFormat', () => { diff --git a/frontend/src/app/features/calendar/op-calendar.service.ts b/frontend/src/app/features/calendar/op-calendar.service.ts index e59d607e81f..a8f63538014 100644 --- a/frontend/src/app/features/calendar/op-calendar.service.ts +++ b/frontend/src/app/features/calendar/op-calendar.service.ts @@ -1,7 +1,4 @@ -import { - ElementRef, - Injectable, -} from '@angular/core'; +import { ElementRef, Injectable, inject } from '@angular/core'; import { Subject } from 'rxjs'; import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; import { WeekdayService } from 'core-app/core/days/weekday.service'; @@ -13,18 +10,14 @@ import { DayHeaderContentArg } from '@fullcalendar/core'; @Injectable() export class OpCalendarService extends UntilDestroyedMixin { + readonly weekdayService = inject(WeekdayService); + readonly dayService = inject(DayResourceService); + readonly configurationService = inject(ConfigurationService); + resize$ = new Subject(); resizeObs:ResizeObserver; - constructor( - readonly weekdayService:WeekdayService, - readonly dayService:DayResourceService, - readonly configurationService:ConfigurationService, - ) { - super(); - } - resizeObserver(v:ElementRef|undefined):void { if (!v) { return; diff --git a/frontend/src/app/features/calendar/op-work-packages-calendar.service.ts b/frontend/src/app/features/calendar/op-work-packages-calendar.service.ts index 5184f8c5be1..641751a4e31 100644 --- a/frontend/src/app/features/calendar/op-work-packages-calendar.service.ts +++ b/frontend/src/app/features/calendar/op-work-packages-calendar.service.ts @@ -75,6 +75,28 @@ interface CalendarOptionsWithDayGrid extends CalendarOptions { @Injectable() export class OpWorkPackagesCalendarService extends UntilDestroyedMixin { + private I18n = inject(I18nService); + private configuration = inject(ConfigurationService); + private sanitizer = inject(DomSanitizer); + readonly injector = inject(Injector); + readonly schemaCache = inject(SchemaCacheService); + readonly toastService = inject(ToastService); + readonly wpTableFilters = inject(WorkPackageViewFiltersService); + readonly wpListService = inject(WorkPackagesListService); + readonly wpListChecksumService = inject(WorkPackagesListChecksumService); + readonly urlParamsHelper = inject(UrlParamsHelperService); + readonly querySpace = inject(IsolatedQuerySpace); + readonly apiV3Service = inject(ApiV3Service); + readonly halResourceService = inject(HalResourceService); + readonly timezoneService = inject(TimezoneService); + readonly pathHelper = inject(PathHelperService); + readonly halEditing = inject(HalResourceEditingService); + readonly wpTableSelection = inject(WorkPackageViewSelectionService); + readonly contextMenuService = inject(OPContextMenuService); + readonly calendarService = inject(OpCalendarService); + readonly weekdayService = inject(WeekdayService); + readonly dayService = inject(DayResourceService); + static MAX_DISPLAYED = 500; tooManyResultsText:string|null; @@ -91,32 +113,6 @@ export class OpWorkPackagesCalendarService extends UntilDestroyedMixin { private readonly states = inject(States); - constructor( - private I18n:I18nService, - private configuration:ConfigurationService, - private sanitizer:DomSanitizer, - readonly injector:Injector, - readonly schemaCache:SchemaCacheService, - readonly toastService:ToastService, - readonly wpTableFilters:WorkPackageViewFiltersService, - readonly wpListService:WorkPackagesListService, - readonly wpListChecksumService:WorkPackagesListChecksumService, - readonly urlParamsHelper:UrlParamsHelperService, - readonly querySpace:IsolatedQuerySpace, - readonly apiV3Service:ApiV3Service, - readonly halResourceService:HalResourceService, - readonly timezoneService:TimezoneService, - readonly pathHelper:PathHelperService, - readonly halEditing:HalResourceEditingService, - readonly wpTableSelection:WorkPackageViewSelectionService, - readonly contextMenuService:OPContextMenuService, - readonly calendarService:OpCalendarService, - readonly weekdayService:WeekdayService, - readonly dayService:DayResourceService, - ) { - super(); - } - calendarOptions(additionalOptions:CalendarOptions):CalendarOptions { return { ...this.defaultOptions(), ...additionalOptions }; } diff --git a/frontend/src/app/features/calendar/te-calendar/te-calendar.component.ts b/frontend/src/app/features/calendar/te-calendar/te-calendar.component.ts index c47d86ceb7e..3d2f844f2b0 100644 --- a/frontend/src/app/features/calendar/te-calendar/te-calendar.component.ts +++ b/frontend/src/app/features/calendar/te-calendar/te-calendar.component.ts @@ -1,17 +1,4 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - Component, - ElementRef, - EventEmitter, - Injector, - Input, - OnDestroy, - Output, - SecurityContext, - ViewChild, - ViewEncapsulation, -} from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Injector, Input, OnDestroy, Output, SecurityContext, ViewChild, ViewEncapsulation, inject } from '@angular/core'; import { FullCalendarComponent } from '@fullcalendar/angular'; import { States } from 'core-app/core/states/states.service'; import moment, { Moment } from 'moment'; @@ -115,6 +102,25 @@ const ADD_ENTRY_PROHIBITED_CLASS_NAME = '-prohibited'; standalone: false, }) export class TimeEntryCalendarComponent implements AfterViewInit, OnDestroy { + readonly states = inject(States); + readonly apiV3Service = inject(ApiV3Service); + readonly $state = inject(StateService); + private element = inject(ElementRef); + readonly i18n = inject(I18nService); + readonly injector = inject(Injector); + readonly notifications = inject(HalResourceNotificationService); + private sanitizer = inject(DomSanitizer); + private configuration = inject(ConfigurationService); + private timezone = inject(TimezoneService); + private schemaCache = inject(SchemaCacheService); + private colors = inject(ColorsService); + private browserDetector = inject(BrowserDetector); + private calendar = inject(OpCalendarService); + readonly weekdayService = inject(WeekdayService); + readonly dayService = inject(DayResourceService); + readonly turboRequests = inject(TurboRequestsService); + readonly pathHelper = inject(PathHelperService); + @ViewChild(FullCalendarComponent) ucCalendar:FullCalendarComponent; @Input() projectIdentifier:string; @@ -202,27 +208,6 @@ export class TimeEntryCalendarComponent implements AfterViewInit, OnDestroy { }); } - constructor( - readonly states:States, - readonly apiV3Service:ApiV3Service, - readonly $state:StateService, - private element:ElementRef, - readonly i18n:I18nService, - readonly injector:Injector, - readonly notifications:HalResourceNotificationService, - private sanitizer:DomSanitizer, - private configuration:ConfigurationService, - private timezone:TimezoneService, - private schemaCache:SchemaCacheService, - private colors:ColorsService, - private browserDetector:BrowserDetector, - private calendar:OpCalendarService, - readonly weekdayService:WeekdayService, - readonly dayService:DayResourceService, - readonly turboRequests:TurboRequestsService, - readonly pathHelper:PathHelperService, - ) { } - ngAfterViewInit():void { document.addEventListener('dialog:close', this.closeDialogHandler); } diff --git a/frontend/src/app/features/calendar/wp-calendar/wp-calendar.component.ts b/frontend/src/app/features/calendar/wp-calendar/wp-calendar.component.ts index 7c60b93882b..5fe76aa445b 100644 --- a/frontend/src/app/features/calendar/wp-calendar/wp-calendar.component.ts +++ b/frontend/src/app/features/calendar/wp-calendar/wp-calendar.component.ts @@ -26,15 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - Input, - OnInit, - ViewChild, - ViewEncapsulation, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, ViewChild, ViewEncapsulation, inject } from '@angular/core'; import { CalendarOptions, DateSelectArg, @@ -103,6 +95,29 @@ import { TimezoneService } from 'core-app/core/datetime/timezone.service'; standalone: false, }) export class WorkPackagesCalendarComponent extends UntilDestroyedMixin implements OnInit { + readonly actions$ = inject(ActionsService); + readonly states = inject(States); + readonly wpTableFilters = inject(WorkPackageViewFiltersService); + readonly wpListService = inject(WorkPackagesListService); + readonly querySpace = inject(IsolatedQuerySpace); + readonly schemaCache = inject(SchemaCacheService); + private element = inject(ElementRef); + readonly i18n = inject(I18nService); + readonly toastService = inject(ToastService); + private sanitizer = inject(DomSanitizer); + private I18n = inject(I18nService); + private configuration = inject(ConfigurationService); + readonly calendar = inject(OpCalendarService); + readonly workPackagesCalendar = inject(OpWorkPackagesCalendarService); + readonly currentProject = inject(CurrentProjectService); + readonly halEditing = inject(HalResourceEditingService); + readonly halNotification = inject(HalResourceNotificationService); + readonly weekdayService = inject(WeekdayService); + readonly dayService = inject(DayResourceService); + readonly apiV3Service = inject(ApiV3Service); + readonly pathHelper = inject(PathHelperService); + readonly timezoneService = inject(TimezoneService); + @ViewChild(FullCalendarComponent) ucCalendar:FullCalendarComponent; @ViewChild('ucCalendar', { read: ElementRef }) @@ -123,33 +138,6 @@ export class WorkPackagesCalendarComponent extends UntilDestroyedMixin implement today: this.I18n.t('js.team_planner.today'), }; - constructor( - readonly actions$:ActionsService, - readonly states:States, - readonly wpTableFilters:WorkPackageViewFiltersService, - readonly wpListService:WorkPackagesListService, - readonly querySpace:IsolatedQuerySpace, - readonly schemaCache:SchemaCacheService, - private element:ElementRef, - readonly i18n:I18nService, - readonly toastService:ToastService, - private sanitizer:DomSanitizer, - private I18n:I18nService, - private configuration:ConfigurationService, - readonly calendar:OpCalendarService, - readonly workPackagesCalendar:OpWorkPackagesCalendarService, - readonly currentProject:CurrentProjectService, - readonly halEditing:HalResourceEditingService, - readonly halNotification:HalResourceNotificationService, - readonly weekdayService:WeekdayService, - readonly dayService:DayResourceService, - readonly apiV3Service:ApiV3Service, - readonly pathHelper:PathHelperService, - readonly timezoneService:TimezoneService, - ) { - super(); - } - ngOnInit():void { registerEffectCallbacks(this, this.untilDestroyed()); diff --git a/frontend/src/app/features/enterprise/enterprise-banner-frame.component.ts b/frontend/src/app/features/enterprise/enterprise-banner-frame.component.ts index 62aa7175b5c..971277fe9fd 100644 --- a/frontend/src/app/features/enterprise/enterprise-banner-frame.component.ts +++ b/frontend/src/app/features/enterprise/enterprise-banner-frame.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit, inject } from '@angular/core'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { BannersService } from 'core-app/core/enterprise/banners.service'; @@ -37,6 +37,9 @@ import { BannersService } from 'core-app/core/enterprise/banners.service'; standalone: false, }) export class EnterpriseBannerFrameComponent implements OnInit { + protected pathHelper = inject(PathHelperService); + protected banners = inject(BannersService); + @Input() public feature:string; @Input() public dismissable = false; @@ -45,12 +48,6 @@ export class EnterpriseBannerFrameComponent implements OnInit { frameURL:string; frameID:string; - constructor( - protected pathHelper:PathHelperService, - protected banners:BannersService, - ) { - } - ngOnInit() { this.visible = this.banners.showBannerFor(this.feature); this.frameURL = this.pathHelper.bannerFramePath(this.feature, this.dismissable); diff --git a/frontend/src/app/features/hal/services/hal-aware-error-handler.ts b/frontend/src/app/features/hal/services/hal-aware-error-handler.ts index 46075f34ca1..3cfe401f624 100644 --- a/frontend/src/app/features/hal/services/hal-aware-error-handler.ts +++ b/frontend/src/app/features/hal/services/hal-aware-error-handler.ts @@ -1,7 +1,4 @@ -import { - ErrorHandler, - Injectable, -} from '@angular/core'; +import { ErrorHandler, Injectable, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { ErrorResource } from 'core-app/features/hal/resources/error-resource'; @@ -14,14 +11,12 @@ interface RejectedPromise { @Injectable() export class HalAwareErrorHandler extends ErrorHandler { + private readonly I18n = inject(I18nService); + private text = { internal_error: this.I18n.t('js.error.internal'), }; - constructor(private readonly I18n:I18nService) { - super(); - } - public handleError(error:unknown):void { let message:string = this.text.internal_error; diff --git a/frontend/src/app/features/hal/services/hal-resource-notification.service.ts b/frontend/src/app/features/hal/services/hal-resource-notification.service.ts index dbfb1d344e5..709804c8077 100644 --- a/frontend/src/app/features/hal/services/hal-resource-notification.service.ts +++ b/frontend/src/app/features/hal/services/hal-resource-notification.service.ts @@ -28,7 +28,7 @@ import { StateService } from '@uirouter/core'; import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service'; -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { LoadingIndicatorService } from 'core-app/core/loading-indicator/loading-indicator.service'; import { ToastService } from 'core-app/shared/components/toaster/toast.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @@ -41,6 +41,8 @@ import { HalError } from 'core-app/features/hal/services/hal-error'; @Injectable() export class HalResourceNotificationService { + injector = inject(Injector); + @InjectField() protected I18n:I18nService; @InjectField() protected $state:StateService; @@ -53,9 +55,6 @@ export class HalResourceNotificationService { @InjectField() protected schemaCache:SchemaCacheService; - constructor(public injector:Injector) { - } - public showSave(resource:HalResource, isCreate = false) { const message:any = { message: this.I18n.t(`js.notice_successful_${isCreate ? 'create' : 'update'}`), diff --git a/frontend/src/app/features/hal/services/hal-resource.service.ts b/frontend/src/app/features/hal/services/hal-resource.service.ts index 1d583726870..2d86b4f0eb1 100644 --- a/frontend/src/app/features/hal/services/hal-resource.service.ts +++ b/frontend/src/app/features/hal/services/hal-resource.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http'; import { catchError, map } from 'rxjs/operators'; import { Observable, throwError } from 'rxjs'; @@ -59,17 +59,14 @@ interface ErrorWithType { @Injectable({ providedIn: 'root' }) export class HalResourceService { + readonly injector = inject(Injector); + readonly http = inject(HttpClient); + /** * List of all known hal resources, extendable. */ private config:Record = {}; - constructor( - readonly injector:Injector, - readonly http:HttpClient, - ) { - } - /** * Perform a HTTP request and return a HalResource promise. */ diff --git a/frontend/src/app/features/in-app-notifications/bell/in-app-notification-bell.component.ts b/frontend/src/app/features/in-app-notifications/bell/in-app-notification-bell.component.ts index 658aa051593..cf917c208a8 100644 --- a/frontend/src/app/features/in-app-notifications/bell/in-app-notification-bell.component.ts +++ b/frontend/src/app/features/in-app-notifications/bell/in-app-notification-bell.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, HostListener } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, HostListener, inject } from '@angular/core'; import { combineLatest, merge, Observable, timer } from 'rxjs'; import { filter, map, shareReplay, switchMap, throttleTime } from 'rxjs/operators'; import { ActiveWindowService } from 'core-app/core/active-window/active-window.service'; @@ -15,6 +15,12 @@ import { populateInputsFromDataset } from 'core-app/shared/components/dataset-in standalone: false, }) export class InAppNotificationBellComponent implements OnInit { + readonly elementRef = inject(ElementRef); + readonly storeService = inject(IanBellService); + readonly apiV3Service = inject(ApiV3Service); + readonly activeWindow = inject(ActiveWindowService); + readonly pathHelper = inject(PathHelperService); + @Input() interval = 50000; polling$:Observable; @@ -25,13 +31,7 @@ export class InAppNotificationBellComponent implements OnInit { public bellDisplayLimit = 99; - constructor( - readonly elementRef:ElementRef, - readonly storeService:IanBellService, - readonly apiV3Service:ApiV3Service, - readonly activeWindow:ActiveWindowService, - readonly pathHelper:PathHelperService, - ) { + constructor() { populateInputsFromDataset(this); } diff --git a/frontend/src/app/features/in-app-notifications/bell/state/ian-bell.service.ts b/frontend/src/app/features/in-app-notifications/bell/state/ian-bell.service.ts index 8c059b2665f..3263283204a 100644 --- a/frontend/src/app/features/in-app-notifications/bell/state/ian-bell.service.ts +++ b/frontend/src/app/features/in-app-notifications/bell/state/ian-bell.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { catchError, map, @@ -57,6 +57,9 @@ import { IanBellStore } from 'core-app/features/in-app-notifications/bell/state/ @Injectable({ providedIn: 'root' }) @EffectHandler export class IanBellService { + readonly actions$ = inject(ActionsService); + readonly resourceService = inject(InAppNotificationsResourceService); + readonly id = 'ian-bell'; readonly store = new IanBellStore(); @@ -65,10 +68,7 @@ export class IanBellService { unread$ = this.query.unread$; - constructor( - readonly actions$:ActionsService, - readonly resourceService:InAppNotificationsResourceService, - ) { + constructor() { this.query.unreadCountChanged$.subscribe((count) => { this.actions$.dispatch(notificationCountChanged({ origin: this.id, count })); }); diff --git a/frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.ts b/frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.ts index 0760d630ab2..8d882fa961d 100644 --- a/frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.ts +++ b/frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { filter, map } from 'rxjs/operators'; import { StateService } from '@uirouter/angular'; @@ -52,6 +52,17 @@ import { standalone: false, }) export class InAppNotificationCenterComponent implements OnInit { + readonly cdRef = inject(ChangeDetectorRef); + readonly elementRef = inject(ElementRef); + readonly I18n = inject(I18nService); + readonly storeService = inject(IanCenterService); + readonly bellService = inject(IanBellService); + readonly urlParams = inject(UrlParamsService); + readonly state = inject(StateService); + readonly apiV3 = inject(ApiV3Service); + readonly pathService = inject(PathHelperService); + readonly colorsService = inject(ColorsService); + maxSize = NOTIFICATIONS_MAX_SIZE; hasMoreThanPageSize$ = this.storeService.hasMoreThanPageSize$; @@ -141,20 +152,6 @@ export class InAppNotificationCenterComponent implements OnInit { protected readonly idFromLink = idFromLink; - constructor( - readonly cdRef:ChangeDetectorRef, - readonly elementRef:ElementRef, - readonly I18n:I18nService, - readonly storeService:IanCenterService, - readonly bellService:IanBellService, - readonly urlParams:UrlParamsService, - readonly state:StateService, - readonly apiV3:ApiV3Service, - readonly pathService:PathHelperService, - readonly colorsService:ColorsService, - ) { - } - ngOnInit():void { const facet = this.urlParams.get('facet') || 'unread'; this.storeService.setFacet(facet as 'unread'|'all'); diff --git a/frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts b/frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts index d6361f137c0..31709f6a2fb 100644 --- a/frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts +++ b/frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { debounceTime, defaultIfEmpty, distinctUntilChanged, map, mapTo, switchMap, take, tap } from 'rxjs/operators'; import { forkJoin, from, Observable, Subject } from 'rxjs'; import { ID, Query } from '@datorama/akita'; @@ -71,6 +71,18 @@ export interface INotificationPageQueryParameters { @Injectable({ providedIn: 'root' }) @EffectHandler export class IanCenterService extends UntilDestroyedMixin { + readonly I18n = inject(I18nService); + readonly injector = inject(Injector); + readonly resourceService = inject(InAppNotificationsResourceService); + readonly actions$ = inject(ActionsService); + readonly apiV3Service = inject(ApiV3Service); + readonly toastService = inject(ToastService); + readonly urlParams = inject(UrlParamsService); + readonly state = inject(StateService); + readonly deviceService = inject(DeviceService); + readonly pathHelper = inject(PathHelperService); + readonly ianBellService = inject(IanBellService); + readonly id = 'ian-center'; readonly store = new IanCenterStore(); @@ -182,19 +194,7 @@ export class IanCenterService extends UntilDestroyedMixin { selectedWorkPackage$ = this.urlParams.pathMatching$(/\/details\/(\d+)/); - constructor( - readonly I18n:I18nService, - readonly injector:Injector, - readonly resourceService:InAppNotificationsResourceService, - readonly actions$:ActionsService, - readonly apiV3Service:ApiV3Service, - readonly toastService:ToastService, - readonly urlParams:UrlParamsService, - readonly state:StateService, - readonly deviceService:DeviceService, - readonly pathHelper:PathHelperService, - readonly ianBellService:IanBellService, - ) { + constructor() { super(); this.reload.subscribe(); diff --git a/frontend/src/app/features/in-app-notifications/entry/actors-line/in-app-notification-actors-line.component.ts b/frontend/src/app/features/in-app-notifications/entry/actors-line/in-app-notification-actors-line.component.ts index 9ceab9a5b41..df7f859d9f1 100644 --- a/frontend/src/app/features/in-app-notifications/entry/actors-line/in-app-notification-actors-line.component.ts +++ b/frontend/src/app/features/in-app-notifications/entry/actors-line/in-app-notification-actors-line.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation, inject } from '@angular/core'; import { DeviceService } from 'core-app/core/browser/device.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { INotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model'; @@ -13,6 +13,9 @@ import { PrincipalLike } from 'core-app/shared/components/principal/principal-ty standalone: false, }) export class InAppNotificationActorsLineComponent implements OnInit { + readonly deviceService = inject(DeviceService); + private I18n = inject(I18nService); + @HostBinding('class.op-ian-actors') className = true; @Input() aggregatedNotifications:INotification[]; @@ -34,11 +37,6 @@ export class InAppNotificationActorsLineComponent implements OnInit { mark_as_read: this.I18n.t('js.notifications.center.mark_as_read'), }; - constructor( - readonly deviceService:DeviceService, - private I18n:I18nService, - ) { } - ngOnInit():void { // Don't show the actor if the first item is actor-less (date alert) if (this.notification._links.actor) { diff --git a/frontend/src/app/features/in-app-notifications/entry/date-alert/in-app-notification-date-alert.component.ts b/frontend/src/app/features/in-app-notifications/entry/date-alert/in-app-notification-date-alert.component.ts index bf7cf86a5cd..0ef6d1b36e6 100644 --- a/frontend/src/app/features/in-app-notifications/entry/date-alert/in-app-notification-date-alert.component.ts +++ b/frontend/src/app/features/in-app-notifications/entry/date-alert/in-app-notification-date-alert.component.ts @@ -1,11 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - HostBinding, - Input, - OnInit, - ViewEncapsulation, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation, inject } from '@angular/core'; import { TimezoneService } from 'core-app/core/datetime/timezone.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { IInAppNotificationDetailsAttribute, INotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model'; @@ -20,6 +13,9 @@ import moment, { Moment } from 'moment'; standalone: false, }) export class InAppNotificationDateAlertComponent implements OnInit { + private I18n = inject(I18nService); + private timezoneService = inject(TimezoneService); + @Input() aggregatedNotifications:INotification[]; @HostBinding('class.op-ian-date-alert') className = true; @@ -49,11 +45,6 @@ export class InAppNotificationDateAlertComponent implements OnInit { note: '', // date alerts do not have notes }; - constructor( - private I18n:I18nService, - private timezoneService:TimezoneService, - ) { } - ngOnInit():void { // Find the most important date alert const interestingAlert = this.deriveMostRelevantAlert(this.aggregatedNotifications); diff --git a/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts b/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts index bf3b91a1cfd..57aae5371e8 100644 --- a/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts +++ b/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation, inject } from '@angular/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @@ -23,6 +23,14 @@ import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destr standalone: false, }) export class InAppNotificationEntryComponent extends UntilDestroyedMixin implements OnInit { + readonly apiV3Service = inject(ApiV3Service); + readonly I18n = inject(I18nService); + readonly storeService = inject(IanCenterService); + readonly timezoneService = inject(TimezoneService); + readonly pathHelper = inject(PathHelperService); + readonly deviceService = inject(DeviceService); + readonly urlParams = inject(UrlParamsService); + @HostBinding('class.op-ian-item') className = true; @Input() notification:INotification; @@ -55,18 +63,6 @@ export class InAppNotificationEntryComponent extends UntilDestroyedMixin impleme workPackageId:string|null; - constructor( - readonly apiV3Service:ApiV3Service, - readonly I18n:I18nService, - readonly storeService:IanCenterService, - readonly timezoneService:TimezoneService, - readonly pathHelper:PathHelperService, - readonly deviceService:DeviceService, - readonly urlParams:UrlParamsService, - ) { - super(); - } - ngOnInit():void { const href = this.notification._links.resource?.href; this.workPackageId = href && HalResource.matchFromLink(href, 'work_packages'); diff --git a/frontend/src/app/features/in-app-notifications/entry/relative-time/in-app-notification-relative-time.component.ts b/frontend/src/app/features/in-app-notifications/entry/relative-time/in-app-notification-relative-time.component.ts index a2db83bca92..1c387e3113b 100644 --- a/frontend/src/app/features/in-app-notifications/entry/relative-time/in-app-notification-relative-time.component.ts +++ b/frontend/src/app/features/in-app-notifications/entry/relative-time/in-app-notification-relative-time.component.ts @@ -1,10 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - Input, - OnInit, - ViewEncapsulation, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit, ViewEncapsulation, inject } from '@angular/core'; import { TimezoneService } from 'core-app/core/datetime/timezone.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { INotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model'; @@ -20,6 +14,9 @@ import { distinctUntilChanged, map } from 'rxjs/operators'; standalone: false, }) export class InAppNotificationRelativeTimeComponent implements OnInit { + private I18n = inject(I18nService); + private timezoneService = inject(TimezoneService); + @Input() notification:INotification; @Input() hasActorByLine = true; @@ -37,11 +34,6 @@ export class InAppNotificationRelativeTimeComponent implements OnInit { ), }; - constructor( - private I18n:I18nService, - private timezoneService:TimezoneService, - ) { } - ngOnInit():void { this.buildTime(); } diff --git a/frontend/src/app/features/in-app-notifications/entry/reminder-alert/in-app-notification-reminder-alert.component.ts b/frontend/src/app/features/in-app-notifications/entry/reminder-alert/in-app-notification-reminder-alert.component.ts index 65e0354fa10..32bcd973b38 100644 --- a/frontend/src/app/features/in-app-notifications/entry/reminder-alert/in-app-notification-reminder-alert.component.ts +++ b/frontend/src/app/features/in-app-notifications/entry/reminder-alert/in-app-notification-reminder-alert.component.ts @@ -1,11 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - HostBinding, - Input, - OnInit, - ViewEncapsulation, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { IInAppNotificationDetailsResource, INotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model'; @@ -18,6 +11,8 @@ import { IInAppNotificationDetailsResource, INotification } from 'core-app/core/ standalone: false, }) export class InAppNotificationReminderAlertComponent implements OnInit { + private I18n = inject(I18nService); + @Input() aggregatedNotifications:INotification[]; @HostBinding('class.op-ian-reminder-alert') className = true; @@ -27,10 +22,6 @@ export class InAppNotificationReminderAlertComponent implements OnInit { hasDateAlert = false; dateAlerts:INotification[] = []; - constructor( - private I18n:I18nService, - ) { } - ngOnInit():void { this.reminderAlert = this.deriveMostRecentReminder(this.aggregatedNotifications); this.reminderNote = this.extractReminderNoteValue(this.reminderAlert._embedded.details); diff --git a/frontend/src/app/features/job-status/job-status-modal.service.ts b/frontend/src/app/features/job-status/job-status-modal.service.ts index b5a22ac57b3..a3db171ad24 100644 --- a/frontend/src/app/features/job-status/job-status-modal.service.ts +++ b/frontend/src/app/features/job-status/job-status-modal.service.ts @@ -1,13 +1,12 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { TurboRequestsService } from 'core-app/core/turbo/turbo-requests.service'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; @Injectable({ providedIn: 'root' }) export class JobStatusModalService { - constructor( - protected pathHelper:PathHelperService, - protected turboRequests:TurboRequestsService, - ) {} + protected pathHelper = inject(PathHelperService); + protected turboRequests = inject(TurboRequestsService); + public show(jobId:string):void { void this.turboRequests.requestStream(this.jobModalUrl(jobId)); diff --git a/frontend/src/app/features/plugins/openproject-plugins.module.ts b/frontend/src/app/features/plugins/openproject-plugins.module.ts index 04c3ccbafef..f5ee7e0ffe4 100644 --- a/frontend/src/app/features/plugins/openproject-plugins.module.ts +++ b/frontend/src/app/features/plugins/openproject-plugins.module.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ Ng1FieldControlsWrapper, -import { Injector, NgModule } from '@angular/core'; +import { Injector, NgModule, inject } from '@angular/core'; import { HookService } from 'core-app/features/plugins/hook-service'; import { OpenProjectPluginContext } from 'core-app/features/plugins/plugin-context'; import { debugLog } from 'core-app/shared/helpers/debug_output'; @@ -37,7 +37,9 @@ import { debugLog } from 'core-app/shared/helpers/debug_output'; ], }) export class OpenprojectPluginsModule { - constructor(injector:Injector) { + constructor() { + const injector = inject(Injector); + debugLog('Registering OpenProject plugin context'); const pluginContext = new OpenProjectPluginContext(injector); window.OpenProject.pluginContext.putValue(pluginContext); diff --git a/frontend/src/app/features/team-planner/team-planner/add-work-packages/add-existing-pane.component.ts b/frontend/src/app/features/team-planner/team-planner/add-work-packages/add-existing-pane.component.ts index 6ef92b88a1b..9d7fee04bb6 100644 --- a/frontend/src/app/features/team-planner/team-planner/add-work-packages/add-existing-pane.component.ts +++ b/frontend/src/app/features/team-planner/team-planner/add-work-packages/add-existing-pane.component.ts @@ -1,12 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - HostBinding, - OnDestroy, - OnInit, - ViewChild, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, HostBinding, OnDestroy, OnInit, ViewChild, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { imagePath } from 'core-app/shared/helpers/images/path-helper'; import { @@ -45,6 +37,17 @@ import { OpWorkPackagesCalendarService } from 'core-app/features/calendar/op-wor standalone: false, }) export class AddExistingPaneComponent extends UntilDestroyedMixin implements OnInit, OnDestroy { + private readonly querySpace = inject(IsolatedQuerySpace); + private I18n = inject(I18nService); + private readonly apiV3Service = inject(ApiV3Service); + private readonly notificationService = inject(WorkPackageNotificationService); + private readonly currentProject = inject(CurrentProjectService); + private readonly urlParamsHelper = inject(UrlParamsHelperService); + private readonly workPackagesCalendar = inject(OpWorkPackagesCalendarService); + private readonly calendarDrag = inject(CalendarDragDropService); + private readonly actions$ = inject(ActionsService); + private readonly wpFilters = inject(WorkPackageViewFiltersService); + @HostBinding('class.op-add-existing-pane') className = true; @ViewChild('container') container:ElementRef; @@ -109,21 +112,6 @@ export class AddExistingPaneComponent extends UntilDestroyedMixin implements OnI empty_state: imagePath('team-planner/add-existing-pane--empty-state.gif'), }; - constructor( - private readonly querySpace:IsolatedQuerySpace, - private I18n:I18nService, - private readonly apiV3Service:ApiV3Service, - private readonly notificationService:WorkPackageNotificationService, - private readonly currentProject:CurrentProjectService, - private readonly urlParamsHelper:UrlParamsHelperService, - private readonly workPackagesCalendar:OpWorkPackagesCalendarService, - private readonly calendarDrag:CalendarDragDropService, - private readonly actions$:ActionsService, - private readonly wpFilters:WorkPackageViewFiltersService, - ) { - super(); - } - ngOnInit():void { combineLatest([ this diff --git a/frontend/src/app/features/team-planner/team-planner/assignee/add-assignee.component.ts b/frontend/src/app/features/team-planner/team-planner/assignee/add-assignee.component.ts index 7bab1282a29..28f2c7a4343 100644 --- a/frontend/src/app/features/team-planner/team-planner/assignee/add-assignee.component.ts +++ b/frontend/src/app/features/team-planner/team-planner/assignee/add-assignee.component.ts @@ -26,15 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - EventEmitter, - Injector, - Input, - Output, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Injector, Input, Output, inject } from '@angular/core'; import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @@ -54,24 +46,22 @@ import { WorkPackageViewFiltersService } from 'core-app/features/work-packages/r standalone: false, }) export class AddAssigneeComponent { + protected elementRef = inject(ElementRef); + protected halResourceService = inject(HalResourceService); + protected I18n = inject(I18nService); + protected halNotification = inject(HalResourceNotificationService); + readonly pathHelper = inject(PathHelperService); + readonly apiV3Service = inject(ApiV3Service); + readonly injector = inject(Injector); + readonly currentProjectService = inject(CurrentProjectService); + readonly wpTableFilters = inject(WorkPackageViewFiltersService); + @Output() public selectAssignee = new EventEmitter(); @Input() alreadySelected:string[] = []; public getOptionsFn = (query:string):Observable => this.autocomplete(query); - constructor( - protected elementRef:ElementRef, - protected halResourceService:HalResourceService, - protected I18n:I18nService, - protected halNotification:HalResourceNotificationService, - readonly pathHelper:PathHelperService, - readonly apiV3Service:ApiV3Service, - readonly injector:Injector, - readonly currentProjectService:CurrentProjectService, - readonly wpTableFilters:WorkPackageViewFiltersService, - ) { } - public autocomplete(term:string|null):Observable { const filters = new ApiV3FilterBuilder(); diff --git a/frontend/src/app/features/team-planner/team-planner/calendar-drag-drop.service.ts b/frontend/src/app/features/team-planner/team-planner/calendar-drag-drop.service.ts index 85586a4d20d..9a337c5efc5 100644 --- a/frontend/src/app/features/team-planner/team-planner/calendar-drag-drop.service.ts +++ b/frontend/src/app/features/team-planner/team-planner/calendar-drag-drop.service.ts @@ -1,7 +1,4 @@ -import { - ElementRef, - Injectable, -} from '@angular/core'; +import { ElementRef, Injectable, inject } from '@angular/core'; import { ThirdPartyDraggable } from '@fullcalendar/interaction'; import { DragMetaInput } from '@fullcalendar/common'; import dragula, { Drake } from 'dragula'; @@ -15,6 +12,11 @@ import moment from 'moment-timezone'; @Injectable() export class CalendarDragDropService { + readonly authorisation = inject(AuthorisationService); + readonly schemaCache = inject(SchemaCacheService); + readonly workPackagesCalendarService = inject(OpWorkPackagesCalendarService); + readonly I18n = inject(I18nService); + drake:Drake; draggableWorkPackages$ = new BehaviorSubject([]); @@ -28,14 +30,6 @@ export class CalendarDragDropService { }, }; - constructor( - readonly authorisation:AuthorisationService, - readonly schemaCache:SchemaCacheService, - readonly workPackagesCalendarService:OpWorkPackagesCalendarService, - readonly I18n:I18nService, - ) { - } - destroyDrake():void { if (this.drake) { this.drake.destroy(); diff --git a/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts b/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts index 569a70aac64..ccdad53dcb9 100644 --- a/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts +++ b/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts @@ -26,17 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - HostListener, - Injector, - OnDestroy, - OnInit, - TemplateRef, - ViewChild, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Injector, OnDestroy, OnInit, TemplateRef, ViewChild, inject } from '@angular/core'; import { CalendarOptions, DateSelectArg, @@ -143,6 +133,27 @@ export type TeamPlannerViewOptions = Record):Promise { return this.$state.go(route, params); } diff --git a/frontend/src/app/features/work-packages/components/edit-actions-bar/wp-edit-actions-bar.component.ts b/frontend/src/app/features/work-packages/components/edit-actions-bar/wp-edit-actions-bar.component.ts index 17ab41212f4..65d3e74def5 100644 --- a/frontend/src/app/features/work-packages/components/edit-actions-bar/wp-edit-actions-bar.component.ts +++ b/frontend/src/app/features/work-packages/components/edit-actions-bar/wp-edit-actions-bar.component.ts @@ -26,9 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Output, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Output, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { EditFormComponent } from 'core-app/shared/components/fields/edit/edit-form/edit-form.component'; @@ -39,6 +37,10 @@ import { EditFormComponent } from 'core-app/shared/components/fields/edit/edit-f standalone: false, }) export class WorkPackageEditActionsBarComponent { + private I18n = inject(I18nService); + private editForm = inject(EditFormComponent); + private cdRef = inject(ChangeDetectorRef); + @Output() public onSave = new EventEmitter(); @Output() public onCancel = new EventEmitter(); @@ -50,11 +52,6 @@ export class WorkPackageEditActionsBarComponent { cancel: this.I18n.t('js.button_cancel'), }; - constructor(private I18n:I18nService, - private editForm:EditFormComponent, - private cdRef:ChangeDetectorRef) { - } - public set saving(active:boolean) { this._saving = active; this.cdRef.detectChanges(); diff --git a/frontend/src/app/features/work-packages/components/filters/abstract-filter-date-time-value/abstract-filter-date-time-value.controller.ts b/frontend/src/app/features/work-packages/components/filters/abstract-filter-date-time-value/abstract-filter-date-time-value.controller.ts index 6b16d361750..088992948d9 100644 --- a/frontend/src/app/features/work-packages/components/filters/abstract-filter-date-time-value/abstract-filter-date-time-value.controller.ts +++ b/frontend/src/app/features/work-packages/components/filters/abstract-filter-date-time-value/abstract-filter-date-time-value.controller.ts @@ -28,21 +28,17 @@ import { Moment } from 'moment'; import { I18nService } from 'core-app/core/i18n/i18n.service'; -import { OnInit, Directive } from '@angular/core'; +import { OnInit, Directive, inject } from '@angular/core'; import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; import { TimezoneService } from 'core-app/core/datetime/timezone.service'; import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/query-filter-instance-resource'; @Directive() export abstract class AbstractDateTimeValueController extends UntilDestroyedMixin implements OnInit { - public filter:QueryFilterInstanceResource; + protected I18n = inject(I18nService); + protected timezoneService = inject(TimezoneService); - constructor( - protected I18n:I18nService, - protected timezoneService:TimezoneService, - ) { - super(); - } + public filter:QueryFilterInstanceResource; ngOnInit() { this.filter.values = (this.filter.values as string[]).filter((value) => (value === '' || this.timezoneService.isValidISODateTime(value))); diff --git a/frontend/src/app/features/work-packages/components/filters/filter-boolean-value/filter-boolean-value.component.ts b/frontend/src/app/features/work-packages/components/filters/filter-boolean-value/filter-boolean-value.component.ts index 4d1319a13f7..0954036bbd3 100644 --- a/frontend/src/app/features/work-packages/components/filters/filter-boolean-value/filter-boolean-value.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/filter-boolean-value/filter-boolean-value.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core'; +import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/query-filter-instance-resource'; @@ -38,15 +38,14 @@ import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/que standalone: false, }) export class FilterBooleanValueComponent { + readonly I18n = inject(I18nService); + @Input() public shouldFocus = false; @Input() public filter:QueryFilterInstanceResource; @Output() public filterChanged = new EventEmitter(); - constructor(readonly I18n:I18nService) { - } - public get value():HalResource | string { // Boolean fields should be initialized as true by default if (this.filter.values.length === 0) { diff --git a/frontend/src/app/features/work-packages/components/filters/filter-container/filter-container.directive.ts b/frontend/src/app/features/work-packages/components/filters/filter-container/filter-container.directive.ts index 412cafc0393..66b62172957 100644 --- a/frontend/src/app/features/work-packages/components/filters/filter-container/filter-container.directive.ts +++ b/frontend/src/app/features/work-packages/components/filters/filter-container/filter-container.directive.ts @@ -26,15 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnDestroy, - OnInit, - Output, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, Output, inject } from '@angular/core'; import { WorkPackageViewFiltersService, } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service'; @@ -55,6 +47,11 @@ import { WorkPackagesListService } from 'core-app/features/work-packages/compone standalone: false, }) export class WorkPackageFilterContainerComponent extends UntilDestroyedMixin implements OnInit, OnDestroy { + readonly wpTableFilters = inject(WorkPackageViewFiltersService); + readonly cdRef = inject(ChangeDetectorRef); + readonly wpFiltersService = inject(WorkPackageFiltersService); + readonly wpListService = inject(WorkPackagesListService); + @Input() showFilterButton = false; @Input() filterButtonText:string = I18n.t('js.button_filter'); @@ -67,12 +64,7 @@ export class WorkPackageFilterContainerComponent extends UntilDestroyedMixin imp public loaded = false; - constructor( - readonly wpTableFilters:WorkPackageViewFiltersService, - readonly cdRef:ChangeDetectorRef, - readonly wpFiltersService:WorkPackageFiltersService, - readonly wpListService:WorkPackagesListService, - ) { + constructor() { super(); this.visible$ = this.wpFiltersService.observeUntil(componentDestroyed(this)); } diff --git a/frontend/src/app/features/work-packages/components/filters/filter-date-time-value/filter-date-time-value.component.ts b/frontend/src/app/features/work-packages/components/filters/filter-date-time-value/filter-date-time-value.component.ts index feec49ccfae..f249c5a0aad 100644 --- a/frontend/src/app/features/work-packages/components/filters/filter-date-time-value/filter-date-time-value.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/filter-date-time-value/filter-date-time-value.component.ts @@ -26,20 +26,11 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - Input, - HostBinding, - OnInit, - Output, -} from '@angular/core'; -import { I18nService } from 'core-app/core/i18n/i18n.service'; +import { ChangeDetectionStrategy, Component, Input, HostBinding, OnInit, Output } from '@angular/core'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { DebouncedEventEmitter } from 'core-app/shared/helpers/rxjs/debounced-event-emitter'; import { Moment } from 'moment'; import { componentDestroyed } from '@w11k/ngx-componentdestroyed'; -import { TimezoneService } from 'core-app/core/datetime/timezone.service'; import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/query-filter-instance-resource'; import { AbstractDateTimeValueController } from '../abstract-filter-date-time-value/abstract-filter-date-time-value.controller'; @@ -65,11 +56,6 @@ export class FilterDateTimeValueComponent extends AbstractDateTimeValueControlle @Output() public filterChanged = new DebouncedEventEmitter(componentDestroyed(this)); - constructor(readonly I18n:I18nService, - readonly timezoneService:TimezoneService) { - super(I18n, timezoneService); - } - public get value():HalResource|string { return this.filter.values[0]; } diff --git a/frontend/src/app/features/work-packages/components/filters/filter-date-times-value/filter-date-times-value.component.ts b/frontend/src/app/features/work-packages/components/filters/filter-date-times-value/filter-date-times-value.component.ts index dcc3fccaf18..fb175587fef 100644 --- a/frontend/src/app/features/work-packages/components/filters/filter-date-times-value/filter-date-times-value.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/filter-date-times-value/filter-date-times-value.component.ts @@ -27,18 +27,9 @@ //++ import { Moment } from 'moment'; -import { - ChangeDetectionStrategy, - Component, - HostBinding, - Input, - OnInit, - Output, -} from '@angular/core'; -import { I18nService } from 'core-app/core/i18n/i18n.service'; +import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, Output } from '@angular/core'; import { DebouncedEventEmitter } from 'core-app/shared/helpers/rxjs/debounced-event-emitter'; import { componentDestroyed } from '@w11k/ngx-componentdestroyed'; -import { TimezoneService } from 'core-app/core/datetime/timezone.service'; import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/query-filter-instance-resource'; import { AbstractDateTimeValueController } from '../abstract-filter-date-time-value/abstract-filter-date-time-value.controller'; import { validDate } from 'core-app/shared/components/datepicker/helpers/date-modal.helpers'; @@ -69,13 +60,6 @@ export class FilterDateTimesValueComponent extends AbstractDateTimeValueControll spacer: this.I18n.t('js.filter.value_spacer'), }; - constructor( - readonly I18n:I18nService, - readonly timezoneService:TimezoneService, - ) { - super(I18n, timezoneService); - } - public get begin():string { return (this.filter.values[0] || '') as string; } diff --git a/frontend/src/app/features/work-packages/components/filters/filter-date-value/filter-date-value.component.ts b/frontend/src/app/features/work-packages/components/filters/filter-date-value/filter-date-value.component.ts index b3fc174bdf4..ddc6e34ef13 100644 --- a/frontend/src/app/features/work-packages/components/filters/filter-date-value/filter-date-value.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/filter-date-value/filter-date-value.component.ts @@ -26,13 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - HostBinding, - Input, - Output, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostBinding, Input, Output, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { DebouncedEventEmitter } from 'core-app/shared/helpers/rxjs/debounced-event-emitter'; import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; @@ -51,6 +45,9 @@ import moment from 'moment-timezone'; changeDetection: ChangeDetectionStrategy.Default, }) export class FilterDateValueComponent extends UntilDestroyedMixin { + readonly timezoneService = inject(TimezoneService); + readonly I18n = inject(I18nService); + @HostBinding('id') get id() { return `div-values-${this.filter.id}`; } @@ -61,11 +58,6 @@ export class FilterDateValueComponent extends UntilDestroyedMixin { @Output() public filterChanged = new DebouncedEventEmitter(componentDestroyed(this)); - constructor(readonly timezoneService:TimezoneService, - readonly I18n:I18nService) { - super(); - } - public get value():string { return this.filter.values[0] as string; } diff --git a/frontend/src/app/features/work-packages/components/filters/filter-dates-value/filter-dates-value.component.ts b/frontend/src/app/features/work-packages/components/filters/filter-dates-value/filter-dates-value.component.ts index 2812c33b302..3286d1e3465 100644 --- a/frontend/src/app/features/work-packages/components/filters/filter-dates-value/filter-dates-value.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/filter-dates-value/filter-dates-value.component.ts @@ -26,13 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - HostBinding, - Input, - Output, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostBinding, Input, Output, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { DebouncedEventEmitter } from 'core-app/shared/helpers/rxjs/debounced-event-emitter'; import moment from 'moment'; @@ -51,6 +45,9 @@ import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/que changeDetection: ChangeDetectionStrategy.Default, }) export class FilterDatesValueComponent extends UntilDestroyedMixin { + readonly timezoneService = inject(TimezoneService); + readonly I18n = inject(I18nService); + @HostBinding('id') get id() { return `div-values-${this.filter.id}`; } @@ -67,13 +64,6 @@ export class FilterDatesValueComponent extends UntilDestroyedMixin { spacer: this.I18n.t('js.filter.value_spacer'), }; - constructor( - readonly timezoneService:TimezoneService, - readonly I18n:I18nService, - ) { - super(); - } - public get value():string[] { return (this.filter.values || []) as string[]; } diff --git a/frontend/src/app/features/work-packages/components/filters/filter-integer-value/filter-integer-value.component.ts b/frontend/src/app/features/work-packages/components/filters/filter-integer-value/filter-integer-value.component.ts index 2002cde4d30..9a7c9b7828d 100644 --- a/frontend/src/app/features/work-packages/components/filters/filter-integer-value/filter-integer-value.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/filter-integer-value/filter-integer-value.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, Output, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { DebouncedEventEmitter } from 'core-app/shared/helpers/rxjs/debounced-event-emitter'; import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; @@ -45,17 +45,15 @@ import { QueryFilterResource } from 'core-app/features/hal/resources/query-filte changeDetection: ChangeDetectionStrategy.Default, }) export class FilterIntegerValueComponent extends UntilDestroyedMixin { + readonly I18n = inject(I18nService); + readonly schemaCache = inject(SchemaCacheService); + @Input() public shouldFocus = false; @Input() public filter:QueryFilterInstanceResource; @Output() public filterChanged = new DebouncedEventEmitter(componentDestroyed(this)); - constructor(readonly I18n:I18nService, - readonly schemaCache:SchemaCacheService) { - super(); - } - public get value() { return parseInt(this.filter.values[0] as string); } diff --git a/frontend/src/app/features/work-packages/components/filters/filter-project/filter-project.component.ts b/frontend/src/app/features/work-packages/components/filters/filter-project/filter-project.component.ts index 19043245cfa..c0ea251b2cb 100644 --- a/frontend/src/app/features/work-packages/components/filters/filter-project/filter-project.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/filter-project/filter-project.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input, OnInit, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit, Output, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { DebouncedEventEmitter } from 'core-app/shared/helpers/rxjs/debounced-event-emitter'; @@ -48,6 +48,10 @@ import { IAPIFilter } from 'core-app/shared/components/autocompleter/op-autocomp standalone: false, }) export class FilterProjectComponent extends UntilDestroyedMixin implements OnInit { + readonly I18n = inject(I18nService); + readonly apiV3Service = inject(ApiV3Service); + readonly currentProjectService = inject(CurrentProjectService); + @Input() public shouldFocus = false; @Input() public filter:QueryFilterInstanceResource; @@ -56,14 +60,6 @@ export class FilterProjectComponent extends UntilDestroyedMixin implements OnIni additionalProjectApiFilters:IAPIFilter[] = []; - constructor( - readonly I18n:I18nService, - readonly apiV3Service:ApiV3Service, - readonly currentProjectService:CurrentProjectService, - ) { - super(); - } - ngOnInit():void { const projectID = this.currentProjectService.id; diff --git a/frontend/src/app/features/work-packages/components/filters/filter-searchable-multiselect-value/filter-searchable-multiselect-value.component.ts b/frontend/src/app/features/work-packages/components/filters/filter-searchable-multiselect-value/filter-searchable-multiselect-value.component.ts index 2aea22095fe..de1a795993a 100644 --- a/frontend/src/app/features/work-packages/components/filters/filter-searchable-multiselect-value/filter-searchable-multiselect-value.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/filter-searchable-multiselect-value/filter-searchable-multiselect-value.component.ts @@ -10,16 +10,7 @@ import { withLatestFrom, } from 'rxjs/operators'; import { take } from 'rxjs/internal/operators/take'; -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnInit, - Output, - ViewChild, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild, inject } from '@angular/core'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { ApiV3FilterBuilder } from 'core-app/shared/helpers/api-v3/api-v3-filter-builder'; @@ -44,6 +35,14 @@ import { MAGIC_FILTER_AUTOCOMPLETE_PAGE_SIZE } from 'core-app/core/apiv3/helpers standalone: false, }) export class FilterSearchableMultiselectValueComponent extends UntilDestroyedMixin implements OnInit { + readonly halResourceService = inject(HalResourceService); + readonly apiV3Service = inject(ApiV3Service); + readonly cdRef = inject(ChangeDetectorRef); + readonly I18n = inject(I18nService); + protected currentProject = inject(CurrentProjectService); + protected currentUser = inject(CurrentUserService); + readonly halNotification = inject(HalResourceNotificationService); + @Input() public filter:QueryFilterInstanceResource; @Input() public shouldFocus = false; @@ -85,18 +84,6 @@ export class FilterSearchableMultiselectValueComponent extends UntilDestroyedMix @ViewChild('ngSelectInstance', { static: true }) ngSelectInstance:NgSelectComponent; - constructor( - readonly halResourceService:HalResourceService, - readonly apiV3Service:ApiV3Service, - readonly cdRef:ChangeDetectorRef, - readonly I18n:I18nService, - protected currentProject:CurrentProjectService, - protected currentUser:CurrentUserService, - readonly halNotification:HalResourceNotificationService, - ) { - super(); - } - ngOnInit():void { if (this.filter.id === 'id') { this.resourceType = 'work_packages'; diff --git a/frontend/src/app/features/work-packages/components/filters/filter-string-value/filter-string-value.component.ts b/frontend/src/app/features/work-packages/components/filters/filter-string-value/filter-string-value.component.ts index 64b35623ffe..d8b6622b496 100644 --- a/frontend/src/app/features/work-packages/components/filters/filter-string-value/filter-string-value.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/filter-string-value/filter-string-value.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, Output, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { DebouncedEventEmitter } from 'core-app/shared/helpers/rxjs/debounced-event-emitter'; @@ -44,6 +44,8 @@ import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/que changeDetection: ChangeDetectionStrategy.Default, }) export class FilterStringValueComponent extends UntilDestroyedMixin { + readonly I18n = inject(I18nService); + @Input() public shouldFocus = false; @Input() public filter:QueryFilterInstanceResource; @@ -54,10 +56,6 @@ export class FilterStringValueComponent extends UntilDestroyedMixin { enter_text: this.I18n.t('js.work_packages.description_enter_text'), }; - constructor(readonly I18n:I18nService) { - super(); - } - public get value():HalResource|string { return this.filter.values[0]; } diff --git a/frontend/src/app/features/work-packages/components/filters/filter-toggled-multiselect-value/filter-toggled-multiselect-value.component.ts b/frontend/src/app/features/work-packages/components/filters/filter-toggled-multiselect-value/filter-toggled-multiselect-value.component.ts index 0f0ab32b317..7626c72b92d 100644 --- a/frontend/src/app/features/work-packages/components/filters/filter-toggled-multiselect-value/filter-toggled-multiselect-value.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/filter-toggled-multiselect-value/filter-toggled-multiselect-value.component.ts @@ -27,17 +27,7 @@ //++ import { HalResource } from 'core-app/features/hal/resources/hal-resource'; -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnInit, - Output, - ViewChild, -} from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { HalResourceSortingService } from 'core-app/features/hal/services/hal-resource-sorting.service'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; @@ -55,6 +45,14 @@ import { compareByHref } from 'core-app/shared/helpers/angular/tracking-function standalone: false, }) export class FilterToggledMultiselectValueComponent implements OnInit, AfterViewInit { + readonly halResourceService = inject(HalResourceService); + readonly halSorting = inject(HalResourceSortingService); + readonly PathHelper = inject(PathHelperService); + readonly apiV3Service = inject(ApiV3Service); + readonly currentUser = inject(CurrentUserService); + readonly cdRef = inject(ChangeDetectorRef); + readonly I18n = inject(I18nService); + @Input() public shouldFocus = false; @Input() public filter:QueryFilterInstanceResource; @@ -73,17 +71,6 @@ export class FilterToggledMultiselectValueComponent implements OnInit, AfterView placeholder: this.I18n.t('js.placeholders.selection'), }; - constructor( - readonly halResourceService:HalResourceService, - readonly halSorting:HalResourceSortingService, - readonly PathHelper:PathHelperService, - readonly apiV3Service:ApiV3Service, - readonly currentUser:CurrentUserService, - readonly cdRef:ChangeDetectorRef, - readonly I18n:I18nService, - ) { - } - ngOnInit():void { const values = (this.filter.currentSchema!.values!.allowedValues as HalResource[]); this.availableOptions = this.halSorting.sort(values); diff --git a/frontend/src/app/features/work-packages/components/filters/query-filter/query-filter.component.ts b/frontend/src/app/features/work-packages/components/filters/query-filter/query-filter.component.ts index 2a3bba8124a..aa0455a1a9c 100644 --- a/frontend/src/app/features/work-packages/components/filters/query-filter/query-filter.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/query-filter/query-filter.component.ts @@ -26,16 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - HostBinding, - Input, - OnInit, - Output, - ViewEncapsulation, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, OnInit, Output, ViewEncapsulation, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { compareByHref, @@ -61,6 +52,12 @@ import { WorkPackageViewBaselineService } from 'core-app/features/work-packages/ changeDetection: ChangeDetectionStrategy.Default, }) export class QueryFilterComponent implements OnInit { + readonly wpTableFilters = inject(WorkPackageViewFiltersService); + readonly wpTableBaseline = inject(WorkPackageViewBaselineService); + readonly schemaCache = inject(SchemaCacheService); + readonly I18n = inject(I18nService); + readonly currentProject = inject(CurrentProjectService); + @HostBinding('class.op-query-filter') className = true; @Input() public shouldFocus = false; @@ -91,15 +88,6 @@ export class QueryFilterComponent implements OnInit { incompatible_filter: this.I18n.t('js.work_packages.filters.baseline_incompatible'), }; - constructor( - readonly wpTableFilters:WorkPackageViewFiltersService, - readonly wpTableBaseline:WorkPackageViewBaselineService, - readonly schemaCache:SchemaCacheService, - readonly I18n:I18nService, - readonly currentProject:CurrentProjectService, - ) { - } - public onFilterUpdated(filter:QueryFilterInstanceResource) { this.filter = filter; this.showValuesInput = this.showValues(); diff --git a/frontend/src/app/features/work-packages/components/filters/query-filters/query-filters.component.ts b/frontend/src/app/features/work-packages/components/filters/query-filters/query-filters.component.ts index 2321aadd3d1..dfe92d1313d 100644 --- a/frontend/src/app/features/work-packages/components/filters/query-filters/query-filters.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/query-filters/query-filters.component.ts @@ -26,16 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - OnChanges, - OnInit, - Output, - ViewChild, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnInit, Output, ViewChild, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { DebouncedEventEmitter } from 'core-app/shared/helpers/rxjs/debounced-event-emitter'; import { NgSelectComponent } from '@ng-select/ng-select'; @@ -59,6 +50,13 @@ const ADD_FILTER_SELECT_INDEX = -1; standalone: false, }) export class QueryFiltersComponent extends UntilDestroyedMixin implements OnInit, OnChanges { + readonly wpTableFilters = inject(WorkPackageViewFiltersService); + readonly wpTableBaseline = inject(WorkPackageViewBaselineService); + readonly wpFiltersService = inject(WorkPackageFiltersService); + readonly I18n = inject(I18nService); + readonly alternativeSearchService = inject(AlternativeSearchService); + readonly cdRef = inject(ChangeDetectorRef); + @ViewChild(NgSelectComponent) public ngSelectComponent:NgSelectComponent; @Input() public filters:QueryFilterInstanceResource[]; @@ -88,17 +86,6 @@ export class QueryFiltersComponent extends UntilDestroyedMixin implements OnInit baseline_warning: this.I18n.t('js.work_packages.filters.baseline_warning'), }; - constructor( - readonly wpTableFilters:WorkPackageViewFiltersService, - readonly wpTableBaseline:WorkPackageViewBaselineService, - readonly wpFiltersService:WorkPackageFiltersService, - readonly I18n:I18nService, - readonly alternativeSearchService:AlternativeSearchService, - readonly cdRef:ChangeDetectorRef, - ) { - super(); - } - ngOnInit():void { this.wpTableFilters.live$() .pipe( diff --git a/frontend/src/app/features/work-packages/components/filters/quick-filter-by-text-input/quick-filter-by-text-input.component.ts b/frontend/src/app/features/work-packages/components/filters/quick-filter-by-text-input/quick-filter-by-text-input.component.ts index 6052eecc646..b1ed134ffe3 100644 --- a/frontend/src/app/features/work-packages/components/filters/quick-filter-by-text-input/quick-filter-by-text-input.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/quick-filter-by-text-input/quick-filter-by-text-input.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Output, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageViewFiltersService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service'; import { Subject } from 'rxjs'; @@ -48,6 +48,10 @@ import { QueryFilterResource } from 'core-app/features/hal/resources/query-filte changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageFilterByTextInputComponent extends UntilDestroyedMixin { + readonly I18n = inject(I18nService); + readonly querySpace = inject(IsolatedQuerySpace); + readonly wpTableFilters = inject(WorkPackageViewFiltersService); + @Output() public deactivateFilter = new EventEmitter(); public text = { @@ -63,9 +67,7 @@ export class WorkPackageFilterByTextInputComponent extends UntilDestroyedMixin { /** Input for search requests */ public searchTermChanged:Subject = new Subject(); - constructor(readonly I18n:I18nService, - readonly querySpace:IsolatedQuerySpace, - readonly wpTableFilters:WorkPackageViewFiltersService) { + constructor() { super(); this.wpTableFilters diff --git a/frontend/src/app/features/work-packages/components/wp-baseline/baseline-legends/baseline-legends.component.ts b/frontend/src/app/features/work-packages/components/wp-baseline/baseline-legends/baseline-legends.component.ts index ce265bc3dfb..ddcfd00a104 100644 --- a/frontend/src/app/features/work-packages/components/wp-baseline/baseline-legends/baseline-legends.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-baseline/baseline-legends/baseline-legends.component.ts @@ -26,14 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - HostBinding, - OnInit, - ViewEncapsulation, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, OnInit, ViewEncapsulation, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageViewBaselineService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-baseline.service'; import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space'; @@ -61,6 +54,14 @@ import { filter } from 'rxjs/operators'; standalone: false, }) export class OpBaselineLegendsComponent extends UntilDestroyedMixin implements OnInit { + readonly I18n = inject(I18nService); + readonly wpTableBaseline = inject(WorkPackageViewBaselineService); + readonly querySpace = inject(IsolatedQuerySpace); + readonly schemaCache = inject(SchemaCacheService); + readonly timezoneService = inject(TimezoneService); + readonly configuration = inject(ConfigurationService); + readonly cdRef = inject(ChangeDetectorRef); + @HostBinding('class.op-baseline-legends') className = true; public numAdded = 0; @@ -86,18 +87,6 @@ export class OpBaselineLegendsComponent extends UntilDestroyedMixin implements O in_your_timezone: this.I18n.t('js.baseline.legends.in_your_timezone'), }; - constructor( - readonly I18n:I18nService, - readonly wpTableBaseline:WorkPackageViewBaselineService, - readonly querySpace:IsolatedQuerySpace, - readonly schemaCache:SchemaCacheService, - readonly timezoneService:TimezoneService, - readonly configuration:ConfigurationService, - readonly cdRef:ChangeDetectorRef, - ) { - super(); - } - ngOnInit() { this .wpTableBaseline diff --git a/frontend/src/app/features/work-packages/components/wp-baseline/baseline-modal/baseline-modal.component.ts b/frontend/src/app/features/work-packages/components/wp-baseline/baseline-modal/baseline-modal.component.ts index 6616f8a55e6..c04ea13a584 100644 --- a/frontend/src/app/features/work-packages/components/wp-baseline/baseline-modal/baseline-modal.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-baseline/baseline-modal/baseline-modal.component.ts @@ -26,11 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - HostBinding, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostBinding, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @Component({ @@ -40,6 +36,8 @@ import { I18nService } from 'core-app/core/i18n/i18n.service'; standalone: false, }) export class OpBaselineModalComponent { + readonly I18n = inject(I18nService); + @HostBinding('class.op-baseline-modal') className = true; public opened = false; @@ -52,10 +50,6 @@ export class OpBaselineModalComponent { }; - constructor( - readonly I18n:I18nService, - ) {} - public toggleOpen():void { this.opened = !this.opened; } diff --git a/frontend/src/app/features/work-packages/components/wp-baseline/baseline/baseline.component.ts b/frontend/src/app/features/work-packages/components/wp-baseline/baseline/baseline.component.ts index cf9130613a6..faae4c000ef 100644 --- a/frontend/src/app/features/work-packages/components/wp-baseline/baseline/baseline.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-baseline/baseline/baseline.component.ts @@ -26,15 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - HostBinding, - Input, - OnInit, - Output, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, OnInit, Output, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; @@ -71,6 +63,15 @@ const DEFAULT_SELECTED_TIME = '08:00'; standalone: false, }) export class OpBaselineComponent extends UntilDestroyedMixin implements OnInit { + readonly I18n = inject(I18nService); + readonly wpTableBaseline = inject(WorkPackageViewBaselineService); + readonly halResourceService = inject(HalResourceService); + readonly weekdaysService = inject(WeekdayService); + readonly daysService = inject(DayResourceService); + readonly timezoneService = inject(TimezoneService); + readonly configuration = inject(ConfigurationService); + readonly Banner = inject(BannersService); + @HostBinding('class.op-baseline') className = true; @Output() submitted = new EventEmitter(); @@ -162,19 +163,6 @@ export class OpBaselineComponent extends UntilDestroyedMixin implements OnInit { }, ]; - constructor( - readonly I18n:I18nService, - readonly wpTableBaseline:WorkPackageViewBaselineService, - readonly halResourceService:HalResourceService, - readonly weekdaysService:WeekdayService, - readonly daysService:DayResourceService, - readonly timezoneService:TimezoneService, - readonly configuration:ConfigurationService, - readonly Banner:BannersService, - ) { - super(); - } - public ngOnInit():void { this.userTimezone = this.timezoneService.userTimezone(); this.userOffset = moment().tz(this.userTimezone).format('Z'); diff --git a/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb-parent.component.ts b/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb-parent.component.ts index ac29f91b335..c164626783a 100644 --- a/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb-parent.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb-parent.component.ts @@ -26,13 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - Input, - Output, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { WorkPackageRelationsHierarchyService } from 'core-app/features/work-packages/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @@ -49,6 +43,11 @@ import { PathHelperService } from 'core-app/core/path-helper/path-helper.service changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageBreadcrumbParentComponent { + protected readonly I18n = inject(I18nService); + protected readonly wpRelationsHierarchy = inject(WorkPackageRelationsHierarchyService); + protected readonly notificationService = inject(WorkPackageNotificationService); + protected readonly pathHelper = inject(PathHelperService); + @Input() workPackage:WorkPackageResource; @Output() onSwitch = new EventEmitter(); @@ -64,14 +63,6 @@ export class WorkPackageBreadcrumbParentComponent { private editing:boolean; - public constructor( - protected readonly I18n:I18nService, - protected readonly wpRelationsHierarchy:WorkPackageRelationsHierarchyService, - protected readonly notificationService:WorkPackageNotificationService, - protected readonly pathHelper:PathHelperService, - ) { - } - public canModifyParent():boolean { return !!this.workPackage.changeParent; } diff --git a/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb.component.ts b/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb.component.ts index 8f0662c6bb2..89f007dcd95 100644 --- a/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; @@ -42,6 +42,9 @@ import { PathHelperService } from 'core-app/core/path-helper/path-helper.service changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageBreadcrumbComponent { + private I18n = inject(I18nService); + private pathHelper = inject(PathHelperService); + @Input() workPackage:WorkPackageResource; public text = { @@ -49,11 +52,6 @@ export class WorkPackageBreadcrumbComponent { hierarchy: this.I18n.t('js.relations_hierarchy.hierarchy_headline'), }; - constructor( - private I18n:I18nService, - private pathHelper:PathHelperService, - ) {} - public inputActive = false; public get hierarchyCount() { diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/wp-create-button/wp-create-button.component.ts b/frontend/src/app/features/work-packages/components/wp-buttons/wp-create-button/wp-create-button.component.ts index 2bca877a7fd..85d6e0eaa1f 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/wp-create-button/wp-create-button.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-buttons/wp-create-button/wp-create-button.component.ts @@ -27,7 +27,7 @@ //++ import { StateService, TransitionService } from '@uirouter/core'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service'; import { Observable } from 'rxjs'; @@ -43,6 +43,14 @@ import { CurrentUserService } from 'core-app/core/current-user/current-user.serv standalone: false, }) export class WorkPackageCreateButtonComponent extends UntilDestroyedMixin implements OnInit, OnDestroy { + readonly $state = inject(StateService); + readonly currentUser = inject(CurrentUserService); + readonly currentProject = inject(CurrentProjectService); + readonly authorisationService = inject(AuthorisationService); + readonly transition = inject(TransitionService); + readonly I18n = inject(I18nService); + readonly cdRef = inject(ChangeDetectorRef); + @Input() stateName$:Observable; @Input() routedFromAngular = true; @@ -64,18 +72,6 @@ export class WorkPackageCreateButtonComponent extends UntilDestroyedMixin implem explanation: this.I18n.t('js.label_create_work_package'), }; - constructor( - readonly $state:StateService, - readonly currentUser:CurrentUserService, - readonly currentProject:CurrentProjectService, - readonly authorisationService:AuthorisationService, - readonly transition:TransitionService, - readonly I18n:I18nService, - readonly cdRef:ChangeDetectorRef, - ) { - super(); - } - ngOnInit() { this.projectIdentifier = this.currentProject.identifier; diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/wp-details-view-button/wp-details-view-button.component.ts b/frontend/src/app/features/work-packages/components/wp-buttons/wp-details-view-button/wp-details-view-button.component.ts index 50462a62384..94b6a73d354 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/wp-details-view-button/wp-details-view-button.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-buttons/wp-details-view-button/wp-details-view-button.component.ts @@ -28,9 +28,7 @@ import { WorkPackageViewFocusService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-focus.service'; import { StateService, TransitionService } from '@uirouter/core'; -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, inject } from '@angular/core'; import { AbstractWorkPackageButtonComponent } from 'core-app/features/work-packages/components/wp-buttons/wp-buttons.module'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { States } from 'core-app/core/states/states.service'; @@ -44,6 +42,14 @@ import { resolveRoutingId } from 'core-app/features/work-packages/helpers/work-p standalone: false, }) export class WorkPackageDetailsViewButtonComponent extends AbstractWorkPackageButtonComponent implements OnDestroy { + readonly $state = inject(StateService); + readonly I18n:I18nService; + readonly transitions = inject(TransitionService); + readonly cdRef = inject(ChangeDetectorRef); + states = inject(States); + wpTableFocus = inject(WorkPackageViewFocusService); + keepTab = inject(KeepTabService); + public projectIdentifier:string; public accessKey = 8; @@ -64,16 +70,12 @@ export class WorkPackageDetailsViewButtonComponent extends AbstractWorkPackageBu private transitionListener:Function; - constructor( - readonly $state:StateService, - readonly I18n:I18nService, - readonly transitions:TransitionService, - readonly cdRef:ChangeDetectorRef, - public states:States, - public wpTableFocus:WorkPackageViewFocusService, - public keepTab:KeepTabService, - ) { + constructor() { + const I18n = inject(I18nService); + super(I18n); + this.I18n = I18n; + this.activateLabel = I18n.t('js.button_open_details'); this.deactivateLabel = I18n.t('js.button_close_details'); diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/wp-filter-button/wp-filter-button.component.ts b/frontend/src/app/features/work-packages/components/wp-buttons/wp-filter-button/wp-filter-button.component.ts index 825f91f93e6..a8e2d62ab58 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/wp-filter-button/wp-filter-button.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-buttons/wp-filter-button/wp-filter-button.component.ts @@ -28,9 +28,7 @@ import { AbstractWorkPackageButtonComponent } from 'core-app/features/work-packages/components/wp-buttons/wp-buttons.module'; import { I18nService } from 'core-app/core/i18n/i18n.service'; -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, inject } from '@angular/core'; import { WorkPackageViewFiltersService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service'; import { componentDestroyed } from '@w11k/ngx-componentdestroyed'; import { WorkPackageFiltersService } from 'core-app/features/work-packages/components/filters/wp-filters/wp-filters.service'; @@ -42,6 +40,11 @@ import { WorkPackageFiltersService } from 'core-app/features/work-packages/compo standalone: false, }) export class WorkPackageFilterButtonComponent extends AbstractWorkPackageButtonComponent implements OnInit { + readonly I18n:I18nService; + protected cdRef = inject(ChangeDetectorRef); + protected wpFiltersService = inject(WorkPackageFiltersService); + protected wpTableFilters = inject(WorkPackageViewFiltersService); + public count:number; public initialized = false; @@ -52,13 +55,12 @@ export class WorkPackageFilterButtonComponent extends AbstractWorkPackageButtonC public title = this.I18n.t('js.work_packages.filters.title'); - constructor( - readonly I18n:I18nService, - protected cdRef:ChangeDetectorRef, - protected wpFiltersService:WorkPackageFiltersService, - protected wpTableFilters:WorkPackageViewFiltersService, - ) { + constructor() { + const I18n = inject(I18nService); + super(I18n); + + this.I18n = I18n; } ngOnInit():void { diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/wp-mark-notification-button/work-package-mark-notification-button.component.ts b/frontend/src/app/features/work-packages/components/wp-buttons/wp-mark-notification-button/work-package-mark-notification-button.component.ts index b174a20c9b1..25a77f50368 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/wp-mark-notification-button/work-package-mark-notification-button.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-buttons/wp-mark-notification-button/work-package-mark-notification-button.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { WpSingleViewService } from 'core-app/features/work-packages/routing/wp-view-base/state/wp-single-view.service'; @@ -10,18 +10,15 @@ import { WpSingleViewService } from 'core-app/features/work-packages/routing/wp- standalone: false, }) export class WorkPackageMarkNotificationButtonComponent { + private I18n = inject(I18nService); + private storeService = inject(WpSingleViewService); + @Input() public workPackage:WorkPackageResource; text = { mark_as_read: this.I18n.t('js.notifications.center.mark_as_read'), }; - constructor( - private I18n:I18nService, - private storeService:WpSingleViewService, - ) { - } - markAllBelongingNotificationsAsRead():void { this.storeService.markAllAsRead(); // emit custom event in order to inform the activity tab stimulus controller diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/wp-reminder-button/wp-reminder-button.component.ts b/frontend/src/app/features/work-packages/components/wp-buttons/wp-reminder-button/wp-reminder-button.component.ts index 4dd6608d0c0..135a9547544 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/wp-reminder-button/wp-reminder-button.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-buttons/wp-reminder-button/wp-reminder-button.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, inject } from '@angular/core'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { ActionsService } from 'core-app/core/state/actions/actions.service'; @@ -51,23 +51,19 @@ import { filter, map, startWith, switchMap } from 'rxjs/operators'; standalone: false, }) export class WorkPackageReminderButtonComponent extends UntilDestroyedMixin implements OnInit { + readonly I18n = inject(I18nService); + readonly opModalService = inject(OpModalService); + readonly cdRef = inject(ChangeDetectorRef); + readonly apiV3Service = inject(ApiV3Service); + readonly actions$ = inject(ActionsService); + readonly storeService = inject(IanBellService); + @Input() public workPackage:WorkPackageResource; hasReminder$:Observable; public buttonTitle = this.I18n.t('js.work_packages.reminders.button_label'); - constructor( - readonly I18n:I18nService, - readonly opModalService:OpModalService, - readonly cdRef:ChangeDetectorRef, - readonly apiV3Service:ApiV3Service, - readonly actions$:ActionsService, - readonly storeService:IanBellService, - ) { - super(); - } - ngOnInit() { const reminderModalUpdated$ = this .actions$ diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/wp-reminder-button/wp-reminder-context-menu.directive.ts b/frontend/src/app/features/work-packages/components/wp-buttons/wp-reminder-button/wp-reminder-context-menu.directive.ts index 75b06874064..88097ce5b08 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/wp-reminder-button/wp-reminder-context-menu.directive.ts +++ b/frontend/src/app/features/work-packages/components/wp-buttons/wp-reminder-button/wp-reminder-context-menu.directive.ts @@ -1,5 +1,4 @@ -import { Directive, ElementRef, Input, OnInit } from '@angular/core'; -import { OPContextMenuService } from 'core-app/shared/components/op-context-menu/op-context-menu.service'; +import { Directive, Input, OnInit, inject } from '@angular/core'; import { OpContextMenuTrigger } from 'core-app/shared/components/op-context-menu/handlers/op-context-menu-trigger.directive'; import { OpContextMenuItem } from 'core-app/shared/components/op-context-menu/op-context-menu.types'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @@ -14,20 +13,14 @@ import { ReminderPreset, REMINDER_PRESET_OPTIONS } from 'core-app/features/work- standalone: false, }) export class WorkPackageReminderContextMenuDirective extends OpContextMenuTrigger implements OnInit { + readonly I18n = inject(I18nService); + readonly opModalService = inject(OpModalService); + // eslint-disable-next-line @angular-eslint/no-input-rename @Input('wpReminderContextMenu-workPackage') workPackage:WorkPackageResource; protected items:OpContextMenuItem[] = []; - constructor( - readonly elementRef:ElementRef, - readonly opContextMenu:OPContextMenuService, - readonly I18n:I18nService, - readonly opModalService:OpModalService, - ) { - super(elementRef, opContextMenu); - } - ngOnInit() { this.buildItems(); } diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/wp-settings-button/wp-settings-button.component.ts b/frontend/src/app/features/work-packages/components/wp-buttons/wp-settings-button/wp-settings-button.component.ts index 7ce89528228..671e389d27c 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/wp-settings-button/wp-settings-button.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-buttons/wp-settings-button/wp-settings-button.component.ts @@ -26,11 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - Input, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @Component({ @@ -39,6 +35,8 @@ import { I18nService } from 'core-app/core/i18n/i18n.service'; standalone: false, }) export class WorkPackageSettingsButtonComponent { + readonly I18n = inject(I18nService); + @Input() hideTableOptions = false; @Input() showCalendarSharingOption = false; @@ -46,7 +44,4 @@ export class WorkPackageSettingsButtonComponent { public text = { more_actions: this.I18n.t('js.button_more_actions'), }; - - constructor(readonly I18n:I18nService) { - } } diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/wp-share-button/wp-share-button.component.ts b/frontend/src/app/features/work-packages/components/wp-buttons/wp-share-button/wp-share-button.component.ts index 5928d5d01e2..8e5d3e1796d 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/wp-share-button/wp-share-button.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-buttons/wp-share-button/wp-share-button.component.ts @@ -27,7 +27,7 @@ //++ import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; import { OpModalService } from 'core-app/shared/components/modal/modal.service'; @@ -51,6 +51,13 @@ import { CollectionResource } from 'core-app/features/hal/resources/collection-r standalone: false, }) export class WorkPackageShareButtonComponent extends UntilDestroyedMixin implements OnInit { + readonly I18n = inject(I18nService); + readonly opModalService = inject(OpModalService); + readonly cdRef = inject(ChangeDetectorRef); + readonly bannersService = inject(BannersService); + readonly apiV3Service = inject(ApiV3Service); + readonly actions$ = inject(ActionsService); + @Input() public workPackage:WorkPackageResource; showEnterpriseIcon = !this.bannersService.allowsTo('work_package_sharing'); @@ -61,17 +68,6 @@ export class WorkPackageShareButtonComponent extends UntilDestroyedMixin impleme share: this.I18n.t('js.sharing.share'), }; - constructor( - readonly I18n:I18nService, - readonly opModalService:OpModalService, - readonly cdRef:ChangeDetectorRef, - readonly bannersService:BannersService, - readonly apiV3Service:ApiV3Service, - readonly actions$:ActionsService, - ) { - super(); - } - ngOnInit() { this.shareCount$ = this .actions$ diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/wp-status-button/wp-status-button.component.ts b/frontend/src/app/features/work-packages/components/wp-buttons/wp-status-button/wp-status-button.component.ts index 88c060906d3..b728d6e6f0c 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/wp-status-button/wp-status-button.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-buttons/wp-status-button/wp-status-button.component.ts @@ -28,9 +28,7 @@ import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { HalResourceEditingService } from 'core-app/shared/components/fields/edit/services/hal-resource-editing.service'; -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { Highlighting } from 'core-app/features/work-packages/components/wp-fast-table/builders/highlighting/highlighting.functions'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; @@ -48,6 +46,11 @@ import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service'; changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageStatusButtonComponent extends UntilDestroyedMixin implements OnInit { + readonly I18n = inject(I18nService); + readonly cdRef = inject(ChangeDetectorRef); + readonly schemaCache = inject(SchemaCacheService); + readonly halEditing = inject(HalResourceEditingService); + @Input() public workPackage:WorkPackageResource; @Input() public small = false; @@ -58,13 +61,6 @@ export class WorkPackageStatusButtonComponent extends UntilDestroyedMixin implem workPackageStatusBlocked: this.I18n.t('js.work_packages.message_work_package_status_blocked'), }; - constructor(readonly I18n:I18nService, - readonly cdRef:ChangeDetectorRef, - readonly schemaCache:SchemaCacheService, - readonly halEditing:HalResourceEditingService) { - super(); - } - ngOnInit() { this.halEditing .temporaryEditResource(this.workPackage) diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/wp-timeline-toggle-button/wp-timeline-toggle-button.component.ts b/frontend/src/app/features/work-packages/components/wp-buttons/wp-timeline-toggle-button/wp-timeline-toggle-button.component.ts index 12ffec68e32..1fd0c0c4a44 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/wp-timeline-toggle-button/wp-timeline-toggle-button.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-buttons/wp-timeline-toggle-button/wp-timeline-toggle-button.component.ts @@ -26,9 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageViewTimelineService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-timeline.service'; import { TimelineZoomLevel } from 'core-app/features/hal/resources/query-resource'; @@ -48,6 +46,10 @@ export interface TimelineButtonText extends ButtonControllerText { standalone: false, }) export class WorkPackageTimelineButtonComponent extends AbstractWorkPackageButtonComponent implements OnInit { + readonly I18n:I18nService; + readonly cdRef = inject(ChangeDetectorRef); + wpTableTimeline = inject(WorkPackageViewTimelineService); + public buttonId = 'work-packages-timeline-toggle-button'; public iconClass = 'icon-view-timeline'; @@ -68,10 +70,12 @@ export class WorkPackageTimelineButtonComponent extends AbstractWorkPackageButto public isMinLevel = false; - constructor(readonly I18n:I18nService, - readonly cdRef:ChangeDetectorRef, - public wpTableTimeline:WorkPackageViewTimelineService) { + constructor() { + const I18n = inject(I18nService); + super(I18n); + this.I18n = I18n; + this.activateLabel = I18n.t('js.gantt_chart.button_activate'); this.deactivateLabel = I18n.t('js.gantt_chart.button_deactivate'); diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/zen-mode-toggle-button/zen-mode-toggle-button.component.ts b/frontend/src/app/features/work-packages/components/wp-buttons/zen-mode-toggle-button/zen-mode-toggle-button.component.ts index cb143a5e600..2871ebb5831 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/zen-mode-toggle-button/zen-mode-toggle-button.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-buttons/zen-mode-toggle-button/zen-mode-toggle-button.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { AbstractWorkPackageButtonComponent } from '../wp-buttons.module'; @@ -39,6 +39,9 @@ import screenfull from 'screenfull'; standalone: false, }) export class ZenModeButtonComponent extends AbstractWorkPackageButtonComponent { + readonly I18n:I18nService; + readonly cdRef = inject(ChangeDetectorRef); + public buttonId = 'work-packages-zen-mode-toggle-button'; public buttonClass = 'toolbar-icon'; @@ -51,13 +54,12 @@ export class ZenModeButtonComponent extends AbstractWorkPackageButtonComponent { private deactivateLabel:string; - constructor( - // eslint-disable-next-line @angular-eslint/prefer-inject - readonly I18n:I18nService, - // eslint-disable-next-line @angular-eslint/prefer-inject - readonly cdRef:ChangeDetectorRef - ) { + constructor() { + const I18n = inject(I18nService); + super(I18n); + this.I18n = I18n; + this.activateLabel = I18n.t('js.zen_mode.button_activate'); this.deactivateLabel = I18n.t('js.zen_mode.button_deactivate'); diff --git a/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service.ts b/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service.ts index b3dcbbe9649..632622edd27 100644 --- a/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Injector, Optional } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { WorkPackageViewOrderService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-order.service'; import { States } from 'core-app/core/states/states.service'; @@ -16,6 +16,16 @@ import { firstValueFrom } from 'rxjs'; @Injectable() export class WorkPackageCardDragAndDropService { + readonly states = inject(States); + readonly injector = inject(Injector); + readonly reorderService = inject(WorkPackageViewOrderService); + readonly wpCreate = inject(WorkPackageCreateService); + readonly notificationService = inject(WorkPackageNotificationService); + readonly apiV3Service = inject(ApiV3Service); + readonly currentProject = inject(CurrentProjectService); + readonly dragService = inject(DragAndDropService, { optional: true }); + readonly wpInlineCreate = inject(WorkPackageInlineCreateService); + private _workPackages:WorkPackageResource[]; /** Whether the card view has an active inline created wp */ @@ -24,18 +34,6 @@ export class WorkPackageCardDragAndDropService { /** A reference to the component in use, to have access to the current input variables */ public cardView:WorkPackageCardViewComponent; - public constructor(readonly states:States, - readonly injector:Injector, - readonly reorderService:WorkPackageViewOrderService, - readonly wpCreate:WorkPackageCreateService, - readonly notificationService:WorkPackageNotificationService, - readonly apiV3Service:ApiV3Service, - readonly currentProject:CurrentProjectService, - @Optional() readonly dragService:DragAndDropService, - readonly wpInlineCreate:WorkPackageInlineCreateService) { - - } - public init(componentRef:WorkPackageCardViewComponent) { this.cardView = componentRef; } diff --git a/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-view.service.ts b/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-view.service.ts index bbbb69483b3..47f80f78eb8 100644 --- a/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-view.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-view.service.ts @@ -1,11 +1,11 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space'; @Injectable() export class WorkPackageCardViewService { - public constructor(readonly querySpace:IsolatedQuerySpace) { - } + readonly querySpace = inject(IsolatedQuerySpace); + public classIdentifier(wp:WorkPackageResource) { // The same class names are used for the proximity to the table representation. diff --git a/frontend/src/app/features/work-packages/components/wp-card-view/wp-card-view.component.ts b/frontend/src/app/features/work-packages/components/wp-card-view/wp-card-view.component.ts index 9cf71531f98..8e0031b2d53 100644 --- a/frontend/src/app/features/work-packages/components/wp-card-view/wp-card-view.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-card-view/wp-card-view.component.ts @@ -1,16 +1,4 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Injector, - Input, - OnInit, - Output, - ViewChild, OnDestroy, -} from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Injector, Input, OnInit, Output, ViewChild, OnDestroy, inject } from '@angular/core'; import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageInlineCreateService } from 'core-app/features/work-packages/components/wp-inline-create/wp-inline-create.service'; @@ -55,6 +43,25 @@ export type CardViewOrientation = 'horizontal'|'vertical'; standalone: false, }) export class WorkPackageCardViewComponent extends UntilDestroyedMixin implements OnInit, AfterViewInit, WorkPackageViewOutputs, OnDestroy { + readonly querySpace = inject(IsolatedQuerySpace); + readonly states = inject(States); + readonly injector = inject(Injector); + readonly $state = inject(StateService); + readonly I18n = inject(I18nService); + readonly wpCreate = inject(WorkPackageCreateService); + readonly wpInlineCreate = inject(WorkPackageInlineCreateService); + readonly notificationService = inject(WorkPackageNotificationService); + readonly halEvents = inject(HalEventsService); + readonly authorisationService = inject(AuthorisationService); + readonly causedUpdates = inject(CausedUpdatesService); + readonly cdRef = inject(ChangeDetectorRef); + readonly pathHelper = inject(PathHelperService); + readonly wpTableSelection = inject(WorkPackageViewSelectionService); + readonly wpViewOrder = inject(WorkPackageViewOrderService); + readonly cardView = inject(WorkPackageCardViewService); + readonly cardDragDrop = inject(WorkPackageCardDragAndDropService); + readonly deviceService = inject(DeviceService); + @Input('dragOutOfHandler') public canDragOutOf:(wp:WorkPackageResource) => boolean; @Input() public dragInto:boolean; @@ -119,27 +126,6 @@ export class WorkPackageCardViewComponent extends UntilDestroyedMixin implements isNewResource = isNewResource; - constructor(readonly querySpace:IsolatedQuerySpace, - readonly states:States, - readonly injector:Injector, - readonly $state:StateService, - readonly I18n:I18nService, - readonly wpCreate:WorkPackageCreateService, - readonly wpInlineCreate:WorkPackageInlineCreateService, - readonly notificationService:WorkPackageNotificationService, - readonly halEvents:HalEventsService, - readonly authorisationService:AuthorisationService, - readonly causedUpdates:CausedUpdatesService, - readonly cdRef:ChangeDetectorRef, - readonly pathHelper:PathHelperService, - readonly wpTableSelection:WorkPackageViewSelectionService, - readonly wpViewOrder:WorkPackageViewOrderService, - readonly cardView:WorkPackageCardViewService, - readonly cardDragDrop:WorkPackageCardDragAndDropService, - readonly deviceService:DeviceService) { - super(); - } - ngOnInit() { this.registerCreationCallback(); diff --git a/frontend/src/app/features/work-packages/components/wp-custom-actions/wp-custom-actions.component.ts b/frontend/src/app/features/work-packages/components/wp-custom-actions/wp-custom-actions.component.ts index 8f94d5129b2..a99eca561b0 100644 --- a/frontend/src/app/features/work-packages/components/wp-custom-actions/wp-custom-actions.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-custom-actions/wp-custom-actions.component.ts @@ -26,9 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, inject } from '@angular/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { CustomActionResource } from 'core-app/features/hal/resources/custom-action-resource'; @@ -42,20 +40,16 @@ import { BannersService } from 'core-app/core/enterprise/banners.service'; standalone: false, }) export class WpCustomActionsComponent extends UntilDestroyedMixin implements OnInit { + readonly apiV3Service = inject(ApiV3Service); + readonly cdRef = inject(ChangeDetectorRef); + readonly bannersService = inject(BannersService); + @Input() workPackage:WorkPackageResource; actions:CustomActionResource[] = []; available = this.bannersService.allowsTo('custom_actions'); - constructor( - readonly apiV3Service:ApiV3Service, - readonly cdRef:ChangeDetectorRef, - readonly bannersService:BannersService, - ) { - super(); - } - ngOnInit() { this .apiV3Service diff --git a/frontend/src/app/features/work-packages/components/wp-custom-actions/wp-custom-actions/wp-custom-action.component.ts b/frontend/src/app/features/work-packages/components/wp-custom-actions/wp-custom-actions/wp-custom-action.component.ts index ea9cead6aee..604bf4a4e3a 100644 --- a/frontend/src/app/features/work-packages/components/wp-custom-actions/wp-custom-actions/wp-custom-action.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-custom-actions/wp-custom-actions/wp-custom-action.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Input, OnInit, inject } from '@angular/core'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { CustomActionResource } from 'core-app/features/hal/resources/custom-action-resource'; import { HalEventsService } from 'core-app/features/hal/services/hal-events.service'; @@ -51,22 +51,18 @@ import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destr standalone: false, }) export class WpCustomActionComponent extends UntilDestroyedMixin implements OnInit { + private halResourceService = inject(HalResourceService); + private apiV3Service = inject(ApiV3Service); + private wpActivity = inject(WorkPackagesActivityService); + private notificationService = inject(WorkPackageNotificationService); + private halEditing = inject(HalResourceEditingService); + private halEvents = inject(HalEventsService); + private cdRef = inject(ChangeDetectorRef); + @Input() workPackage:WorkPackageResource; @Input() action:CustomActionResource; - constructor( - private halResourceService:HalResourceService, - private apiV3Service:ApiV3Service, - private wpActivity:WorkPackagesActivityService, - private notificationService:WorkPackageNotificationService, - private halEditing:HalResourceEditingService, - private halEvents:HalEventsService, - private cdRef:ChangeDetectorRef, - ) { - super(); - } - ngOnInit() { this .halEvents diff --git a/frontend/src/app/features/work-packages/components/wp-details/wp-details-toolbar.component.ts b/frontend/src/app/features/work-packages/components/wp-details/wp-details-toolbar.component.ts index 3d3ad0323f7..383b4154d16 100644 --- a/frontend/src/app/features/work-packages/components/wp-details/wp-details-toolbar.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-details/wp-details-toolbar.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit, inject } from '@angular/core'; import { ConfigurationService } from 'core-app/core/config/configuration.service'; import { CurrentUserService } from 'core-app/core/current-user/current-user.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @@ -44,6 +44,11 @@ import { Observable, of } from 'rxjs'; standalone: false, }) export class WorkPackageSplitViewToolbarComponent implements OnInit { + readonly I18n = inject(I18nService); + readonly halEditing = inject(HalResourceEditingService); + readonly configurationService = inject(ConfigurationService); + readonly currentUserService = inject(CurrentUserService); + @Input() workPackage:WorkPackageResource; @Input() displayNotificationsButton:boolean; @@ -55,14 +60,6 @@ export class WorkPackageSplitViewToolbarComponent implements OnInit { button_more: this.I18n.t('js.button_more'), }; - constructor( - readonly I18n:I18nService, - readonly halEditing:HalResourceEditingService, - readonly configurationService:ConfigurationService, - readonly currentUserService:CurrentUserService, - ) { - } - ngOnInit() { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access this.displayShareButton$ = this.currentUserService.hasCapabilities$('work_package_shares/index', this.workPackage.project.id); diff --git a/frontend/src/app/features/work-packages/components/wp-edit/wp-edit-field/wp-replacement-label.component.ts b/frontend/src/app/features/work-packages/components/wp-edit/wp-edit-field/wp-replacement-label.component.ts index 70919c585f0..6b7a7cba7f1 100644 --- a/frontend/src/app/features/work-packages/components/wp-edit/wp-edit-field/wp-replacement-label.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-edit/wp-edit-field/wp-replacement-label.component.ts @@ -26,9 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, inject } from '@angular/core'; import { EditFormComponent } from 'core-app/shared/components/fields/edit/edit-form/edit-form.component'; @Component({ @@ -41,14 +39,13 @@ import { EditFormComponent } from 'core-app/shared/components/fields/edit/edit-f changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageReplacementLabelComponent implements OnInit { + protected wpeditForm = inject(EditFormComponent); + protected elementRef = inject(ElementRef); + @Input() public fieldName:string; private element:HTMLElement; - constructor(protected wpeditForm:EditFormComponent, - protected elementRef:ElementRef) { - } - ngOnInit() { this.element = this.elementRef.nativeElement; } diff --git a/frontend/src/app/features/work-packages/components/wp-form-group/wp-attribute-group.component.ts b/frontend/src/app/features/work-packages/components/wp-form-group/wp-attribute-group.component.ts index 4a8ebf88201..1327b19968d 100644 --- a/frontend/src/app/features/work-packages/components/wp-form-group/wp-attribute-group.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-form-group/wp-attribute-group.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, HostBinding, Injector, Input, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostBinding, Injector, Input, ViewEncapsulation, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { EditFormComponent } from 'core-app/shared/components/fields/edit/edit-form/edit-form.component'; @@ -48,6 +48,10 @@ import { changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageFormAttributeGroupComponent extends UntilDestroyedMixin { + readonly I18n = inject(I18nService); + wpEditForm = inject(EditFormComponent); + protected injector = inject(Injector); + @HostBinding('class.wp-attribute-group') className = true; @HostBinding('class.attributes-group--attributes') parentClassName = true; @@ -55,14 +59,6 @@ export class WorkPackageFormAttributeGroupComponent extends UntilDestroyedMixin @Input() public group:GroupDescriptor; - constructor( - readonly I18n:I18nService, - public wpEditForm:EditFormComponent, - protected injector:Injector, - ) { - super(); - } - /** * Hide read-only fields, but only when in the create mode * @param {FieldDescriptor} field diff --git a/frontend/src/app/features/work-packages/components/wp-grid/wp-grid.component.ts b/frontend/src/app/features/work-packages/components/wp-grid/wp-grid.component.ts index 635b6060a39..8b872922e0f 100644 --- a/frontend/src/app/features/work-packages/components/wp-grid/wp-grid.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-grid/wp-grid.component.ts @@ -26,9 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, OnInit, inject } from '@angular/core'; import { WorkPackageViewHighlightingService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-highlighting.service'; import { CardViewOrientation } from 'core-app/features/work-packages/components/wp-card-view/wp-card-view.component'; import { WorkPackageViewSortByService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-sort-by.service'; @@ -74,6 +72,12 @@ import { WorkPackageViewOutputs } from 'core-app/features/work-packages/routing/ standalone: false, }) export class WorkPackagesGridComponent implements WorkPackageViewOutputs, OnInit { + readonly wpTableHighlight = inject(WorkPackageViewHighlightingService); + readonly wpTableSortBy = inject(WorkPackageViewSortByService); + readonly wpList = inject(WorkPackagesListService); + readonly querySpace = inject(IsolatedQuerySpace); + readonly cdRef = inject(ChangeDetectorRef); + @Input() public configuration:WorkPackageTableConfiguration; @Input() public showResizer = false; @@ -96,13 +100,6 @@ export class WorkPackagesGridComponent implements WorkPackageViewOutputs, OnInit public highlightingMode:HighlightingMode = 'none'; - constructor(readonly wpTableHighlight:WorkPackageViewHighlightingService, - readonly wpTableSortBy:WorkPackageViewSortByService, - readonly wpList:WorkPackagesListService, - readonly querySpace:IsolatedQuerySpace, - readonly cdRef:ChangeDetectorRef) { - } - ngOnInit() { this.dragInto = this.configuration.dragAndDropEnabled; this.canDragOutOf = () => this.configuration.dragAndDropEnabled; diff --git a/frontend/src/app/features/work-packages/components/wp-inline-create/wp-inline-create.component.ts b/frontend/src/app/features/work-packages/components/wp-inline-create/wp-inline-create.component.ts index 97473e2d33a..daecf685f38 100644 --- a/frontend/src/app/features/work-packages/components/wp-inline-create/wp-inline-create.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-inline-create/wp-inline-create.component.ts @@ -26,19 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - HostListener, - Injector, - Input, - OnInit, - Output, -} from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Injector, Input, OnInit, Output, inject } from '@angular/core'; import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service'; import { WorkPackageViewFocusService, @@ -81,6 +69,19 @@ import { delegate, DelegateEvent } from '@knowledgecode/delegate'; changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageInlineCreateComponent extends UntilDestroyedMixin implements OnInit, AfterViewInit { + readonly injector = inject(Injector); + protected readonly elementRef = inject(ElementRef); + protected readonly schemaCache = inject(SchemaCacheService); + protected readonly I18n = inject(I18nService); + protected readonly querySpace = inject(IsolatedQuerySpace); + protected readonly cdRef = inject(ChangeDetectorRef); + protected readonly wpCreate = inject(WorkPackageCreateService); + protected readonly wpInlineCreate = inject(WorkPackageInlineCreateService); + protected readonly wpTableColumns = inject(WorkPackageViewColumnsService); + protected readonly wpTableFocus = inject(WorkPackageViewFocusService); + protected readonly halEditing = inject(HalResourceEditingService); + protected readonly authorisationService = inject(AuthorisationService); + @Input() colspan:number; @Input() table:WorkPackageTable; @@ -113,21 +114,6 @@ export class WorkPackageInlineCreateComponent extends UntilDestroyedMixin implem return this.mode !== 'inactive'; } - constructor(public readonly injector:Injector, - protected readonly elementRef:ElementRef, - protected readonly schemaCache:SchemaCacheService, - protected readonly I18n:I18nService, - protected readonly querySpace:IsolatedQuerySpace, - protected readonly cdRef:ChangeDetectorRef, - protected readonly wpCreate:WorkPackageCreateService, - protected readonly wpInlineCreate:WorkPackageInlineCreateService, - protected readonly wpTableColumns:WorkPackageViewColumnsService, - protected readonly wpTableFocus:WorkPackageViewFocusService, - protected readonly halEditing:HalResourceEditingService, - protected readonly authorisationService:AuthorisationService) { - super(); - } - ngOnInit() { this.element = this.elementRef.nativeElement; } diff --git a/frontend/src/app/features/work-packages/components/wp-inline-create/wp-inline-create.service.ts b/frontend/src/app/features/work-packages/components/wp-inline-create/wp-inline-create.service.ts index 64996713ebe..ab4efddfc21 100644 --- a/frontend/src/app/features/work-packages/components/wp-inline-create/wp-inline-create.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-inline-create/wp-inline-create.service.ts @@ -26,11 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - Injectable, - Injector, - OnDestroy, -} from '@angular/core'; +import { Injectable, Injector, OnDestroy, inject } from '@angular/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { Observable, @@ -45,15 +41,14 @@ import { CurrentProjectService } from 'core-app/core/current-project/current-pro @Injectable() export class WorkPackageInlineCreateService implements OnDestroy { + readonly injector = inject(Injector); + @InjectField() I18n!:I18nService; @InjectField() protected readonly currentUser:CurrentUserService; @InjectField() protected readonly currentProject:CurrentProjectService; - constructor(readonly injector:Injector) { - } - /** * A separate reference pane for the inline create component */ diff --git a/frontend/src/app/features/work-packages/components/wp-list/wp-list-checksum.service.ts b/frontend/src/app/features/work-packages/components/wp-list/wp-list-checksum.service.ts index 8b214522f5f..72d2446e1e9 100644 --- a/frontend/src/app/features/work-packages/components/wp-list/wp-list-checksum.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-list/wp-list-checksum.service.ts @@ -28,16 +28,16 @@ import { StateService, TransitionPromise } from '@uirouter/core'; import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { WorkPackageViewPagination } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-table-pagination'; import { QueryResource } from 'core-app/features/hal/resources/query-resource'; import { Subject } from 'rxjs'; @Injectable() export class WorkPackagesListChecksumService { - constructor(protected UrlParamsHelper:UrlParamsHelperService, - protected $state:StateService) { - } + protected UrlParamsHelper = inject(UrlParamsHelperService); + protected $state = inject(StateService); + public id:string|null; diff --git a/frontend/src/app/features/work-packages/components/wp-list/wp-list-invalid-query.service.ts b/frontend/src/app/features/work-packages/components/wp-list/wp-list-invalid-query.service.ts index 64d5d8bd965..e576e7ca231 100644 --- a/frontend/src/app/features/work-packages/components/wp-list/wp-list-invalid-query.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-list/wp-list-invalid-query.service.ts @@ -27,7 +27,7 @@ //++ import { QueryResource } from 'core-app/features/hal/resources/query-resource'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service'; import { QueryFilterInstanceSchemaResource } from 'core-app/features/hal/resources/query-filter-instance-schema-resource'; import { QueryFormResource } from 'core-app/features/hal/resources/query-form-resource'; @@ -39,8 +39,8 @@ import { QueryColumn } from '../wp-query/query-column'; @Injectable() export class WorkPackagesListInvalidQueryService { - constructor(protected halResourceService:HalResourceService) { - } + protected halResourceService = inject(HalResourceService); + public restoreQuery(query:QueryResource, form:QueryFormResource) { this.restoreFilters(query, form.payload, form.schema); diff --git a/frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts b/frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts index 6b47e92c2b6..ffa019a4d83 100644 --- a/frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts @@ -31,7 +31,7 @@ import { States } from 'core-app/core/states/states.service'; import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service'; import { StateService } from '@uirouter/core'; import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space'; -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator'; import isPersistedResource from 'core-app/features/hal/helpers/is-persisted-resource'; import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper'; @@ -63,6 +63,23 @@ export interface QueryDefinition { @Injectable() export class WorkPackagesListService { + readonly injector = inject(Injector); + protected toastService = inject(ToastService); + readonly I18n = inject(I18nService); + protected UrlParamsHelper = inject(UrlParamsHelperService); + protected authorisationService = inject(AuthorisationService); + protected $state = inject(StateService); + protected apiV3Service = inject(ApiV3Service); + protected states = inject(States); + protected querySpace = inject(IsolatedQuerySpace); + protected pagination = inject(PaginationService); + protected configuration = inject(ConfigurationService); + protected wpTablePagination = inject(WorkPackageViewPaginationService); + protected wpStatesInitialization = inject(WorkPackageStatesInitializationService); + protected wpListInvalidQueryService = inject(WorkPackagesListInvalidQueryService); + protected wpQueryView = inject(WorkPackagesQueryViewService); + protected submenuService = inject(SubmenuService); + @InjectField() protected readonly currentUser:CurrentUserService; // We remember the query requests coming in so we can ensure only the latest request is being tended to @@ -88,25 +105,6 @@ export class WorkPackagesListService { share(), ); - constructor( - readonly injector:Injector, - protected toastService:ToastService, - readonly I18n:I18nService, - protected UrlParamsHelper:UrlParamsHelperService, - protected authorisationService:AuthorisationService, - protected $state:StateService, - protected apiV3Service:ApiV3Service, - protected states:States, - protected querySpace:IsolatedQuerySpace, - protected pagination:PaginationService, - protected configuration:ConfigurationService, - protected wpTablePagination:WorkPackageViewPaginationService, - protected wpStatesInitialization:WorkPackageStatesInitializationService, - protected wpListInvalidQueryService:WorkPackagesListInvalidQueryService, - protected wpQueryView:WorkPackagesQueryViewService, - protected submenuService:SubmenuService, - ) { } - /** * Stream a query request as a HTTP observable. Each request to this method will * result in a new HTTP request. diff --git a/frontend/src/app/features/work-packages/components/wp-list/wp-query-view.service.ts b/frontend/src/app/features/work-packages/components/wp-list/wp-query-view.service.ts index e9b7d05c6e0..b9aaba7c476 100644 --- a/frontend/src/app/features/work-packages/components/wp-list/wp-query-view.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-list/wp-query-view.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { StateService } from '@uirouter/core'; import { QueryResource } from 'core-app/features/hal/resources/query-resource'; import { Observable } from 'rxjs'; @@ -8,10 +8,9 @@ import { ConfigurationService } from 'core-app/core/config/configuration.service @Injectable() export class WorkPackagesQueryViewService { - constructor( - protected $state:StateService, - protected apiV3Service:ApiV3Service, - ) { } + protected $state = inject(StateService); + protected apiV3Service = inject(ApiV3Service); + create(query:QueryResource):Observable { if (!query.href) { diff --git a/frontend/src/app/features/work-packages/components/wp-list/wp-states-initialization.service.ts b/frontend/src/app/features/work-packages/components/wp-list/wp-states-initialization.service.ts index ddca30dd19d..e38645c9884 100644 --- a/frontend/src/app/features/work-packages/components/wp-list/wp-states-initialization.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-list/wp-states-initialization.service.ts @@ -2,7 +2,7 @@ import { States } from 'core-app/core/states/states.service'; import { QueryResource } from 'core-app/features/hal/resources/query-resource'; import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service'; import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { WorkPackageViewHighlightingService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-highlighting.service'; import { take } from 'rxjs/operators'; import { WorkPackageViewOrderService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-order.service'; @@ -29,29 +29,28 @@ import { WorkPackageViewBaselineService } from 'core-app/features/work-packages/ @Injectable() export class WorkPackageStatesInitializationService { - constructor( - protected states:States, - protected querySpace:IsolatedQuerySpace, - protected wpTableColumns:WorkPackageViewColumnsService, - protected wpTableGroupBy:WorkPackageViewGroupByService, - protected wpTableGroupFold:WorkPackageViewCollapsedGroupsService, - protected wpTableSortBy:WorkPackageViewSortByService, - protected wpTableFilters:WorkPackageViewFiltersService, - protected wpTableSum:WorkPackageViewSumService, - protected wpTableTimeline:WorkPackageViewTimelineService, - protected wpTableHierarchies:WorkPackageViewHierarchiesService, - protected wpTableHighlighting:WorkPackageViewHighlightingService, - protected wpTableRelationColumns:WorkPackageViewRelationColumnsService, - protected wpTablePagination:WorkPackageViewPaginationService, - protected wpTableOrder:WorkPackageViewOrderService, - protected wpTableAdditionalElements:WorkPackageViewAdditionalElementsService, - protected apiV3Service:ApiV3Service, - protected wpListChecksumService:WorkPackagesListChecksumService, - protected authorisationService:AuthorisationService, - protected wpDisplayRepresentation:WorkPackageViewDisplayRepresentationService, - protected wpIncludeSubprojects:WorkPackageViewIncludeSubprojectsService, - protected wpTimestamps:WorkPackageViewBaselineService, - ) { } + protected states = inject(States); + protected querySpace = inject(IsolatedQuerySpace); + protected wpTableColumns = inject(WorkPackageViewColumnsService); + protected wpTableGroupBy = inject(WorkPackageViewGroupByService); + protected wpTableGroupFold = inject(WorkPackageViewCollapsedGroupsService); + protected wpTableSortBy = inject(WorkPackageViewSortByService); + protected wpTableFilters = inject(WorkPackageViewFiltersService); + protected wpTableSum = inject(WorkPackageViewSumService); + protected wpTableTimeline = inject(WorkPackageViewTimelineService); + protected wpTableHierarchies = inject(WorkPackageViewHierarchiesService); + protected wpTableHighlighting = inject(WorkPackageViewHighlightingService); + protected wpTableRelationColumns = inject(WorkPackageViewRelationColumnsService); + protected wpTablePagination = inject(WorkPackageViewPaginationService); + protected wpTableOrder = inject(WorkPackageViewOrderService); + protected wpTableAdditionalElements = inject(WorkPackageViewAdditionalElementsService); + protected apiV3Service = inject(ApiV3Service); + protected wpListChecksumService = inject(WorkPackagesListChecksumService); + protected authorisationService = inject(AuthorisationService); + protected wpDisplayRepresentation = inject(WorkPackageViewDisplayRepresentationService); + protected wpIncludeSubprojects = inject(WorkPackageViewIncludeSubprojectsService); + protected wpTimestamps = inject(WorkPackageViewBaselineService); + /** * Initialize the query and table states from the given query and results. diff --git a/frontend/src/app/features/work-packages/components/wp-new/wp-create.component.ts b/frontend/src/app/features/work-packages/components/wp-new/wp-create.component.ts index 340847154b2..2a7dc88f0b4 100644 --- a/frontend/src/app/features/work-packages/components/wp-new/wp-create.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-new/wp-create.component.ts @@ -26,14 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectorRef, - Directive, - Injector, - Input, - OnInit, - ViewChild, OnDestroy, -} from '@angular/core'; +import { ChangeDetectorRef, Directive, Injector, Input, OnInit, ViewChild, OnDestroy, inject } from '@angular/core'; import { StateService, Transition, @@ -63,6 +56,20 @@ import { HalSource } from 'core-app/features/hal/interfaces'; @Directive() export class WorkPackageCreateComponent extends UntilDestroyedMixin implements OnInit, OnDestroy { + readonly injector = inject(Injector); + protected readonly $state = inject(StateService); + protected readonly I18n = inject(I18nService); + protected readonly titleService = inject(OpTitleService); + protected readonly notificationService = inject(WorkPackageNotificationService); + protected readonly states = inject(States); + protected readonly wpCreate = inject(WorkPackageCreateService); + protected readonly wpViewFocus = inject(WorkPackageViewFocusService); + protected readonly wpTableFilters = inject(WorkPackageViewFiltersService); + protected readonly pathHelper = inject(PathHelperService); + protected readonly apiV3Service = inject(ApiV3Service); + protected readonly currentProjectService = inject(CurrentProjectService); + protected readonly cdRef = inject(ChangeDetectorRef); + public successState:string = splitViewRoute(this.$state); public cancelState:string = this.$state?.current?.data?.baseRoute; @@ -89,24 +96,6 @@ export class WorkPackageCreateComponent extends UntilDestroyedMixin implements O /** Explicitly remember destroy state in this abstract base */ protected destroyed = false; - constructor( - public readonly injector:Injector, - protected readonly $state:StateService, - protected readonly I18n:I18nService, - protected readonly titleService:OpTitleService, - protected readonly notificationService:WorkPackageNotificationService, - protected readonly states:States, - protected readonly wpCreate:WorkPackageCreateService, - protected readonly wpViewFocus:WorkPackageViewFocusService, - protected readonly wpTableFilters:WorkPackageViewFiltersService, - protected readonly pathHelper:PathHelperService, - protected readonly apiV3Service:ApiV3Service, - protected readonly currentProjectService:CurrentProjectService, - protected readonly cdRef:ChangeDetectorRef, - ) { - super(); - } - public ngOnInit() { // In case the create form is still routed via Angular, the stateParams are empty. We then read the params from the Transition if (this.routedFromAngular) { diff --git a/frontend/src/app/features/work-packages/components/wp-new/wp-create.service.ts b/frontend/src/app/features/work-packages/components/wp-new/wp-create.service.ts index 99bbcf29a6d..ed928939038 100644 --- a/frontend/src/app/features/work-packages/components/wp-new/wp-create.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-new/wp-create.service.ts @@ -26,10 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - Injectable, - Injector, -} from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { firstValueFrom, Observable, @@ -68,23 +65,23 @@ export const newWorkPackageHref = '/api/v3/work_packages/new'; @Injectable() export class WorkPackageCreateService extends UntilDestroyedMixin { + protected injector = inject(Injector); + protected hooks = inject(HookService); + protected apiV3Service = inject(ApiV3Service); + protected halResourceService = inject(HalResourceService); + protected querySpace = inject(IsolatedQuerySpace); + protected authorisationService = inject(AuthorisationService); + protected halEditing = inject(HalResourceEditingService); + protected schemaCache = inject(SchemaCacheService); + protected halEvents = inject(HalEventsService); + protected attachmentsService = inject(AttachmentsResourceService); + protected form:Promise|undefined; // Allow callbacks to happen on newly created work packages protected newWorkPackageCreatedSubject = new Subject(); - constructor( - protected injector:Injector, - protected hooks:HookService, - protected apiV3Service:ApiV3Service, - protected halResourceService:HalResourceService, - protected querySpace:IsolatedQuerySpace, - protected authorisationService:AuthorisationService, - protected halEditing:HalResourceEditingService, - protected schemaCache:SchemaCacheService, - protected halEvents:HalEventsService, - protected attachmentsService:AttachmentsResourceService, - ) { + constructor() { super(); this.halEditing diff --git a/frontend/src/app/features/work-packages/components/wp-query/query-filters.service.ts b/frontend/src/app/features/work-packages/components/wp-query/query-filters.service.ts index ef0a450e64a..ea889854dff 100644 --- a/frontend/src/app/features/work-packages/components/wp-query/query-filters.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-query/query-filters.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { QueryFormResource } from 'core-app/features/hal/resources/query-form-resource'; import { QueryFilterInstanceSchemaResource, @@ -11,8 +11,8 @@ import { CollectionResource } from 'core-app/features/hal/resources/collection-r @Injectable() export class QueryFiltersService { - constructor(protected schemaCache:SchemaCacheService) { - } + protected schemaCache = inject(SchemaCacheService); + /** * Get the matching schema of the filter resource diff --git a/frontend/src/app/features/work-packages/components/wp-query/query-param-listener.service.ts b/frontend/src/app/features/work-packages/components/wp-query/query-param-listener.service.ts index adf14df4d0c..c10d297612f 100644 --- a/frontend/src/app/features/work-packages/components/wp-query/query-param-listener.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-query/query-param-listener.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { WorkPackagesListChecksumService } from 'core-app/features/work-packages/components/wp-list/wp-list-checksum.service'; import { WorkPackagesListService } from 'core-app/features/work-packages/components/wp-list/wp-list.service'; import { TransitionService } from '@uirouter/core'; @@ -34,6 +34,8 @@ import { Subject } from 'rxjs'; @Injectable() export class QueryParamListenerService { + readonly injector = inject(Injector); + readonly wpListChecksumService:WorkPackagesListChecksumService = this.injector.get(WorkPackagesListChecksumService); readonly wpListService:WorkPackagesListService = this.injector.get(WorkPackagesListService); @@ -44,7 +46,7 @@ export class QueryParamListenerService { public queryChangeListener:Function; - constructor(readonly injector:Injector) { + constructor() { this.listenForQueryParamsChanged(); } diff --git a/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.spec.ts b/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.spec.ts index 0ca8f1bc024..0a94e526349 100644 --- a/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.spec.ts +++ b/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.spec.ts @@ -26,16 +26,29 @@ // See COPYRIGHT and LICENSE files for more details. //++ +import { TestBed } from '@angular/core/testing'; import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper'; +import { PaginationService } from 'core-app/shared/components/table-pagination/pagination-service'; describe('UrlParamsHelper', () => { const paginationStub = { getPerPage: () => 20, } as any; - const UrlParamsHelper = new UrlParamsHelperService(paginationStub); + let UrlParamsHelper:UrlParamsHelperService; let queryString; + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + UrlParamsHelperService, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + { provide: PaginationService, useValue: paginationStub }, + ], + }); + UrlParamsHelper = TestBed.inject(UrlParamsHelperService); + }); + describe('buildQueryString', () => { const params = { ids: [1, 2, 3], diff --git a/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.ts b/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.ts index 79478a48dab..1ad8c474663 100644 --- a/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.ts +++ b/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.ts @@ -31,7 +31,7 @@ import { QuerySortByResource } from 'core-app/features/hal/resources/query-sort- import { HalLink } from 'core-app/features/hal/hal-link/hal-link'; import idFromLink from 'core-app/features/hal/helpers/id-from-link'; import isPersistedResource from 'core-app/features/hal/helpers/is-persisted-resource'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/query-filter-instance-resource'; import { ApiV3Filter, ApiV3FilterBuilder, FilterOperator } from 'core-app/shared/helpers/api-v3/api-v3-filter-builder'; import { PaginationService } from 'core-app/shared/components/table-pagination/pagination-service'; @@ -102,8 +102,8 @@ export interface QueryRequestParams { @Injectable({ providedIn: 'root' }) export class UrlParamsHelperService { - public constructor(public paginationService:PaginationService) { - } + paginationService = inject(PaginationService); + // copied more or less from angular buildUrl public buildQueryString(params:any) { diff --git a/frontend/src/app/features/work-packages/components/wp-relations/embedded/children/wp-children-inline-create.service.ts b/frontend/src/app/features/work-packages/components/wp-relations/embedded/children/wp-children-inline-create.service.ts index 633708ee788..93926348376 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/embedded/children/wp-children-inline-create.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/embedded/children/wp-children-inline-create.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { WorkPackageRelationsHierarchyService } from 'core-app/features/work-packages/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service'; import { WorkPackageInlineCreateService } from 'core-app/features/work-packages/components/wp-inline-create/wp-inline-create.service'; @@ -41,11 +41,8 @@ import idFromLink from 'core-app/features/hal/helpers/id-from-link'; @Injectable() export class WpChildrenInlineCreateService extends WorkPackageInlineCreateService implements WpRelationInlineCreateServiceInterface { - constructor(readonly injector:Injector, - protected readonly wpRelationsHierarchyService:WorkPackageRelationsHierarchyService, - protected readonly schemaCache:SchemaCacheService) { - super(injector); - } + protected readonly wpRelationsHierarchyService = inject(WorkPackageRelationsHierarchyService); + protected readonly schemaCache = inject(SchemaCacheService); /** * A separate reference pane for the inline create component diff --git a/frontend/src/app/features/work-packages/components/wp-relations/embedded/children/wp-children-query.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/embedded/children/wp-children-query.component.ts index d0ffc9275db..1bd82798aa0 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/embedded/children/wp-children-query.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/embedded/children/wp-children-query.component.ts @@ -26,11 +26,10 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; -import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper'; import { WorkPackageRelationsHierarchyService } from 'core-app/features/work-packages/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service'; import { OpUnlinkTableAction } from 'core-app/features/work-packages/components/wp-table/table-actions/actions/unlink-table-action'; import { OpTableActionFactory } from 'core-app/features/work-packages/components/wp-table/table-actions/table-action'; @@ -58,6 +57,14 @@ import { WorkPackageRelationsService } from 'core-app/features/work-packages/com changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageChildrenQueryComponent extends WorkPackageRelationQueryBase implements OnInit { + protected wpRelationsHierarchyService = inject(WorkPackageRelationsHierarchyService); + protected PathHelper = inject(PathHelperService); + protected wpInlineCreate = inject(WorkPackageInlineCreateService); + protected halEvents = inject(HalEventsService); + protected apiV3Service = inject(ApiV3Service); + readonly I18n = inject(I18nService); + readonly wpRelations = inject(WorkPackageRelationsService); + @Input() public workPackage:WorkPackageResource; @Input() public query:QueryResource; @@ -82,19 +89,6 @@ export class WorkPackageChildrenQueryComponent extends WorkPackageRelationQueryB ), ]; - constructor( - protected wpRelationsHierarchyService:WorkPackageRelationsHierarchyService, - protected PathHelper:PathHelperService, - protected wpInlineCreate:WorkPackageInlineCreateService, - protected halEvents:HalEventsService, - protected apiV3Service:ApiV3Service, - protected queryUrlParamsHelper:UrlParamsHelperService, - readonly I18n:I18nService, - readonly wpRelations:WorkPackageRelationsService, - ) { - super(queryUrlParamsHelper); - } - ngOnInit() { // Set reference target and reference class this.wpInlineCreate.referenceTarget = this.workPackage; diff --git a/frontend/src/app/features/work-packages/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts index 3057f20973b..7f814538a47 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageInlineCreateService, @@ -61,6 +61,16 @@ import { FilterOperator } from 'core-app/shared/helpers/api-v3/api-v3-filter-bui changeDetection: ChangeDetectionStrategy.Default, }) export class WpRelationInlineAddExistingComponent { + protected readonly parent = inject(WorkPackageInlineCreateComponent); + protected readonly wpInlineCreate = inject(WorkPackageInlineCreateService) as WpRelationInlineCreateServiceInterface; + protected apiV3Service = inject(ApiV3Service); + protected wpRelations = inject(WorkPackageRelationsService); + protected notificationService = inject(WorkPackageNotificationService); + protected halEvents = inject(HalEventsService); + protected urlParamsHelper = inject(UrlParamsHelperService); + protected querySpace = inject(IsolatedQuerySpace); + protected readonly I18n = inject(I18nService); + public selectedWpId:string; public isDisabled = false; @@ -71,18 +81,6 @@ export class WpRelationInlineAddExistingComponent { abort: this.I18n.t('js.relation_buttons.abort'), }; - constructor( - protected readonly parent:WorkPackageInlineCreateComponent, - @Inject(WorkPackageInlineCreateService) protected readonly wpInlineCreate:WpRelationInlineCreateServiceInterface, - protected apiV3Service:ApiV3Service, - protected wpRelations:WorkPackageRelationsService, - protected notificationService:WorkPackageNotificationService, - protected halEvents:HalEventsService, - protected urlParamsHelper:UrlParamsHelperService, - protected querySpace:IsolatedQuerySpace, - protected readonly I18n:I18nService, - ) {} - public addExisting() { if (_.isNil(this.selectedWpId)) { return; diff --git a/frontend/src/app/features/work-packages/components/wp-relations/embedded/relations/wp-relation-inline-create.service.ts b/frontend/src/app/features/work-packages/components/wp-relations/embedded/relations/wp-relation-inline-create.service.ts index df66e0976a4..8352fad2383 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/embedded/relations/wp-relation-inline-create.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/embedded/relations/wp-relation-inline-create.service.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { Injectable, Injector, OnDestroy } from '@angular/core'; +import { Injectable, OnDestroy } from '@angular/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { WorkPackageInlineCreateService } from 'core-app/features/work-packages/components/wp-inline-create/wp-inline-create.service'; import { WpRelationInlineAddExistingComponent } from 'core-app/features/work-packages/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component'; @@ -46,10 +46,6 @@ export class WpRelationInlineCreateService extends WorkPackageInlineCreateServic @InjectField() halEvents:HalEventsService; - constructor(public injector:Injector) { - super(injector); - } - /** * A separate reference pane for the inline create component */ diff --git a/frontend/src/app/features/work-packages/components/wp-relations/embedded/relations/wp-relation-query.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/embedded/relations/wp-relation-query.component.ts index 30a888bb6ce..35a6d33b7c9 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/embedded/relations/wp-relation-query.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/embedded/relations/wp-relation-query.component.ts @@ -26,17 +26,10 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - Inject, - Input, - OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; -import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper'; import { OpUnlinkTableAction } from 'core-app/features/work-packages/components/wp-table/table-actions/actions/unlink-table-action'; import { OpTableActionFactory } from 'core-app/features/work-packages/components/wp-table/table-actions/table-action'; import { WorkPackageInlineCreateService } from 'core-app/features/work-packages/components/wp-inline-create/wp-inline-create.service'; @@ -63,6 +56,13 @@ import { GroupDescriptor } from 'core-app/features/work-packages/components/wp-s changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageRelationQueryComponent extends WorkPackageRelationQueryBase implements OnInit { + protected readonly PathHelper = inject(PathHelperService); + protected readonly wpInlineCreate = inject(WorkPackageInlineCreateService) as WpRelationInlineCreateService; + protected readonly wpRelations = inject(WorkPackageRelationsService); + protected readonly halEvents = inject(HalEventsService); + protected readonly notificationService = inject(WorkPackageNotificationService); + protected readonly I18n = inject(I18nService); + @Input() public workPackage:WorkPackageResource; @Input() public query:QueryResource; @@ -89,16 +89,6 @@ export class WorkPackageRelationQueryComponent extends WorkPackageRelationQueryB public idFromLink = idFromLink; - constructor(protected readonly PathHelper:PathHelperService, - @Inject(WorkPackageInlineCreateService) protected readonly wpInlineCreate:WpRelationInlineCreateService, - protected readonly wpRelations:WorkPackageRelationsService, - protected readonly halEvents:HalEventsService, - protected readonly queryUrlParamsHelper:UrlParamsHelperService, - protected readonly notificationService:WorkPackageNotificationService, - protected readonly I18n:I18nService) { - super(queryUrlParamsHelper); - } - ngOnInit() { const relationType = this.getRelationTypeFromQuery(); diff --git a/frontend/src/app/features/work-packages/components/wp-relations/embedded/wp-relation-query.base.ts b/frontend/src/app/features/work-packages/components/wp-relations/embedded/wp-relation-query.base.ts index 890b0176532..8a97dcb5b12 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/embedded/wp-relation-query.base.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/embedded/wp-relation-query.base.ts @@ -27,7 +27,7 @@ //++ import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; -import { Directive, ViewChild } from '@angular/core'; +import { Directive, ViewChild, inject } from '@angular/core'; import { WorkPackageEmbeddedTableComponent } from 'core-app/features/work-packages/components/wp-table/embedded/wp-embedded-table.component'; import { QueryResource } from 'core-app/features/hal/resources/query-resource'; import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper'; @@ -35,6 +35,8 @@ import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destr @Directive() export abstract class WorkPackageRelationQueryBase extends UntilDestroyedMixin { + protected queryUrlParamsHelper = inject(UrlParamsHelperService); + public workPackage:WorkPackageResource; /** Input is either a query resource, or directly query props */ @@ -49,10 +51,6 @@ export abstract class WorkPackageRelationQueryBase extends UntilDestroyedMixin { /** Reference to the embedded table instance */ @ViewChild('embeddedTable') protected embeddedTable?:WorkPackageEmbeddedTableComponent; - constructor(protected queryUrlParamsHelper:UrlParamsHelperService) { - super(); - } - /** * Request to refresh the results of the embedded table */ diff --git a/frontend/src/app/features/work-packages/components/wp-relations/wp-relation-row/wp-relation-row.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/wp-relation-row/wp-relation-row.component.ts index 2fafea2ad62..0ac5e207e7e 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/wp-relation-row/wp-relation-row.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/wp-relation-row/wp-relation-row.component.ts @@ -1,8 +1,6 @@ import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnInit, ViewChild, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnInit, ViewChild, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { HalEventsService } from 'core-app/features/hal/services/hal-events.service'; import { WorkPackageNotificationService } from 'core-app/features/work-packages/services/notifications/work-package-notification.service'; @@ -23,6 +21,14 @@ import { Highlighting } from 'core-app/features/work-packages/components/wp-fast changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageRelationRowComponent extends UntilDestroyedMixin implements OnInit { + protected apiV3Service = inject(ApiV3Service); + protected notificationService = inject(WorkPackageNotificationService); + protected wpRelations = inject(WorkPackageRelationsService); + protected halEvents = inject(HalEventsService); + protected I18n = inject(I18nService); + protected cdRef = inject(ChangeDetectorRef); + protected PathHelper = inject(PathHelperService); + @Input() public workPackage:WorkPackageResource; @Input() public relatedWorkPackage:WorkPackageResource; @@ -70,16 +76,6 @@ export class WorkPackageRelationRowComponent extends UntilDestroyedMixin impleme }, }; - constructor(protected apiV3Service:ApiV3Service, - protected notificationService:WorkPackageNotificationService, - protected wpRelations:WorkPackageRelationsService, - protected halEvents:HalEventsService, - protected I18n:I18nService, - protected cdRef:ChangeDetectorRef, - protected PathHelper:PathHelperService) { - super(); - } - ngOnInit() { this.relation = this.relatedWorkPackage.relatedBy!; diff --git a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-create/wp-relations-create.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-create/wp-relations-create.component.ts index 3d2c1564cd0..8316aff3efa 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-create/wp-relations-create.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-create/wp-relations-create.component.ts @@ -1,10 +1,5 @@ import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { HalEventsService } from 'core-app/features/hal/services/hal-events.service'; import { WorkPackageNotificationService } from 'core-app/features/work-packages/services/notifications/work-package-notification.service'; @@ -21,6 +16,12 @@ import { WorkPackageRelationsService } from '../wp-relations.service'; changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageRelationsCreateComponent { + readonly I18n = inject(I18nService); + protected wpRelations = inject(WorkPackageRelationsService); + protected notificationService = inject(WorkPackageNotificationService); + protected halEvents = inject(HalEventsService); + protected cdRef = inject(ChangeDetectorRef); + @Input() readonly workPackage:WorkPackageResource; public showRelationsCreateForm = false; @@ -39,15 +40,6 @@ export class WorkPackageRelationsCreateComponent { addNewRelation: this.I18n.t('js.relation_buttons.add_new_relation'), }; - constructor( - readonly I18n:I18nService, - protected wpRelations:WorkPackageRelationsService, - protected notificationService:WorkPackageNotificationService, - protected halEvents:HalEventsService, - protected cdRef:ChangeDetectorRef, - ) { - } - public onSelected(workPackage?:WorkPackageResource) { if (workPackage) { this.selectedWpId = workPackage.id!; diff --git a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-group/wp-relations-group.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-group/wp-relations-group.component.ts index 7b1390eb61a..2cbbebb564b 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-group/wp-relations-group.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-group/wp-relations-group.component.ts @@ -27,9 +27,7 @@ //++ import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; -import { - ChangeDetectionStrategy, Component, ElementRef, EventEmitter, HostBinding, Input, Output, ViewChild, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, HostBinding, Input, Output, ViewChild, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @Component({ @@ -42,6 +40,8 @@ import { I18nService } from 'core-app/core/i18n/i18n.service'; changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageRelationsGroupComponent { + readonly I18n = inject(I18nService); + @HostBinding('class.attributes-group') className = true; @Input() public relatedWorkPackages:WorkPackageResource[]; @@ -63,11 +63,6 @@ export class WorkPackageRelationsGroupComponent { groupByRelation: this.I18n.t('js.relation_buttons.group_by_relation_type'), }; - constructor( - readonly I18n:I18nService, - ) { - } - public get togglerText() { if (this.groupByWorkPackageType) { return this.text.groupByRelation; diff --git a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts index c7f52ec6bdf..75a54892d71 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; @@ -49,6 +49,12 @@ import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service'; changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageRelationsHierarchyComponent extends UntilDestroyedMixin implements OnInit { + readonly apiV3Service = inject(ApiV3Service); + readonly PathHelper = inject(PathHelperService); + readonly I18n = inject(I18nService); + readonly schemaCache = inject(SchemaCacheService); + readonly cdRef = inject(ChangeDetectorRef); + @Input() public workPackage:WorkPackageResource; @Input() public relationType:string; @@ -65,16 +71,6 @@ export class WorkPackageRelationsHierarchyComponent extends UntilDestroyedMixin public childrenQueryProps:any; - constructor( - readonly apiV3Service:ApiV3Service, - readonly PathHelper:PathHelperService, - readonly I18n:I18nService, - readonly schemaCache:SchemaCacheService, - readonly cdRef:ChangeDetectorRef, - ) { - super(); - } - public text = { parentHeadline: this.I18n.t('js.relations_hierarchy.parent_headline'), childrenHeadline: this.I18n.t('js.relations_hierarchy.children_headline'), diff --git a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts index bb71ba67f38..7ba2ea95967 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts @@ -29,7 +29,7 @@ import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { States } from 'core-app/core/states/states.service'; import { StateService } from '@uirouter/core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { WorkPackageNotificationService } from 'core-app/features/work-packages/services/notifications/work-package-notification.service'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; @@ -37,14 +37,13 @@ import { HalEventsService } from 'core-app/features/hal/services/hal-events.serv @Injectable() export class WorkPackageRelationsHierarchyService { - constructor(protected $state:StateService, - protected states:States, - protected halEvents:HalEventsService, - protected notificationService:WorkPackageNotificationService, - protected pathHelper:PathHelperService, - protected apiV3Service:ApiV3Service) { + protected $state = inject(StateService); + protected states = inject(States); + protected halEvents = inject(HalEventsService); + protected notificationService = inject(WorkPackageNotificationService); + protected pathHelper = inject(PathHelperService); + protected apiV3Service = inject(ApiV3Service); - } public changeParent(workPackage:WorkPackageResource, parentId:string|null) { const payload:any = { diff --git a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.component.ts index b256f51705a..24ba6141e12 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.component.ts @@ -26,16 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - AfterViewInit, - ChangeDetectionStrategy, - Component, - ElementRef, - Input, - OnDestroy, - OnInit, - ViewChild, -} from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild, inject } from '@angular/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { filter, throttleTime } from 'rxjs/operators'; import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; @@ -53,6 +44,12 @@ import { HalEventsService } from 'core-app/features/hal/services/hal-events.serv standalone: false, }) export class WorkPackageRelationsComponent extends UntilDestroyedMixin implements OnInit, AfterViewInit, OnDestroy { + private wpRelations = inject(WorkPackageRelationsService); + private apiV3Service = inject(ApiV3Service); + private halEvents = inject(HalEventsService); + private PathHelper = inject(PathHelperService); + private turboRequests = inject(TurboRequestsService); + @Input() public workPackage:WorkPackageResource; @ViewChild('frameElement') readonly relationTurboFrame:ElementRef; @@ -61,16 +58,6 @@ export class WorkPackageRelationsComponent extends UntilDestroyedMixin implement private turboFrameListener:EventListener = this.updateFrontendData.bind(this); - constructor( - private wpRelations:WorkPackageRelationsService, - private apiV3Service:ApiV3Service, - private halEvents:HalEventsService, - private PathHelper:PathHelperService, - private turboRequests:TurboRequestsService, -) { - super(); - } - ngOnInit() { this.turboFrameSrc = `${this.PathHelper.staticBase}/work_packages/${this.workPackage.id}/relations_tab`; } diff --git a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.service.ts b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.service.ts index 900e49258c4..c84dadaf40d 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.service.ts @@ -1,7 +1,7 @@ import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { multiInput, MultiInputState, StatesGroup } from '@openproject/reactivestates'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { StateCacheService } from 'core-app/core/apiv3/cache/state-cache.service'; @@ -26,12 +26,12 @@ export class RelationStateGroup extends StatesGroup { @Injectable() export class WorkPackageRelationsService extends StateCacheService { - constructor( - private PathHelper:PathHelperService, - private apiV3Service:ApiV3Service, - private halResource:HalResourceService, - readonly turboRequests:TurboRequestsService, - ) { + private PathHelper = inject(PathHelperService); + private apiV3Service = inject(ApiV3Service); + private halResource = inject(HalResourceService); + readonly turboRequests = inject(TurboRequestsService); + + constructor() { super(new RelationStateGroup().relations); } diff --git a/frontend/src/app/features/work-packages/components/wp-reminder-modal/wp-reminder.modal.ts b/frontend/src/app/features/work-packages/components/wp-reminder-modal/wp-reminder.modal.ts index 089fdaaf5bb..c7d5d4a2bb1 100644 --- a/frontend/src/app/features/work-packages/components/wp-reminder-modal/wp-reminder.modal.ts +++ b/frontend/src/app/features/work-packages/components/wp-reminder-modal/wp-reminder.modal.ts @@ -1,15 +1,5 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Inject, - OnInit, - ViewChild, AfterViewInit, OnDestroy, -} from '@angular/core'; -import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types'; +import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild, AfterViewInit, OnDestroy, inject } from '@angular/core'; import { OpModalComponent } from 'core-app/shared/components/modal/modal.component'; -import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; @@ -28,6 +18,11 @@ import { CollectionResource } from 'core-app/features/hal/resources/collection-r standalone: false, }) export class WorkPackageReminderModalComponent extends OpModalComponent implements OnInit, AfterViewInit, OnDestroy { + readonly I18n = inject(I18nService); + readonly pathHelper = inject(PathHelperService); + readonly actions$ = inject(ActionsService); + readonly apiV3Service = inject(ApiV3Service); + @ViewChild('frameElement') frameElement:ElementRef; // Hide close button so it's not duplicated in primer (WP#51699) @@ -48,16 +43,8 @@ export class WorkPackageReminderModalComponent extends OpModalComponent implemen private boundListener = this.turboSubmitEndListener.bind(this); - constructor( - @Inject(OpModalLocalsToken) public locals:OpModalLocalsMap, - readonly cdRef:ChangeDetectorRef, - readonly I18n:I18nService, - readonly elementRef:ElementRef, - readonly pathHelper:PathHelperService, - readonly actions$:ActionsService, - readonly apiV3Service:ApiV3Service, - ) { - super(locals, cdRef, elementRef); + constructor() { + super(); this.workPackage = this.locals.workPackage as WorkPackageResource; this.preset = this.locals.preset as ReminderPreset | undefined; diff --git a/frontend/src/app/features/work-packages/components/wp-share-modal/wp-share.modal.ts b/frontend/src/app/features/work-packages/components/wp-share-modal/wp-share.modal.ts index fe81b4d0b2b..5cb321d3730 100644 --- a/frontend/src/app/features/work-packages/components/wp-share-modal/wp-share.modal.ts +++ b/frontend/src/app/features/work-packages/components/wp-share-modal/wp-share.modal.ts @@ -1,15 +1,5 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Inject, - OnInit, - ViewChild, -} from '@angular/core'; -import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types'; +import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild, inject } from '@angular/core'; import { OpModalComponent } from 'core-app/shared/components/modal/modal.component'; -import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; @@ -23,6 +13,10 @@ import { shareModalUpdated } from 'core-app/features/work-packages/components/wp standalone: false, }) export class WorkPackageShareModalComponent extends OpModalComponent implements OnInit { + readonly I18n = inject(I18nService); + readonly pathHelper = inject(PathHelperService); + readonly actions$ = inject(ActionsService); + @ViewChild('frameElement') frameElement:ElementRef|undefined; // Hide close button so it's not duplicated in primer (WP#51699) @@ -36,15 +30,8 @@ export class WorkPackageShareModalComponent extends OpModalComponent implements button_close: this.I18n.t('js.button_close'), }; - constructor( - @Inject(OpModalLocalsToken) public locals:OpModalLocalsMap, - readonly cdRef:ChangeDetectorRef, - readonly I18n:I18nService, - readonly elementRef:ElementRef, - readonly pathHelper:PathHelperService, - readonly actions$:ActionsService, - ) { - super(locals, cdRef, elementRef); + constructor() { + super(); this.workPackage = this.locals.workPackage as WorkPackageResource; this.frameSrc = this.pathHelper.workPackageSharePath(this.workPackage.id!); diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/activity-base.controller.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/activity-base.controller.ts index 01cb30fbf20..57c089846f9 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/activity-base.controller.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/activity-base.controller.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectorRef, Directive, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Directive, OnInit, inject } from '@angular/core'; import { UIRouterGlobals } from '@uirouter/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @@ -40,25 +40,21 @@ import { UrlHelpers } from 'core-stimulus/controllers/dynamic/work-packages/acti @Directive() export class ActivityPanelBaseController extends UntilDestroyedMixin implements OnInit { + readonly apiV3Service = inject(ApiV3Service); + readonly I18n = inject(I18nService); + readonly cdRef = inject(ChangeDetectorRef); + readonly uiRouterGlobals = inject(UIRouterGlobals); + readonly storeService = inject(WpSingleViewService); + readonly browserDetector = inject(BrowserDetector); + readonly deviceService = inject(DeviceService); + readonly pathHelper = inject(PathHelperService); + public workPackage:WorkPackageResource; public workPackageId:string; public turboFrameSrc:string; - constructor( - readonly apiV3Service:ApiV3Service, - readonly I18n:I18nService, - readonly cdRef:ChangeDetectorRef, - readonly uiRouterGlobals:UIRouterGlobals, - readonly storeService:WpSingleViewService, - readonly browserDetector:BrowserDetector, - readonly deviceService:DeviceService, - readonly pathHelper:PathHelperService, - ) { - super(); - } - ngOnInit():void { this.turboFrameSrc = this.buildTurboFrameSrc(); } diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/wp-activity.service.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/wp-activity.service.ts index 91d333353b0..21748e04203 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/wp-activity.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/wp-activity.service.ts @@ -28,17 +28,16 @@ import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { ConfigurationService } from 'core-app/core/config/configuration.service'; import { WorkPackageLinkedResourceCache } from 'core-app/features/work-packages/components/wp-single-view-tabs/wp-linked-resource-cache.service'; import { TimezoneService } from 'core-app/core/datetime/timezone.service'; @Injectable() export class WorkPackagesActivityService extends WorkPackageLinkedResourceCache { - constructor(public ConfigurationService:ConfigurationService, - readonly timezoneService:TimezoneService) { - super(); - } + ConfigurationService = inject(ConfigurationService); + readonly timezoneService = inject(TimezoneService); + public get order() { return this.isReversed ? 'desc' : 'asc'; diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts index 9f668e02382..73a482cd7fb 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/files-tab/op-files-tab.component.ts @@ -26,12 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - Input, - OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit, inject } from '@angular/core'; import { combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -51,6 +46,12 @@ import { PathHelperService } from 'core-app/core/path-helper/path-helper.service standalone: false, }) export class WorkPackageFilesTabComponent implements OnInit { + private readonly i18n = inject(I18nService); + private readonly currentUserService = inject(CurrentUserService); + private readonly projectStoragesResourceService = inject(ProjectStoragesResourceService); + private readonly pathHelper = inject(PathHelperService); + private readonly turboRequests = inject(TurboRequestsService); + @Input() workPackage:WorkPackageResource; text = { @@ -67,14 +68,6 @@ export class WorkPackageFilesTabComponent implements OnInit { showAttachments:boolean; - constructor( - private readonly i18n:I18nService, - private readonly currentUserService:CurrentUserService, - private readonly projectStoragesResourceService:ProjectStoragesResourceService, - private readonly pathHelper:PathHelperService, - private readonly turboRequests:TurboRequestsService, - ) { } - ngOnInit():void { const project = this.workPackage.project as HalResource; if (project.id === null) { diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service.spec.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service.spec.ts index 4344381b8d4..ba6dbaf4244 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service.spec.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service.spec.ts @@ -26,6 +26,12 @@ // See COPYRIGHT and LICENSE files for more details. //++ +import { TestBed } from '@angular/core/testing'; +import { + StateService, + TransitionService, + UIRouterGlobals, +} from '@uirouter/core'; import { KeepTabService } from './keep-tab.service'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; @@ -57,7 +63,20 @@ describe('keepTab service', () => { params: { tabIdentifier: 'activity' }, }; - keepTab = new KeepTabService($state, uiRouterGlobals, $transitions, pathHelper, currentProject); + TestBed.configureTestingModule({ + providers: [ + KeepTabService, + /* eslint-disable @typescript-eslint/no-unsafe-assignment */ + { provide: StateService, useValue: $state }, + { provide: UIRouterGlobals, useValue: uiRouterGlobals }, + { provide: TransitionService, useValue: $transitions }, + { provide: PathHelperService, useValue: pathHelper }, + { provide: CurrentProjectService, useValue: currentProject }, + /* eslint-enable @typescript-eslint/no-unsafe-assignment */ + ], + }); + + keepTab = TestBed.inject(KeepTabService); defaults = { showTab: 'work-packages.show.tabs', diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service.ts index d86cf6c42ae..93286856f52 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service.ts @@ -33,24 +33,26 @@ import { UIRouterGlobals, } from '@uirouter/core'; import { ReplaySubject } from 'rxjs'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { splitViewRoute } from 'core-app/features/work-packages/routing/split-view-routes.helper'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; @Injectable({ providedIn: 'root' }) export class KeepTabService { + protected $state = inject(StateService); + protected uiRouterGlobals = inject(UIRouterGlobals); + protected $transitions = inject(TransitionService); + protected pathHelper = inject(PathHelperService); + protected currentProject = inject(CurrentProjectService); + protected currentTab = 'overview'; protected subject = new ReplaySubject>(1); - constructor( - protected $state:StateService, - protected uiRouterGlobals:UIRouterGlobals, - protected $transitions:TransitionService, - protected pathHelper:PathHelperService, - protected currentProject:CurrentProjectService, - ) { + constructor() { + const $transitions = this.$transitions; + this.updateTabs(); $transitions.onSuccess({}, (transition:Transition) => { this.updateTabs(transition.params('to').tabIdentifier); diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/overview-tab/overview-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/overview-tab/overview-tab.component.ts index 8ebc7a6c611..848b9371151 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/overview-tab/overview-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/overview-tab/overview-tab.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, inject } from '@angular/core'; import { StateService } from '@uirouter/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @@ -43,21 +43,17 @@ import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageOverviewTabComponent extends UntilDestroyedMixin implements OnInit { + readonly I18n = inject(I18nService); + readonly $state = inject(StateService); + readonly apiV3Service = inject(ApiV3Service); + readonly cdRef = inject(ChangeDetectorRef); + @Input() public workPackage:WorkPackageResource; public workPackageId:string; public tabName = this.I18n.t('js.label_latest_activity'); - public constructor( - readonly I18n:I18nService, - readonly $state:StateService, - readonly apiV3Service:ApiV3Service, - readonly cdRef:ChangeDetectorRef, - ) { - super(); - } - ngOnInit() { this.workPackageId = this.workPackage?.id || this.$state.params.workPackageId as string; diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/relations-tab/relations-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/relations-tab/relations-tab.component.ts index 4d2b69fbc3d..e3f1b040ea0 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/relations-tab/relations-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/relations-tab/relations-tab.component.ts @@ -27,7 +27,7 @@ //++ import { UIRouterGlobals } from '@uirouter/core'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; @@ -43,19 +43,15 @@ import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageRelationsTabComponent extends UntilDestroyedMixin implements OnInit { + readonly I18n = inject(I18nService); + readonly uiRouterGlobals = inject(UIRouterGlobals); + readonly apiV3Service = inject(ApiV3Service); + readonly cdRef = inject(ChangeDetectorRef); + @Input() public workPackageId?:string; @Input() public workPackage:WorkPackageResource; - public constructor( - readonly I18n:I18nService, - readonly uiRouterGlobals:UIRouterGlobals, - readonly apiV3Service:ApiV3Service, - readonly cdRef:ChangeDetectorRef, - ) { - super(); - } - ngOnInit() { const { workPackageId } = this.uiRouterGlobals.params as unknown as { workPackageId:string }; this.workPackageId = (this.workPackage.id!) || workPackageId; diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts index fe219032838..4889aec0550 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts @@ -26,7 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, OnInit, inject } from '@angular/core'; import { UIRouterGlobals } from '@uirouter/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; @@ -50,6 +50,17 @@ import { TurboRequestsService } from 'core-app/core/turbo/turbo-requests.service standalone: false, }) export class WorkPackageWatchersTabComponent extends UntilDestroyedMixin implements OnInit { + readonly I18n = inject(I18nService); + readonly elementRef = inject(ElementRef); + readonly wpWatchersService = inject(WorkPackageWatchersService); + readonly uiRouterGlobals = inject(UIRouterGlobals); + readonly notificationService = inject(WorkPackageNotificationService); + readonly loadingIndicator = inject(LoadingIndicatorService); + readonly cdRef = inject(ChangeDetectorRef); + readonly pathHelper = inject(PathHelperService); + readonly apiV3Service = inject(ApiV3Service); + readonly turboRequests = inject(TurboRequestsService); + @Input() public workPackage:WorkPackageResource; public workPackageId:string; @@ -78,21 +89,6 @@ export class WorkPackageWatchersTabComponent extends UntilDestroyedMixin impleme }, }; - public constructor( - readonly I18n:I18nService, - readonly elementRef:ElementRef, - readonly wpWatchersService:WorkPackageWatchersService, - readonly uiRouterGlobals:UIRouterGlobals, - readonly notificationService:WorkPackageNotificationService, - readonly loadingIndicator:LoadingIndicatorService, - readonly cdRef:ChangeDetectorRef, - readonly pathHelper:PathHelperService, - readonly apiV3Service:ApiV3Service, - readonly turboRequests:TurboRequestsService, - ) { - super(); - } - public ngOnInit() { this.element = this.elementRef.nativeElement; const { workPackageId } = this.uiRouterGlobals.params as unknown as { workPackageId:string }; diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/wp-watcher-entry.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/wp-watcher-entry.component.ts index 1315b29b2d3..b5da0555b0d 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/wp-watcher-entry.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/watchers-tab/wp-watcher-entry.component.ts @@ -26,12 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - Input, - OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { UserResource } from 'core-app/features/hal/resources/user-resource'; import { WorkPackageWatchersTabComponent } from './watchers-tab.component'; @@ -44,14 +39,13 @@ import { WorkPackageWatchersTabComponent } from './watchers-tab.component'; standalone: false, }) export class WorkPackageWatcherEntryComponent implements OnInit { + readonly I18n = inject(I18nService); + readonly panelCtrl = inject(WorkPackageWatchersTabComponent); + @Input() public watcher:UserResource; public text:{ remove:string }; - constructor(readonly I18n:I18nService, - readonly panelCtrl:WorkPackageWatchersTabComponent) { - } - ngOnInit():void { this.text = { remove: this.I18n.t('js.label_remove_watcher', { name: this.watcher.name }), diff --git a/frontend/src/app/features/work-packages/components/wp-single-view/wp-single-view.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view/wp-single-view.component.ts index 9ebbde7098e..ca4533f1b34 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view/wp-single-view.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view/wp-single-view.component.ts @@ -26,15 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - Injector, - Input, - OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Injector, Input, OnInit, inject } from '@angular/core'; import { StateService } from '@uirouter/core'; import { BehaviorSubject, combineLatest } from 'rxjs'; import { distinctUntilChanged, first, map } from 'rxjs/operators'; @@ -99,6 +91,23 @@ export const overflowingContainerAttribute = 'overflowingIdentifier'; standalone: false, }) export class WorkPackageSingleViewComponent extends UntilDestroyedMixin implements OnInit { + protected readonly injector = inject(Injector); + private readonly states = inject(States); + private readonly I18n = inject(I18nService); + private readonly hook = inject(HookService); + private readonly $state = inject(StateService); + private readonly elementRef = inject(ElementRef); + private readonly cdRef = inject(ChangeDetectorRef); + private readonly PathHelper = inject(PathHelperService); + private readonly schemaCache = inject(SchemaCacheService); + private readonly currentProject = inject(CurrentProjectService); + private readonly halEditing = inject(HalResourceEditingService); + private readonly halResourceService = inject(HalResourceService); + private readonly currentUserService = inject(CurrentUserService); + private readonly displayFieldService = inject(DisplayFieldService); + private readonly projectsResourceService = inject(ProjectsResourceService); + private readonly projectStoragesService = inject(ProjectStoragesResourceService); + @Input() public workPackage:WorkPackageResource; /** Should we show the project field */ @@ -145,27 +154,6 @@ export class WorkPackageSingleViewComponent extends UntilDestroyedMixin implemen projectStorages = new BehaviorSubject([]); - constructor( - protected readonly injector:Injector, - private readonly states:States, - private readonly I18n:I18nService, - private readonly hook:HookService, - private readonly $state:StateService, - private readonly elementRef:ElementRef, - private readonly cdRef:ChangeDetectorRef, - private readonly PathHelper:PathHelperService, - private readonly schemaCache:SchemaCacheService, - private readonly currentProject:CurrentProjectService, - private readonly halEditing:HalResourceEditingService, - private readonly halResourceService:HalResourceService, - private readonly currentUserService:CurrentUserService, - private readonly displayFieldService:DisplayFieldService, - private readonly projectsResourceService:ProjectsResourceService, - private readonly projectStoragesService:ProjectStoragesResourceService, - ) { - super(); - } - public ngOnInit():void { this.element = this.elementRef.nativeElement as HTMLElement; diff --git a/frontend/src/app/features/work-packages/components/wp-subject/wp-subject.component.ts b/frontend/src/app/features/work-packages/components/wp-subject/wp-subject.component.ts index 121c81957a2..9881e8271d6 100644 --- a/frontend/src/app/features/work-packages/components/wp-subject/wp-subject.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-subject/wp-subject.component.ts @@ -26,11 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - ChangeDetectionStrategy, - Component, - Input, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; import { UIRouterGlobals } from '@uirouter/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { randomString } from 'core-app/shared/helpers/random-string'; @@ -47,14 +43,10 @@ import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackageSubjectComponent extends UntilDestroyedMixin { + protected uiRouterGlobals = inject(UIRouterGlobals); + protected apiV3Service = inject(ApiV3Service); + @Input() workPackage:WorkPackageResource; public readonly uniqueElementIdentifier = `work-packages--subject-type-row-${randomString(16)}`; - - constructor( - protected uiRouterGlobals:UIRouterGlobals, - protected apiV3Service:ApiV3Service, - ) { - super(); - } } diff --git a/frontend/src/app/features/work-packages/components/wp-table/config-menu/config-menu.component.ts b/frontend/src/app/features/work-packages/components/wp-table/config-menu/config-menu.component.ts index 75f67ec7be8..d4c289f3388 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/config-menu/config-menu.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/config-menu/config-menu.component.ts @@ -1,5 +1,5 @@ import { I18nService } from 'core-app/core/i18n/i18n.service'; -import { ChangeDetectionStrategy, Component, Injector } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Injector, inject } from '@angular/core'; import { OpModalService } from 'core-app/shared/components/modal/modal.service'; import { OPContextMenuService } from 'core-app/shared/components/op-context-menu/op-context-menu.service'; import { WpTableConfigurationModalComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration.modal'; @@ -14,17 +14,15 @@ import { WpTableConfigurationModalComponent } from 'core-app/features/work-packa changeDetection: ChangeDetectionStrategy.Default, }) export class WorkPackagesTableConfigMenuComponent { + readonly I18n = inject(I18nService); + readonly injector = inject(Injector); + readonly opModalService = inject(OpModalService); + readonly opContextMenu = inject(OPContextMenuService); + public text = { configureTable: this.I18n.t('js.toolbar.settings.configure_view'), }; - constructor( - readonly I18n:I18nService, - readonly injector:Injector, - readonly opModalService:OpModalService, - readonly opContextMenu:OPContextMenuService, - ) { } - public openTableConfigurationModal() { this.opContextMenu.close(); this.opModalService.show(WpTableConfigurationModalComponent, this.injector); diff --git a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/columns-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/columns-tab.component.ts index afadf97d491..613929cc229 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/columns-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/columns-tab.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Injector, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { QueryColumn } from 'core-app/features/work-packages/components/wp-query/query-column'; import { @@ -20,6 +20,10 @@ import { changeDetection: ChangeDetectionStrategy.Default, }) export class WpTableConfigurationColumnsTabComponent implements TabComponent, OnInit { + readonly injector = inject(Injector); + readonly I18n = inject(I18nService); + readonly wpTableColumns = inject(WorkPackageViewColumnsService); + public availableColumnsOptions = this.wpTableColumns.all.map((c) => this.column2Like(c)); public availableColumns = this.wpTableColumns.all; @@ -40,13 +44,6 @@ export class WpTableConfigurationColumnsTabComponent implements TabComponent, On inputDragLabel: this.I18n.t('js.label_manage_columns'), }; - constructor( - readonly injector:Injector, - readonly I18n:I18nService, - readonly wpTableColumns:WorkPackageViewColumnsService, -) { - } - public onSave() { this.wpTableColumns.setColumnsById(this.selectedColumns.map((c) => c.id)); } diff --git a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/display-settings-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/display-settings-tab.component.ts index 3526c27874d..a173cea748c 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/display-settings-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/display-settings-tab.component.ts @@ -3,7 +3,7 @@ import { TabComponent } from 'core-app/features/work-packages/components/wp-tabl import { WorkPackageViewGroupByService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-group-by.service'; import { WorkPackageViewHierarchiesService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-hierarchy.service'; import { WorkPackageViewSumService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-sum.service'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnInit, inject } from '@angular/core'; import { QueryGroupByResource } from 'core-app/features/hal/resources/query-group-by-resource'; @Component({ @@ -16,6 +16,13 @@ import { QueryGroupByResource } from 'core-app/features/hal/resources/query-grou changeDetection: ChangeDetectionStrategy.Default, }) export class WpTableConfigurationDisplaySettingsTabComponent implements TabComponent, OnInit { + readonly injector = inject(Injector); + readonly I18n = inject(I18nService); + readonly wpTableGroupBy = inject(WorkPackageViewGroupByService); + readonly wpTableHierarchies = inject(WorkPackageViewHierarchiesService); + readonly wpTableSums = inject(WorkPackageViewSumService); + readonly cdRef = inject(ChangeDetectorRef); + // Display mode public displayMode:'hierarchy'|'grouped'|'default' = 'default'; @@ -44,15 +51,6 @@ export class WpTableConfigurationDisplaySettingsTabComponent implements TabCompo }, }; - constructor( - readonly injector:Injector, - readonly I18n:I18nService, - readonly wpTableGroupBy:WorkPackageViewGroupByService, - readonly wpTableHierarchies:WorkPackageViewHierarchiesService, - readonly wpTableSums:WorkPackageViewSumService, - readonly cdRef:ChangeDetectorRef, - ) { } - public onSave() { // Update hierarchy state this.wpTableHierarchies.setEnabled(this.displayMode === 'hierarchy'); diff --git a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/filters-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/filters-tab.component.ts index 0af78ae3c7b..b672fee06f1 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/filters-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/filters-tab.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Injector, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { TabComponent, @@ -19,6 +19,11 @@ import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/que standalone: false, }) export class WpTableConfigurationFiltersTabComponent implements TabComponent, OnInit { + readonly injector = inject(Injector); + readonly I18n = inject(I18nService); + readonly wpTableFilters = inject(WorkPackageViewFiltersService); + readonly wpFiltersService = inject(WorkPackageFiltersService); + public filters:QueryFilterInstanceResource[] = []; public eeShowBanners = false; @@ -32,14 +37,6 @@ export class WpTableConfigurationFiltersTabComponent implements TabComponent, On upsellRelationColumnsLink: this.I18n.t('js.modals.upsell_relation_columns_link'), }; - constructor( - readonly injector:Injector, - readonly I18n:I18nService, - readonly wpTableFilters:WorkPackageViewFiltersService, - readonly wpFiltersService:WorkPackageFiltersService, - ) { - } - ngOnInit() { this.wpTableFilters .onReady() diff --git a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/highlighting-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/highlighting-tab.component.ts index c719ba6b6cd..d3e54352107 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/highlighting-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/highlighting-tab.component.ts @@ -1,9 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - Injector, - ViewChild, OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Injector, ViewChild, OnInit, inject } from '@angular/core'; import { TabComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tab-portal-outlet'; import { WorkPackageViewHighlightingService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-highlighting.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @@ -22,6 +17,13 @@ import { repositionDropdownBugfix } from 'core-app/shared/components/autocomplet standalone: false, }) export class WpTableConfigurationHighlightingTabComponent implements TabComponent, OnInit { + readonly injector = inject(Injector); + readonly I18n = inject(I18nService); + readonly states = inject(States); + readonly querySpace = inject(IsolatedQuerySpace); + readonly Banners = inject(BannersService); + readonly wpTableHighlight = inject(WorkPackageViewHighlightingService); + // Display mode public highlightingMode:HighlightingMode = 'inline'; @@ -54,14 +56,6 @@ export class WpTableConfigurationHighlightingTabComponent implements TabComponen more_info_link: enterpriseDocsUrl.tableHighlighting, }; - constructor(readonly injector:Injector, - readonly I18n:I18nService, - readonly states:States, - readonly querySpace:IsolatedQuerySpace, - readonly Banners:BannersService, - readonly wpTableHighlight:WorkPackageViewHighlightingService) { - } - ngOnInit() { this.availableInlineHighlightedAttributes = this.availableHighlightedAttributes; this.availableRowHighlightedAttributes = [ diff --git a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts index aa35a7696cf..8a5f49122cf 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageViewSortByService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-sort-by.service'; import { TabComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tab-portal-outlet'; @@ -30,6 +30,11 @@ export type SortingMode = 'automatic'|'manual'; changeDetection: ChangeDetectionStrategy.Default, }) export class WpTableConfigurationSortByTabComponent implements TabComponent, OnInit { + readonly injector = inject(Injector); + readonly I18n = inject(I18nService); + readonly wpTableSortBy = inject(WorkPackageViewSortByService); + readonly cdRef = inject(ChangeDetectorRef); + public text = { title: this.I18n.t('js.label_sort_by'), placeholder: this.I18n.t('js.placeholders.default'), @@ -61,13 +66,6 @@ export class WpTableConfigurationSortByTabComponent implements TabComponent, OnI public manualSortColumn:SortColumn; - constructor(readonly injector:Injector, - readonly I18n:I18nService, - readonly wpTableSortBy:WorkPackageViewSortByService, - readonly cdRef:ChangeDetectorRef) { - - } - public onSave() { let sortElements; if (this.sortingMode === 'automatic') { diff --git a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/timelines-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/timelines-tab.component.ts index 0d8e8618b26..5092244b5da 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/timelines-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/timelines-tab.component.ts @@ -1,9 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - Injector, - OnInit, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Injector, OnInit, inject } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { TabComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tab-portal-outlet'; import { WorkPackageViewTimelineService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-timeline.service'; @@ -22,6 +17,12 @@ import { StateService } from '@uirouter/angular'; changeDetection: ChangeDetectionStrategy.Default, }) export class WpTableConfigurationTimelinesTabComponent implements TabComponent, OnInit { + readonly injector = inject(Injector); + readonly I18n = inject(I18nService); + readonly wpTableTimeline = inject(WorkPackageViewTimelineService); + readonly wpTableColumns = inject(WorkPackageViewColumnsService); + readonly $state = inject(StateService); + public timelineVisible = false; public availableAttributes:{ id:string, name:string }[]; @@ -61,15 +62,6 @@ export class WpTableConfigurationTimelinesTabComponent implements TabComponent, }, }; - constructor( - readonly injector:Injector, - readonly I18n:I18nService, - readonly wpTableTimeline:WorkPackageViewTimelineService, - readonly wpTableColumns:WorkPackageViewColumnsService, - readonly $state:StateService, - ) { - } - public onSave() { this.wpTableTimeline.update({ ...this.wpTableTimeline.current, diff --git a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.html b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.html index 4884b2d01a8..1b4f42fd1cb 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.html +++ b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.html @@ -3,6 +3,7 @@ &ngsp;