Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b041c23b4 | |||
| 7b6e948aa2 | |||
| f6d81b22bd | |||
| c861dd2ee2 | |||
| 7eabae4b4b | |||
| ae4272a6b6 | |||
| fd672943d1 | |||
| c2ea5e5859 | |||
| c1217c5a58 | |||
| 27eb2d65d4 | |||
| ef407cb0b4 | |||
| 1e20561e91 | |||
| a2ec5f0fa1 | |||
| de8d13a4c1 | |||
| 495d61a11d | |||
| 90fdba8000 | |||
| aa1cada79b | |||
| 790b385849 | |||
| baa2c51123 | |||
| 1e585e06e6 | |||
| 5624484631 | |||
| 810e00da03 | |||
| eeeee2fa0e | |||
| c50a31a503 | |||
| 7360709795 | |||
| df678d7d69 | |||
| 6739242554 | |||
| a5e5eecf8b | |||
| b0248c20eb | |||
| f129968968 |
@@ -65,6 +65,47 @@ jobs:
|
||||
env:
|
||||
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
|
||||
|
||||
- name: Build the chromium docker image
|
||||
env:
|
||||
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
|
||||
run: |
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
|
||||
docker build \
|
||||
-f ./docker/Dockerfile.chromium \
|
||||
--progress=plain \
|
||||
--build-arg TAG="$GIT_SHA" \
|
||||
-t "documenso/documenso-$BUILD_PLATFORM:latest-chromium" \
|
||||
-t "documenso/documenso-$BUILD_PLATFORM:$GIT_SHA-chromium" \
|
||||
-t "documenso/documenso-$BUILD_PLATFORM:$APP_VERSION-chromium" \
|
||||
-t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:latest-chromium" \
|
||||
-t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$GIT_SHA-chromium" \
|
||||
-t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$APP_VERSION-chromium" \
|
||||
.
|
||||
|
||||
- name: Push the chromium docker image to DockerHub
|
||||
env:
|
||||
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
|
||||
run: |
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
|
||||
docker push "documenso/documenso-$BUILD_PLATFORM:latest-chromium"
|
||||
docker push "documenso/documenso-$BUILD_PLATFORM:$GIT_SHA-chromium"
|
||||
docker push "documenso/documenso-$BUILD_PLATFORM:$APP_VERSION-chromium" \
|
||||
|
||||
- name: Push the chromium docker image to GitHub Container Registry
|
||||
env:
|
||||
BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
|
||||
run: |
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
|
||||
docker push "ghcr.io/documenso/documenso-$BUILD_PLATFORM:latest-chromium"
|
||||
docker push "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$GIT_SHA-chromium"
|
||||
docker push "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$APP_VERSION-chromium"
|
||||
|
||||
create_and_publish_manifest:
|
||||
name: Create and publish manifest
|
||||
runs-on: ubuntu-latest
|
||||
@@ -125,6 +166,43 @@ jobs:
|
||||
docker manifest push documenso/documenso:$GIT_SHA
|
||||
docker manifest push documenso/documenso:$APP_VERSION
|
||||
|
||||
- name: Create and push DockerHub chromium manifest
|
||||
run: |
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
|
||||
# Check if the version is stable (no rc or beta in the version)
|
||||
if [[ "$APP_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
docker manifest create \
|
||||
documenso/documenso:latest-chromium \
|
||||
--amend documenso/documenso-amd64:latest-chromium \
|
||||
--amend documenso/documenso-arm64:latest-chromium
|
||||
|
||||
docker manifest push documenso/documenso:latest-chromium
|
||||
fi
|
||||
|
||||
if [[ "$APP_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
|
||||
docker manifest create \
|
||||
documenso/documenso:rc-chromium \
|
||||
--amend documenso/documenso-amd64:rc-chromium \
|
||||
--amend documenso/documenso-arm64:rc-chromium
|
||||
|
||||
docker manifest push documenso/documenso:rc-chromium
|
||||
fi
|
||||
|
||||
docker manifest create \
|
||||
documenso/documenso:$GIT_SHA-chromium \
|
||||
--amend documenso/documenso-amd64:$GIT_SHA-chromium \
|
||||
--amend documenso/documenso-arm64:$GIT_SHA-chromium
|
||||
|
||||
docker manifest create \
|
||||
documenso/documenso:$APP_VERSION-chromium \
|
||||
--amend documenso/documenso-amd64:$APP_VERSION-chromium \
|
||||
--amend documenso/documenso-arm64:$APP_VERSION-chromium
|
||||
|
||||
docker manifest push documenso/documenso:$GIT_SHA-chromium
|
||||
docker manifest push documenso/documenso:$APP_VERSION-chromium
|
||||
|
||||
- name: Create and push Github Container Registry manifest
|
||||
run: |
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
@@ -161,3 +239,40 @@ jobs:
|
||||
|
||||
docker manifest push ghcr.io/documenso/documenso:$GIT_SHA
|
||||
docker manifest push ghcr.io/documenso/documenso:$APP_VERSION
|
||||
|
||||
- name: Create and push Github Container Registry chromium manifest
|
||||
run: |
|
||||
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
|
||||
# Check if the version is stable (no rc or beta in the version)
|
||||
if [[ "$APP_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
docker manifest create \
|
||||
ghcr.io/documenso/documenso:latest-chromium \
|
||||
--amend ghcr.io/documenso/documenso-amd64:latest-chromium \
|
||||
--amend ghcr.io/documenso/documenso-arm64:latest-chromium
|
||||
|
||||
docker manifest push ghcr.io/documenso/documenso:latest-chromium
|
||||
fi
|
||||
|
||||
if [[ "$APP_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
|
||||
docker manifest create \
|
||||
ghcr.io/documenso/documenso:rc-chromium \
|
||||
--amend ghcr.io/documenso/documenso-amd64:rc-chromium \
|
||||
--amend ghcr.io/documenso/documenso-arm64:rc-chromium
|
||||
|
||||
docker manifest push ghcr.io/documenso/documenso:rc-chromium
|
||||
fi
|
||||
|
||||
docker manifest create \
|
||||
ghcr.io/documenso/documenso:$GIT_SHA-chromium \
|
||||
--amend ghcr.io/documenso/documenso-amd64:$GIT_SHA-chromium \
|
||||
--amend ghcr.io/documenso/documenso-arm64:$GIT_SHA-chromium
|
||||
|
||||
docker manifest create \
|
||||
ghcr.io/documenso/documenso:$APP_VERSION-chromium \
|
||||
--amend ghcr.io/documenso/documenso-amd64:$APP_VERSION-chromium \
|
||||
--amend ghcr.io/documenso/documenso-arm64:$APP_VERSION-chromium
|
||||
|
||||
docker manifest push ghcr.io/documenso/documenso:$GIT_SHA-chromium
|
||||
docker manifest push ghcr.io/documenso/documenso:$APP_VERSION-chromium
|
||||
|
||||
@@ -17,5 +17,6 @@
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
},
|
||||
"prisma.pinToPrisma6": true
|
||||
}
|
||||
|
||||
@@ -5,14 +5,22 @@ description: Learn how to get the coordinates of a field in a document.
|
||||
|
||||
## Field Coordinates
|
||||
|
||||
Field coordinates represent the position of a field in a document. They are returned in the `pageX` and `pageY` properties of the field.
|
||||
Field coordinates represent the position of a field in a document. They are returned in the `pageX`, `pageY`, `width` and `height` properties of the field.
|
||||
|
||||
To enable field coordinates, you can use the `devmode` query parameter.
|
||||
|
||||
```bash
|
||||
https://app.documenso.com/documents/<document-id>/edit?devmode=true
|
||||
# Legacy editor
|
||||
|
||||
https://app.documenso.com/t/<team-url>/documents/<envelope-id>/legacy_editor?devmode=true
|
||||
```
|
||||
|
||||
You should then see the coordinates on top of each field.
|
||||

|
||||
|
||||

|
||||
```bash
|
||||
# New editor
|
||||
|
||||
https://app.documenso.com/t/<team-url>/documents/<envelope-id>/edit?step=addFields&devmode=true
|
||||
```
|
||||
|
||||

|
||||
|
||||
@@ -61,6 +61,6 @@ You can access the following services:
|
||||
- Main application - http://localhost:3000
|
||||
- Incoming Mail Access - http://localhost:9000
|
||||
- Database Connection Details:
|
||||
- Port: 54320
|
||||
- Connection: Use your favourite database client to connect to the database.
|
||||
- Port: 54320
|
||||
- Connection: Use your favorite database client to connect to the database.
|
||||
- S3 Storage Dashboard - http://localhost:9001
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Rate Limits
|
||||
description: Learn about the rate limits for the Documenso Public API.
|
||||
---
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# Rate Limits
|
||||
|
||||
@@ -148,6 +148,7 @@ This method avoids file permission issues by creating the certificate directly i
|
||||
|
||||
# Generate certificate inside container using environment variable
|
||||
docker exec -e CERT_PASS="$CERT_PASS" -it documenso-production-documenso-1 bash -c "
|
||||
mkdir -p /app/certs && \
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout /tmp/private.key \
|
||||
-out /tmp/certificate.crt \
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Telemetry
|
||||
description: Learn about the telemetry data that Documenso collects from self-hosted instances.
|
||||
---
|
||||
|
||||
# Telemetry
|
||||
|
||||
Documenso collects anonymous telemetry data from self-hosted instances to help us understand how the software is being used and make improvements to the product. This telemetry is enabled by default, but you can easily disable it if you prefer.
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Learn how to use webhooks to receive real-time notifications about
|
||||
|
||||
# Webhooks
|
||||
|
||||
Webhooks are HTTP callbacks triggered by specific events. When the user subscribes to a specific event, and that event occurs, the webhook makes an HTTP request to the URL provided by the user. The request can be a simple notification or carry a payload with more information about the event.
|
||||
Webhooks are HTTP callbacks triggered by specific events. When you subscribe to a specific event and that event occurs, the webhook makes an HTTP request to the URL you provide. The request can be a simple notification or carry a payload with more information about the event.
|
||||
|
||||
Some of the common use cases for webhooks include:
|
||||
|
||||
@@ -25,13 +25,13 @@ Documenso supports Webhooks and allows you to subscribe to the following events:
|
||||
|
||||
## Create a webhook subscription
|
||||
|
||||
You can create a webhook subscription from the user settings page. Click on your avatar in the top right corner of the dashboard and select "**[User settings](https://app.documenso.com/settings)**" from the dropdown menu.
|
||||
You can create a webhook subscription from the team settings page. Click your avatar in the top right corner of the dashboard and select "Team settings" from the dropdown menu.
|
||||
|
||||

|
||||

|
||||
|
||||
Then, navigate to the "**[Webhooks](https://app.documenso.com/settings/webhooks)**" tab, where you can see a list of your existing webhooks and create new ones.
|
||||
Then, navigate to the "Webhooks" tab, which takes you to the webhooks main page.
|
||||
|
||||

|
||||

|
||||
|
||||
Clicking on the "**Create Webhook**" button opens a modal to create a new webhook subscription.
|
||||
|
||||
@@ -41,7 +41,7 @@ To create a new webhook subscription, you need to provide the following informat
|
||||
- Select the event(s) you want to subscribe to: `document.created`, `document.sent`, `document.opened`, `document.signed`, `document.completed`, `document.rejected`, `document.cancelled`.
|
||||
- Optionally, you can provide a secret key that will be used to sign the payload. This key will be included in the `X-Documenso-Secret` header of the request.
|
||||
|
||||

|
||||

|
||||
|
||||
After you have filled in the required information, click on the "**Create Webhook**" button to save your subscription.
|
||||
|
||||
@@ -49,7 +49,22 @@ The screenshot below illustrates a newly created webhook subscription.
|
||||
|
||||

|
||||
|
||||
You can edit or delete your webhook subscriptions by clicking the "**Edit**" or "**Delete**" buttons next to the webhook.
|
||||
You can edit, view the logs, or delete your webhook subscriptions by clicking the three dots (...) under the "Action" column. You can also access the webhook logs by clicking on the webhook subscription directly.
|
||||
|
||||

|
||||
|
||||
You can go even further and check the execution details of each call by clicking on a specific webhook call.
|
||||
|
||||

|
||||
|
||||
This page shows the details of the webhook call such as:
|
||||
|
||||
- status
|
||||
- event
|
||||
- date when the webhook was sent
|
||||
- response code
|
||||
- request body
|
||||
- response body and headers
|
||||
|
||||
## Webhook fields
|
||||
|
||||
@@ -619,18 +634,26 @@ Example payload for the `document.rejected` event:
|
||||
}
|
||||
```
|
||||
|
||||
## Webhook Events Testing
|
||||
## Webhook events testing
|
||||
|
||||
You can trigger test webhook events to test the webhook functionality. To trigger a test webhook, navigate to the [Webhooks page](/developers/webhooks) and click on the "Test Webhook" button.
|
||||
You can trigger test webhook events to test the webhook functionality. To do so, navigate to the webhook subscription details page and click the "Test" button.
|
||||
|
||||

|
||||

|
||||
|
||||
This opens a dialog where you can select the event type to test.
|
||||
|
||||

|
||||

|
||||
|
||||
Choose the appropriate event and click "Send Test Webhook." You’ll shortly receive a test payload from Documenso with sample data.
|
||||
Choose the event you want to test and click "Send". You’ll then receive a test payload from Documenso with sample data.
|
||||
|
||||
## Webhook events resending
|
||||
|
||||
To resend a webhook call, you need to navigate to the webhook call page and click the "Resend" button.
|
||||
|
||||

|
||||
|
||||
This will send the webhook event to the webhook URL again.
|
||||
|
||||
## Availability
|
||||
|
||||
Webhooks are available to individual users and teams.
|
||||
Webhooks are available to teams only.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Signature Levels
|
||||
description: Learn about the different signature levels for Documenso.
|
||||
---
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# Signature Levels
|
||||
@@ -26,20 +31,20 @@ ensures the legal validity and enforceability of electronic signatures and recor
|
||||
|
||||
### Main Requirements
|
||||
|
||||
- [x] Intent to Sign: "Parties must demonstrate their intent to sign [..]"
|
||||
- [x] Consent: "The ESIGN Act requires that all parties involved in a transaction consent to the use of electronic signatures and records [..]"
|
||||
- [x] Consumer Disclosures: Before obtaining their consent, financial institutions must provide the consumer a clear and conspicuous statement informing the consumer [..]
|
||||
- [x] Record Retention: Electronic Records must be maintained for later access by signers.
|
||||
- [x] Security: The ESIGN Act does not mandate specific security measures, but it does require that parties take reasonable steps to ensure the security and integrity of electronic signatures and records. This may include implementing encryption, access controls, and authentication measures.
|
||||
- [x] **Intent to Sign**: "Parties must demonstrate their intent to sign [..]"
|
||||
- [x] **Consent**: "The ESIGN Act requires that all parties involved in a transaction consent to the use of electronic signatures and records [..]"
|
||||
- [x] **Consumer Disclosures**: Before obtaining their consent, financial institutions must provide the consumer a clear and conspicuous statement informing the consumer [..]
|
||||
- [x] **Record Retention**: Electronic Records must be maintained for later access by signers.
|
||||
- [x] **Security**: The ESIGN Act does not mandate specific security measures, but it does require that parties take reasonable steps to ensure the security and integrity of electronic signatures and records. This may include implementing encryption, access controls, and authentication measures.
|
||||
|
||||
## UETA (Uniform Electronic Transactions Act)
|
||||
|
||||
<Callout type="info" emoji="✅">
|
||||
Status: Compliant
|
||||
</Callout>
|
||||
The Uniform Electronic Transactions Act is a law that provides a legal framework for the use of electronic
|
||||
signatures and records in electronic transactions, ensuring they have the same validity and enforceability
|
||||
as paper documents and handwritten signatures.
|
||||
The Uniform Electronic Transactions Act is a law that provides a legal framework for the use of
|
||||
electronic signatures and records in electronic transactions, ensuring they have the same validity
|
||||
and enforceability as paper documents and handwritten signatures.
|
||||
|
||||
### Main Requirements
|
||||
|
||||
@@ -50,9 +55,9 @@ _See [ESIGN](/users/compliance/signature-levels#-esign-electronic-signatures-in-
|
||||
<Callout type="info" emoji="✅">
|
||||
Status: Compliant for Level 1 - SES (Simple Electronic Signatures)
|
||||
</Callout>
|
||||
eIDAS (Electronic Identification, Authentication and Trust Services) is an EU regulation that standardizes
|
||||
electronic identification and trust services for secure and seamless electronic transactions across European
|
||||
member states.
|
||||
eIDAS (Electronic Identification, Authentication and Trust Services) is an EU regulation that
|
||||
standardizes electronic identification and trust services for secure and seamless electronic
|
||||
transactions across European member states.
|
||||
|
||||
### Level 1 - SES (Simple Electronic Signatures)
|
||||
|
||||
@@ -69,8 +74,8 @@ eIDAS SES (Simple Electronic Signature) is a basic electronic signature with min
|
||||
Status: [Planned](https://github.com/documenso/backlog/issues/9) via third party until [Let's
|
||||
Sign](https://github.com/documenso/backlog/issues/21) is realized.
|
||||
</Callout>
|
||||
eIDAS AES (Advanced Electronic Signature) provides a higher level of security with unique identification
|
||||
of the signer and data integrity.
|
||||
eIDAS AES (Advanced Electronic Signature) provides a higher level of security with unique
|
||||
identification of the signer and data integrity.
|
||||
|
||||
### Main Requirements
|
||||
|
||||
@@ -85,8 +90,8 @@ of the signer and data integrity.
|
||||
Status: [Planned](https://github.com/documenso/backlog/issues/32) via third party until [Let's
|
||||
Sign](https://github.com/documenso/backlog/issues/21) is realized.
|
||||
</Callout>
|
||||
eIDAS QES (Qualified Electronic Signature) is the highest security level, legally equivalent to a handwritten
|
||||
signature within the EU.
|
||||
eIDAS QES (Qualified Electronic Signature) is the highest security level, legally equivalent to a
|
||||
handwritten signature within the EU.
|
||||
|
||||
### Main Requirements
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Standards and Regulations
|
||||
description: Learn about the different standards and regulations for Documenso.
|
||||
---
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
## 21 CFR Part 11
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Email Domains
|
||||
description: Learn how to create and manage email domains in Documenso.
|
||||
---
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# Email Domains
|
||||
|
||||
@@ -7,28 +7,28 @@ import { Callout } from 'nextra/components';
|
||||
|
||||
# Fair Use Policy
|
||||
|
||||
### Why
|
||||
We offer our plans without any limits on volume because we want our users and customers to make the most of their accounts. Estimating volume is incredibly hard, especially for shorter intervals like a quarter. We are not interested in selling volume packages our customers end up not using.
|
||||
|
||||
We offer our plans without any limits on volume because we want our users and customers to make the most of their accounts. Estimating volume is incredibly hard, especially for shorter intervals like a quarter. We are not interested in selling volume packages our customers end up not using. This is why the individual plan and the team plan do not include a limit on signing or API volume. If you are a customer of these [plans](https://documen.so/pricing), we ask you to abide by this fair use policy:
|
||||
This is why the individual plan and the team plan do not include a limit on signing or API volume. If you are a customer of these [plans](https://documen.so/pricing), we ask you to abide by this fair use policy:
|
||||
|
||||
### Spirit of the Plan
|
||||
|
||||
> Use the limitless accounts as much as you like (they are meant to offer a lot) while respecting the spirit and intended scope of the account.
|
||||
Use the limitless accounts as much as you like (they are meant to offer a lot) while respecting the spirit and intended scope of the account.
|
||||
|
||||
<Callout type="info">
|
||||
What happens if I violate this policy? We will ask you to upgrade to a fitting plan or custom
|
||||
pricing. We won’t block your account without reaching out. [Message
|
||||
us](mailto:support@documenso.com) for questions. It's probably fine, though.
|
||||
pricing. We won’t block your account without reaching out. You can [message
|
||||
us](mailto:support@documenso.com) for questions.
|
||||
</Callout>
|
||||
|
||||
### DO
|
||||
|
||||
- Sign as many documents with the individual plan for your single business or organization you are part of
|
||||
- Use the API and Zapier to automate all your signing to sign as much as possible
|
||||
- Experiment with the plans and integrations, testing what you want to build: When in doubt, do it. Especially if you are just starting.
|
||||
- Sign as many documents as you need with the individual plan for your single business or organization you are part of
|
||||
- Use the API and automation tools to automate all your signing workflows
|
||||
- Experiment with the plans and integrations, testing what you want to build
|
||||
|
||||
### DON'T
|
||||
|
||||
- Use the individual account's API to power a platform
|
||||
- Run a huge company, signing thousands of documents per day on a two-user team plan using the API
|
||||
- Let this policy make you overthink. If you are a paying customer, we want you to win, and it's probably fine
|
||||
- Let this policy make you overthink. If you are a paying customer, we want you to win
|
||||
|
||||
@@ -10,7 +10,12 @@ import { Callout, Steps } from 'nextra/components';
|
||||
<Steps>
|
||||
### Pick a Plan
|
||||
|
||||
The first step to start using Documenso is to pick a plan and create an account. At the moment of writing this guide, we have 3 plans available: Free, Individual, Teams and Platform.
|
||||
The first step to start using Documenso is to pick a plan and create an account. At the moment of writing this guide, we have 4 plans available:
|
||||
|
||||
- Free
|
||||
- Individual
|
||||
- Teams
|
||||
- Platform
|
||||
|
||||
Explore each plan's features and choose the one that best suits your needs. The [pricing page](https://documen.so/pricing) has more information about the plans.
|
||||
|
||||
@@ -24,7 +29,7 @@ To create a free account, navigate to the [registration page](https://documen.so
|
||||
|
||||
### Optional: Claim a Premium Username
|
||||
|
||||
You can claim a premium username by upgrading to a paid plan. After upgrading to a paid plan, you can update your [public profile](https://app.documenso.com/settings/public-profile).
|
||||
You can claim a premium username by upgrading to a paid plan. After upgrading to a paid plan, you can update your [public profile](/users/profile).
|
||||
|
||||
### Optional: Create a Team
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Community Edition
|
||||
description: Learn about the Community Edition of Documenso.
|
||||
---
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# Community Edition
|
||||
@@ -32,10 +37,10 @@ Documenso and the Community Edition are licensed under [AGPL3](https://github.co
|
||||
|
||||
### Conditions
|
||||
|
||||
ℹ️ License and copyright notice
|
||||
ℹ️ State changes
|
||||
ℹ️ Disclose source
|
||||
ℹ️ Network use is distribution
|
||||
- License and copyright notice
|
||||
- State changes
|
||||
- Disclose source
|
||||
- Network use is distribution
|
||||
|
||||
<Callout type="warning">
|
||||
It's important to remember that you must keep the AGPL3 license for your modified or non-modified
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Enterprise Edition
|
||||
description: Learn about the Enterprise Edition of Documenso.
|
||||
---
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# Enterprise Edition
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Licenses
|
||||
description: Learn about the different licenses for self-hosting Documenso.
|
||||
---
|
||||
|
||||
# Self-Hosting Licenses
|
||||
|
||||
Documenso comes in two versions for self-hosting:
|
||||
|
||||
@@ -15,7 +15,7 @@ Documenso allows you to create a public profile to share your templates for anyo
|
||||
|
||||
### Navigate to Your Profile Settings
|
||||
|
||||
Click on your profile picture in the top right corner and select "Settings" or "Team Settings". Then, navigate to the "Public Profile" tab to configure your profile.
|
||||
Click on your profile picture in the top right corner and select "Team Settings". Then, navigate to the "Public Profile" tab to configure your profile.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -9,30 +9,30 @@ description: Learn what types of support we offer.
|
||||
|
||||
If you are a developer or free user, you can reach out to the community or raise an issue:
|
||||
|
||||
### [Create Github Issues](https://github.com/documenso/documenso/issues)
|
||||
**[Create Github Issues](https://github.com/documenso/documenso/issues)**
|
||||
|
||||
The community and the core team address GitHub issues. Be sure to check if a similar issue already exists. Please note that while we want to address everything immediately, we must prioritize.
|
||||
|
||||
### [Join our Discord](https://documen.so/discord)
|
||||
**[Join our Discord](https://documen.so/discord)**
|
||||
|
||||
You can ask for help in the [community help channel](https://discord.com/channels/1132216843537485854/1133419426524430376).
|
||||
|
||||
## Paid Account Support
|
||||
|
||||
### Email: support@documenso.com
|
||||
**Email: support@documenso.com**
|
||||
|
||||
If you are paying customers facing issues, email our customer support, especially in urgent cases.
|
||||
|
||||
### Private Discord channel
|
||||
**Private Discord channel**
|
||||
|
||||
If you prefer Discord, we can invite you to a private channel. Message support to make this happen.
|
||||
|
||||
## Enterprise Support
|
||||
|
||||
### Email: support@documenso.com
|
||||
**Email: support@documenso.com**
|
||||
|
||||
If you are paying customers facing issues, email our customer support, especially in urgent cases.
|
||||
|
||||
### Slack
|
||||
**Slack**
|
||||
|
||||
If your team is on Slack, we can create a private workspace to support you more closely.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Templates
|
||||
description: Learn how to create and use templates in Documenso.
|
||||
---
|
||||
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# Document Templates
|
||||
|
||||
|
After Width: | Height: | Size: 596 KiB |
|
After Width: | Height: | Size: 571 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 590 KiB |
|
After Width: | Height: | Size: 362 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 384 KiB |
|
Before Width: | Height: | Size: 49 KiB |
@@ -137,12 +137,12 @@ export const TemplateBulkSendDialog = ({
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-y-4">
|
||||
<div className="bg-muted/70 rounded-lg border p-4">
|
||||
<div className="rounded-lg border bg-muted/70 p-4">
|
||||
<h3 className="text-sm font-medium">
|
||||
<Trans>CSV Structure</Trans>
|
||||
</h3>
|
||||
|
||||
<p className="text-muted-foreground mt-1 text-sm">
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
For each recipient, provide their email (required) and name (optional) in separate
|
||||
columns. Download the template CSV below for the correct format.
|
||||
@@ -153,7 +153,7 @@ export const TemplateBulkSendDialog = ({
|
||||
<Trans>Current recipients:</Trans>
|
||||
</p>
|
||||
|
||||
<ul className="text-muted-foreground mt-2 list-inside list-disc text-sm">
|
||||
<ul className="mt-2 list-inside list-disc text-sm text-muted-foreground">
|
||||
{recipients.map((recipient, index) => (
|
||||
<li key={index}>
|
||||
{recipient.name ? `${recipient.name} (${recipient.email})` : recipient.email}
|
||||
@@ -167,7 +167,7 @@ export const TemplateBulkSendDialog = ({
|
||||
<Trans>Download Template CSV</Trans>
|
||||
</Button>
|
||||
|
||||
<p className="text-muted-foreground text-xs">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<Trans>Pre-formatted CSV template with example data.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@@ -200,14 +200,14 @@ export const TemplateBulkSendDialog = ({
|
||||
) : (
|
||||
<div className="flex h-10 items-center rounded-md border px-3">
|
||||
<div className="flex flex-1 items-center gap-2">
|
||||
<FileIcon className="text-muted-foreground h-4 w-4" />
|
||||
<FileIcon className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="flex-1 truncate text-sm">{value.name}</span>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
className="text-destructive hover:text-destructive p-0 text-xs"
|
||||
className="p-0 text-xs text-destructive hover:text-destructive"
|
||||
onClick={() => onChange(null)}
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
@@ -220,9 +220,9 @@ export const TemplateBulkSendDialog = ({
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
{error && <p className="text-destructive text-sm">{error.message}</p>}
|
||||
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
||||
|
||||
<p className="text-muted-foreground text-xs">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<Trans>
|
||||
Maximum file size: 4MB. Maximum 100 rows per upload. Blank values will use
|
||||
template defaults.
|
||||
@@ -247,7 +247,7 @@ export const TemplateBulkSendDialog = ({
|
||||
|
||||
<label
|
||||
htmlFor="send-immediately"
|
||||
className="text-muted-foreground ml-2 flex items-center text-sm"
|
||||
className="ml-2 flex items-center text-sm text-muted-foreground"
|
||||
>
|
||||
<Trans>Send documents to recipients immediately</Trans>
|
||||
</label>
|
||||
|
||||
@@ -58,6 +58,7 @@ export type TDocumentPreferencesFormSchema = {
|
||||
includeSigningCertificate: boolean | null;
|
||||
includeAuditLog: boolean | null;
|
||||
signatureTypes: DocumentSignatureType[];
|
||||
delegateDocumentOwnership: boolean | null;
|
||||
aiFeaturesEnabled: boolean | null;
|
||||
};
|
||||
|
||||
@@ -73,6 +74,7 @@ type SettingsSubset = Pick<
|
||||
| 'typedSignatureEnabled'
|
||||
| 'uploadSignatureEnabled'
|
||||
| 'drawSignatureEnabled'
|
||||
| 'delegateDocumentOwnership'
|
||||
| 'aiFeaturesEnabled'
|
||||
>;
|
||||
|
||||
@@ -109,6 +111,7 @@ export const DocumentPreferencesForm = ({
|
||||
signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(canInherit ? 0 : 1, {
|
||||
message: msg`At least one signature type must be enabled`.id,
|
||||
}),
|
||||
delegateDocumentOwnership: z.boolean().nullable(),
|
||||
aiFeaturesEnabled: z.boolean().nullable(),
|
||||
});
|
||||
|
||||
@@ -125,6 +128,7 @@ export const DocumentPreferencesForm = ({
|
||||
includeSigningCertificate: settings.includeSigningCertificate,
|
||||
includeAuditLog: settings.includeAuditLog,
|
||||
signatureTypes: extractTeamSignatureSettings({ ...settings }),
|
||||
delegateDocumentOwnership: settings.delegateDocumentOwnership,
|
||||
aiFeaturesEnabled: settings.aiFeaturesEnabled,
|
||||
},
|
||||
resolver: zodResolver(ZDocumentPreferencesFormSchema),
|
||||
@@ -515,6 +519,52 @@ export const DocumentPreferencesForm = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="delegateDocumentOwnership"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>
|
||||
<Trans>Delegate Document Ownership</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value === null ? '-1' : field.value.toString()}
|
||||
onValueChange={(value) =>
|
||||
field.onChange(value === 'true' ? true : value === 'false' ? false : null)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-background text-muted-foreground">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
<SelectItem value="true">
|
||||
<Trans>Yes</Trans>
|
||||
</SelectItem>
|
||||
|
||||
<SelectItem value="false">
|
||||
<Trans>No</Trans>
|
||||
</SelectItem>
|
||||
|
||||
{canInherit && (
|
||||
<SelectItem value={'-1'}>
|
||||
<Trans>Inherit from organisation</Trans>
|
||||
</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
Enable team API tokens to delegate document ownership to another team member.
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{isAiFeaturesConfigured && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -368,7 +368,7 @@ export const SignInForm = ({
|
||||
<p className="mt-2 text-right">
|
||||
<Link
|
||||
to="/forgot-password"
|
||||
className="text-muted-foreground text-sm duration-200 hover:opacity-70"
|
||||
className="text-sm text-muted-foreground duration-200 hover:opacity-70"
|
||||
>
|
||||
<Trans>Forgot your password?</Trans>
|
||||
</Link>
|
||||
@@ -390,11 +390,11 @@ export const SignInForm = ({
|
||||
<>
|
||||
{hasSocialAuthEnabled && (
|
||||
<div className="relative flex items-center justify-center gap-x-4 py-2 text-xs uppercase">
|
||||
<div className="bg-border h-px flex-1" />
|
||||
<span className="text-muted-foreground bg-transparent">
|
||||
<div className="h-px flex-1 bg-border" />
|
||||
<span className="bg-transparent text-muted-foreground">
|
||||
<Trans>Or continue with</Trans>
|
||||
</span>
|
||||
<div className="bg-border h-px flex-1" />
|
||||
<div className="h-px flex-1 bg-border" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -403,7 +403,7 @@ export const SignInForm = ({
|
||||
type="button"
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="bg-background text-muted-foreground border"
|
||||
className="border bg-background text-muted-foreground"
|
||||
disabled={isSubmitting}
|
||||
onClick={onSignInWithGoogleClick}
|
||||
>
|
||||
@@ -417,7 +417,7 @@ export const SignInForm = ({
|
||||
type="button"
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="bg-background text-muted-foreground border"
|
||||
className="border bg-background text-muted-foreground"
|
||||
disabled={isSubmitting}
|
||||
onClick={onSignInWithMicrosoftClick}
|
||||
>
|
||||
@@ -435,7 +435,7 @@ export const SignInForm = ({
|
||||
type="button"
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="bg-background text-muted-foreground border"
|
||||
className="border bg-background text-muted-foreground"
|
||||
disabled={isSubmitting}
|
||||
onClick={onSignInWithOIDCClick}
|
||||
>
|
||||
@@ -452,7 +452,7 @@ export const SignInForm = ({
|
||||
variant="outline"
|
||||
disabled={isSubmitting}
|
||||
loading={isPasskeyLoading}
|
||||
className="bg-background text-muted-foreground border"
|
||||
className="border bg-background text-muted-foreground"
|
||||
onClick={onSignInWithPasskey}
|
||||
>
|
||||
{!isPasskeyLoading && <KeyRoundIcon className="-ml-1 mr-1 h-5 w-5" />}
|
||||
|
||||
@@ -212,12 +212,12 @@ export const ApiTokenForm = ({ className, tokens }: ApiTokenFormProps) => {
|
||||
/>
|
||||
|
||||
<div>
|
||||
<FormLabel className="text-muted-foreground mt-2">
|
||||
<FormLabel className="mt-2 text-muted-foreground">
|
||||
<Trans>Never expire</Trans>
|
||||
</FormLabel>
|
||||
<div className="block md:py-1.5">
|
||||
<Switch
|
||||
className="bg-background mt-2"
|
||||
className="mt-2 bg-background"
|
||||
checked={noExpirationDate}
|
||||
onCheckedChange={setNoExpirationDate}
|
||||
/>
|
||||
@@ -254,14 +254,14 @@ export const ApiTokenForm = ({ className, tokens }: ApiTokenFormProps) => {
|
||||
>
|
||||
<Card gradient>
|
||||
<CardContent className="p-4">
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
Your token was created successfully! Make sure to copy it because you won't be
|
||||
able to see it again!
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<p className="bg-muted-foreground/10 my-4 rounded-md px-2.5 py-1 font-mono text-sm">
|
||||
<p className="my-4 rounded-md bg-muted-foreground/10 px-2.5 py-1 font-mono text-sm">
|
||||
{newlyCreatedToken.token}
|
||||
</p>
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Recipient } from '@prisma/client';
|
||||
import type { Field } from '@prisma/client';
|
||||
@@ -57,8 +55,6 @@ export const DirectTemplateConfigureForm = ({
|
||||
initialEmail,
|
||||
onSubmit,
|
||||
}: DirectTemplateConfigureFormProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { sessionData } = useOptionalSession();
|
||||
const user = sessionData?.user;
|
||||
|
||||
@@ -77,17 +73,7 @@ export const DirectTemplateConfigureForm = ({
|
||||
});
|
||||
|
||||
const form = useForm<TDirectTemplateConfigureFormSchema>({
|
||||
resolver: zodResolver(
|
||||
ZDirectTemplateConfigureFormSchema.superRefine((items, ctx) => {
|
||||
if (template.recipients.map((recipient) => recipient.email).includes(items.email)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: _(msg`Email cannot already exist in the template`),
|
||||
path: ['email'],
|
||||
});
|
||||
}
|
||||
}),
|
||||
),
|
||||
resolver: zodResolver(ZDirectTemplateConfigureFormSchema),
|
||||
defaultValues: {
|
||||
email: initialEmail || '',
|
||||
},
|
||||
@@ -138,7 +124,7 @@ export const DirectTemplateConfigureForm = ({
|
||||
</FormControl>
|
||||
|
||||
{!fieldState.error && (
|
||||
<p className="text-muted-foreground text-xs">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<Trans>Enter your email address to receive the completed document.</Trans>
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -35,7 +35,7 @@ export const DocumentSigningMobileWidget = () => {
|
||||
return (
|
||||
<div className="pointer-events-none fixed bottom-0 left-0 right-0 z-50 flex justify-center px-2 pb-2 sm:px-4 sm:pb-6">
|
||||
<div className="pointer-events-auto w-full max-w-[760px]">
|
||||
<div className="bg-card border-border overflow-hidden rounded-xl border shadow-2xl">
|
||||
<div className="overflow-hidden rounded-xl border border-border bg-card shadow-2xl">
|
||||
{/* Main Header Bar */}
|
||||
<div className="flex items-center justify-between gap-4 p-4">
|
||||
<div className="flex-1">
|
||||
@@ -48,15 +48,15 @@ export const DocumentSigningMobileWidget = () => {
|
||||
aria-label={isExpanded ? 'Collapse' : 'Expand'}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<LucideChevronDown className="text-muted-foreground h-5 w-5 flex-shrink-0" />
|
||||
<LucideChevronDown className="h-5 w-5 flex-shrink-0 text-muted-foreground" />
|
||||
) : (
|
||||
<LucideChevronUp className="text-muted-foreground h-5 w-5 flex-shrink-0" />
|
||||
<LucideChevronUp className="h-5 w-5 flex-shrink-0 text-muted-foreground" />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<h2 className="text-foreground text-lg font-semibold">
|
||||
<h2 className="text-lg font-semibold text-foreground">
|
||||
{match(recipient.role)
|
||||
.with(RecipientRole.VIEWER, () => <Trans>View Document</Trans>)
|
||||
.with(RecipientRole.SIGNER, () => <Trans>Sign Document</Trans>)
|
||||
@@ -65,7 +65,7 @@ export const DocumentSigningMobileWidget = () => {
|
||||
.otherwise(() => null)}
|
||||
</h2>
|
||||
|
||||
<p className="text-muted-foreground -mt-0.5 text-sm">
|
||||
<p className="-mt-0.5 text-sm text-muted-foreground">
|
||||
{recipientFieldsRemaining.length === 0 ? (
|
||||
match(recipient.role)
|
||||
.with(RecipientRole.VIEWER, () => (
|
||||
@@ -102,11 +102,11 @@ export const DocumentSigningMobileWidget = () => {
|
||||
{recipient.role !== RecipientRole.VIEWER &&
|
||||
recipient.role !== RecipientRole.ASSISTANT && (
|
||||
<div className="px-4 pb-3">
|
||||
<div className="bg-muted relative h-[4px] rounded-md">
|
||||
<div className="relative h-[4px] rounded-md bg-muted">
|
||||
<motion.div
|
||||
layout="size"
|
||||
layoutId="document-signing-mobile-widget-progress-bar"
|
||||
className="bg-primary absolute inset-y-0 left-0"
|
||||
className="absolute inset-y-0 left-0 bg-primary"
|
||||
style={{
|
||||
width: `${100 - (100 / requiredRecipientFields.length) * (recipientFieldsRemaining.length ?? 0)}%`,
|
||||
}}
|
||||
@@ -117,11 +117,11 @@ export const DocumentSigningMobileWidget = () => {
|
||||
|
||||
{/* Expandable Content */}
|
||||
{isExpanded && (
|
||||
<div className="border-border animate-in slide-in-from-bottom-2 border-t p-4 duration-200">
|
||||
<div className="border-t border-border p-4 duration-200 animate-in slide-in-from-bottom-2">
|
||||
<EnvelopeSignerForm />
|
||||
|
||||
{!hidePoweredBy && (
|
||||
<div className="bg-primary text-primary-foreground mt-2 inline-block rounded px-2 py-1 text-xs font-medium opacity-60 hover:opacity-100 lg:hidden">
|
||||
<div className="mt-2 inline-block rounded bg-primary px-2 py-1 text-xs font-medium text-primary-foreground opacity-60 hover:opacity-100 lg:hidden">
|
||||
<span>Powered by</span>
|
||||
<BrandingLogo className="ml-2 inline-block h-[14px]" />
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ export const DocumentPageViewRecentActivity = ({
|
||||
documentId,
|
||||
userId,
|
||||
}: DocumentPageViewRecentActivityProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { _, i18n } = useLingui();
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -48,9 +48,9 @@ export const DocumentPageViewRecentActivity = ({
|
||||
const documentAuditLogs = useMemo(() => (data?.pages ?? []).flatMap((page) => page.data), [data]);
|
||||
|
||||
return (
|
||||
<section className="dark:bg-background border-border bg-widget flex flex-col rounded-xl border">
|
||||
<section className="flex flex-col rounded-xl border border-border bg-widget dark:bg-background">
|
||||
<div className="flex flex-row items-center justify-between border-b px-4 py-3">
|
||||
<h1 className="text-foreground font-medium">
|
||||
<h1 className="font-medium text-foreground">
|
||||
<Trans>Recent activity</Trans>
|
||||
</h1>
|
||||
|
||||
@@ -59,18 +59,18 @@ export const DocumentPageViewRecentActivity = ({
|
||||
|
||||
{isLoading && (
|
||||
<div className="flex h-full items-center justify-center py-16">
|
||||
<Loader className="text-muted-foreground h-6 w-6 animate-spin" />
|
||||
<Loader className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoadingError && (
|
||||
<div className="flex h-full flex-col items-center justify-center py-16">
|
||||
<p className="text-foreground/80 text-sm">
|
||||
<p className="text-sm text-foreground/80">
|
||||
<Trans>Unable to load document history</Trans>
|
||||
</p>
|
||||
<button
|
||||
onClick={async () => refetch()}
|
||||
className="text-foreground/70 hover:text-muted-foreground mt-2 text-sm"
|
||||
className="mt-2 text-sm text-foreground/70 hover:text-muted-foreground"
|
||||
>
|
||||
<Trans>Click here to retry</Trans>
|
||||
</button>
|
||||
@@ -83,16 +83,16 @@ export const DocumentPageViewRecentActivity = ({
|
||||
{hasNextPage && (
|
||||
<li className="relative flex gap-x-4">
|
||||
<div className="absolute -bottom-6 left-0 top-0 flex w-6 justify-center">
|
||||
<div className="bg-border w-px" />
|
||||
<div className="w-px bg-border" />
|
||||
</div>
|
||||
|
||||
<div className="bg-widget relative flex h-6 w-6 flex-none items-center justify-center">
|
||||
<div className="bg-widget h-1.5 w-1.5 rounded-full ring-1 ring-gray-300 dark:ring-neutral-600" />
|
||||
<div className="relative flex h-6 w-6 flex-none items-center justify-center bg-widget">
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-widget ring-1 ring-gray-300 dark:ring-neutral-600" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={async () => fetchNextPage()}
|
||||
className="text-foreground/70 hover:text-muted-foreground text-xs"
|
||||
className="text-xs text-foreground/70 hover:text-muted-foreground"
|
||||
>
|
||||
{isFetchingNextPage ? _(msg`Loading...`) : _(msg`Load older activity`)}
|
||||
</button>
|
||||
@@ -101,7 +101,7 @@ export const DocumentPageViewRecentActivity = ({
|
||||
|
||||
{documentAuditLogs.length === 0 && (
|
||||
<div className="flex items-center justify-center py-4">
|
||||
<p className="text-muted-foreground/70 text-sm">
|
||||
<p className="text-sm text-muted-foreground/70">
|
||||
<Trans>No recent activity</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@@ -115,44 +115,44 @@ export const DocumentPageViewRecentActivity = ({
|
||||
'absolute left-0 top-0 flex w-6 justify-center',
|
||||
)}
|
||||
>
|
||||
<div className="bg-border w-px" />
|
||||
<div className="w-px bg-border" />
|
||||
</div>
|
||||
|
||||
<div className="bg-widget text-foreground/40 relative flex h-6 w-6 flex-none items-center justify-center">
|
||||
<div className="relative flex h-6 w-6 flex-none items-center justify-center bg-widget text-foreground/40">
|
||||
{match(auditLog.type)
|
||||
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED, () => (
|
||||
<div className="bg-widget rounded-full border border-gray-300 p-1 dark:border-neutral-600">
|
||||
<div className="rounded-full border border-gray-300 bg-widget p-1 dark:border-neutral-600">
|
||||
<CheckCheckIcon className="h-3 w-3" aria-hidden="true" />
|
||||
</div>
|
||||
))
|
||||
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED, () => (
|
||||
<div className="bg-widget rounded-full border border-gray-300 p-1 dark:border-neutral-600">
|
||||
<div className="rounded-full border border-gray-300 bg-widget p-1 dark:border-neutral-600">
|
||||
<CheckIcon className="h-3 w-3" aria-hidden="true" />
|
||||
</div>
|
||||
))
|
||||
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED, () => (
|
||||
<div className="bg-widget rounded-full border border-gray-300 p-1 dark:border-neutral-600">
|
||||
<div className="rounded-full border border-gray-300 bg-widget p-1 dark:border-neutral-600">
|
||||
<AlertTriangle className="h-3 w-3" aria-hidden="true" />
|
||||
</div>
|
||||
))
|
||||
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED, () => (
|
||||
<div className="bg-widget rounded-full border border-gray-300 p-1 dark:border-neutral-600">
|
||||
<div className="rounded-full border border-gray-300 bg-widget p-1 dark:border-neutral-600">
|
||||
<MailOpen className="h-3 w-3" aria-hidden="true" />
|
||||
</div>
|
||||
))
|
||||
.otherwise(() => (
|
||||
<div className="bg-widget h-1.5 w-1.5 rounded-full ring-1 ring-gray-300 dark:ring-neutral-600" />
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-widget ring-1 ring-gray-300 dark:ring-neutral-600" />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p
|
||||
className="text-muted-foreground dark:text-muted-foreground/70 flex-auto truncate py-0.5 text-xs leading-5"
|
||||
title={formatDocumentAuditLogAction(_, auditLog, userId).description}
|
||||
className="flex-auto truncate py-0.5 text-xs leading-5 text-muted-foreground dark:text-muted-foreground/70"
|
||||
title={formatDocumentAuditLogAction(i18n, auditLog, userId).description}
|
||||
>
|
||||
{formatDocumentAuditLogAction(_, auditLog, userId).description}
|
||||
{formatDocumentAuditLogAction(i18n, auditLog, userId).description}
|
||||
</p>
|
||||
|
||||
<time className="text-muted-foreground dark:text-muted-foreground/70 flex-none py-0.5 text-xs leading-5">
|
||||
<time className="flex-none py-0.5 text-xs leading-5 text-muted-foreground dark:text-muted-foreground/70">
|
||||
{DateTime.fromJSDate(auditLog.createdAt).toRelative({ style: 'short' })}
|
||||
</time>
|
||||
</li>
|
||||
|
||||
@@ -339,7 +339,7 @@ export const EnvelopeEditorSettingsDialog = ({
|
||||
|
||||
<DialogContent className="flex w-full !max-w-5xl flex-row gap-0 p-0">
|
||||
{/* Sidebar. */}
|
||||
<div className="bg-accent/20 flex w-80 flex-col border-r">
|
||||
<div className="flex w-80 flex-col border-r bg-accent/20">
|
||||
<DialogHeader className="p-6 pb-4">
|
||||
<DialogTitle>Document Settings</DialogTitle>
|
||||
</DialogHeader>
|
||||
@@ -390,7 +390,7 @@ export const EnvelopeEditorSettingsDialog = ({
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground">
|
||||
<Trans>
|
||||
Controls the language for the document, including the language
|
||||
to be used for email notifications, and the final certificate
|
||||
@@ -441,7 +441,7 @@ export const EnvelopeEditorSettingsDialog = ({
|
||||
}))}
|
||||
selectedValues={field.value}
|
||||
onChange={field.onChange}
|
||||
className="bg-background w-full"
|
||||
className="w-full bg-background"
|
||||
emptySelectionPlaceholder="Select signature types"
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -518,7 +518,7 @@ export const EnvelopeEditorSettingsDialog = ({
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
<TooltipContent className="max-w-xs text-muted-foreground">
|
||||
<Trans>
|
||||
Add an external ID to the document. This can be used to identify
|
||||
the document in external systems.
|
||||
@@ -548,7 +548,7 @@ export const EnvelopeEditorSettingsDialog = ({
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
<TooltipContent className="max-w-xs text-muted-foreground">
|
||||
<Trans>
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
</Trans>
|
||||
@@ -576,7 +576,7 @@ export const EnvelopeEditorSettingsDialog = ({
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground">
|
||||
<h2>
|
||||
<strong>
|
||||
<Trans>Document Distribution Method</Trans>
|
||||
@@ -735,14 +735,14 @@ export const EnvelopeEditorSettingsDialog = ({
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-muted-foreground p-4">
|
||||
<TooltipContent className="p-4 text-muted-foreground">
|
||||
<DocumentSendEmailMessageHelper />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Textarea className="bg-background h-16 resize-none" {...field} />
|
||||
<Textarea className="h-16 resize-none bg-background" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
|
||||
@@ -28,36 +28,40 @@ export const EnvelopeSignerHeader = () => {
|
||||
const { envelopeData, envelope, recipientFieldsRemaining, recipient } =
|
||||
useRequiredEnvelopeSigningContext();
|
||||
|
||||
const isEmbedSigning = useEmbedSigningContext() !== null;
|
||||
|
||||
return (
|
||||
<nav className="embed--DocumentWidgetHeader bg-background border-border max-w-screen flex flex-row justify-between border-b px-4 py-3 md:px-6">
|
||||
<nav className="embed--DocumentWidgetHeader max-w-screen flex flex-row justify-between border-b border-border bg-background px-4 py-3 md:px-6">
|
||||
{/* Left side - Logo and title */}
|
||||
<div className="flex min-w-0 flex-1 items-center space-x-2 md:w-auto md:flex-none">
|
||||
<Link to="/" className="flex-shrink-0">
|
||||
{envelopeData.settings.brandingEnabled && envelopeData.settings.brandingLogo ? (
|
||||
<img
|
||||
src={`/api/branding/logo/team/${envelope.teamId}`}
|
||||
alt={`${envelope.team.name}'s Logo`}
|
||||
className="h-6 w-auto"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<BrandingLogo className="hidden h-6 w-auto md:block" />
|
||||
<BrandingLogoIcon className="h-6 w-auto md:hidden" />
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
{!isEmbedSigning && (
|
||||
<Link to="/" className="flex-shrink-0">
|
||||
{envelopeData.settings.brandingEnabled && envelopeData.settings.brandingLogo ? (
|
||||
<img
|
||||
src={`/api/branding/logo/team/${envelope.teamId}`}
|
||||
alt={`${envelope.team.name}'s Logo`}
|
||||
className="h-6 w-auto"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<BrandingLogo className="hidden h-6 w-auto md:block" />
|
||||
<BrandingLogoIcon className="h-6 w-auto md:hidden" />
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<h1
|
||||
title={envelope.title}
|
||||
className="text-foreground min-w-0 truncate text-base font-semibold md:hidden"
|
||||
className="min-w-0 truncate text-base font-semibold text-foreground md:hidden"
|
||||
>
|
||||
{envelope.title}
|
||||
</h1>
|
||||
|
||||
<Separator orientation="vertical" className="hidden h-6 md:block" />
|
||||
{!isEmbedSigning && <Separator orientation="vertical" className="hidden h-6 md:block" />}
|
||||
|
||||
<div className="hidden items-center space-x-2 md:flex">
|
||||
<h1 className="text-foreground whitespace-nowrap text-sm font-medium">
|
||||
<h1 className="whitespace-nowrap text-sm font-medium text-foreground">
|
||||
{envelope.title}
|
||||
</h1>
|
||||
|
||||
@@ -74,7 +78,7 @@ export const EnvelopeSignerHeader = () => {
|
||||
|
||||
{/* Right side - Desktop content */}
|
||||
<div className="hidden items-center space-x-2 lg:flex">
|
||||
<p className="text-muted-foreground mr-2 flex-shrink-0 text-sm">
|
||||
<p className="mr-2 flex-shrink-0 text-sm text-muted-foreground">
|
||||
<Plural
|
||||
one="1 Field Remaining"
|
||||
other="# Fields Remaining"
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Skeleton } from '@documenso/ui/primitives/skeleton';
|
||||
export default function DocumentEditSkeleton() {
|
||||
return (
|
||||
<div className="mx-auto -mt-4 flex w-full max-w-screen-xl flex-col px-4 md:px-8">
|
||||
<Link to="/" className="flex grow-0 items-center text-[#7AC455] hover:opacity-80">
|
||||
<Link to="/" className="flex grow-0 items-center text-documenso-700 hover:opacity-80">
|
||||
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
|
||||
<Trans>Documents</Trans>
|
||||
</Link>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState, useTransition } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Role, Subscription } from '@prisma/client';
|
||||
import { Edit, Loader } from 'lucide-react';
|
||||
import { Link } from 'react-router';
|
||||
@@ -82,7 +83,7 @@ export const AdminDashboardUsersTable = ({
|
||||
<Button className="w-24" asChild>
|
||||
<Link to={`/admin/users/${row.original.id}`}>
|
||||
<Edit className="-ml-1 mr-2 h-4 w-4" />
|
||||
Edit
|
||||
<Trans>Edit</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { TableCell } from '@documenso/ui/primitives/table';
|
||||
|
||||
export type DocumentLogsTableProps = {
|
||||
documentId: number;
|
||||
userId?: number;
|
||||
};
|
||||
|
||||
const dateFormat: DateTimeFormatOptions = {
|
||||
@@ -26,7 +27,7 @@ const dateFormat: DateTimeFormatOptions = {
|
||||
hourCycle: 'h12',
|
||||
};
|
||||
|
||||
export const DocumentLogsTable = ({ documentId }: DocumentLogsTableProps) => {
|
||||
export const DocumentLogsTable = ({ documentId, userId }: DocumentLogsTableProps) => {
|
||||
const { _, i18n } = useLingui();
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
@@ -93,7 +94,9 @@ export const DocumentLogsTable = ({ documentId }: DocumentLogsTableProps) => {
|
||||
{
|
||||
header: _(msg`Action`),
|
||||
accessorKey: 'type',
|
||||
cell: ({ row }) => <span>{formatDocumentAuditLogAction(_, row.original).description}</span>,
|
||||
cell: ({ row }) => (
|
||||
<span>{formatDocumentAuditLogAction(i18n, row.original, userId).description}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: _(msg`IP Address`),
|
||||
|
||||
@@ -65,7 +65,7 @@ const formatUserAgent = (userAgent: string | null | undefined, userAgentInfo: UA
|
||||
};
|
||||
|
||||
export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { _, i18n } = useLingui();
|
||||
|
||||
const parser = new UAParser();
|
||||
|
||||
@@ -73,7 +73,7 @@ export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
|
||||
<div className="space-y-4">
|
||||
{logs.map((log, index) => {
|
||||
parser.setUA(log.userAgent || '');
|
||||
const formattedAction = formatDocumentAuditLogAction(_, log);
|
||||
const formattedAction = formatDocumentAuditLogAction(i18n, log);
|
||||
const userAgentInfo = parser.getResult();
|
||||
|
||||
return (
|
||||
@@ -95,17 +95,17 @@ export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div className="text-muted-foreground text-sm font-medium uppercase tracking-wide print:text-[8pt]">
|
||||
<div className="text-sm font-medium uppercase tracking-wide text-muted-foreground print:text-[8pt]">
|
||||
{log.type.replace(/_/g, ' ')}
|
||||
</div>
|
||||
|
||||
<div className="text-foreground text-sm font-medium print:text-[8pt]">
|
||||
<div className="text-sm font-medium text-foreground print:text-[8pt]">
|
||||
{formattedAction.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-muted-foreground text-sm print:text-[8pt]">
|
||||
<div className="text-sm text-muted-foreground print:text-[8pt]">
|
||||
{DateTime.fromJSDate(log.createdAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toLocaleString(dateFormat)}
|
||||
@@ -117,27 +117,27 @@ export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
|
||||
{/* Details Section - Two column layout */}
|
||||
<div className="grid grid-cols-2 gap-x-8 gap-y-2 text-xs print:text-[6pt]">
|
||||
<div>
|
||||
<div className="text-muted-foreground/70 font-medium uppercase tracking-wide">
|
||||
<div className="font-medium uppercase tracking-wide text-muted-foreground/70">
|
||||
{_(msg`User`)}
|
||||
</div>
|
||||
|
||||
<div className="text-foreground mt-1 font-mono">{log.email || 'N/A'}</div>
|
||||
<div className="mt-1 font-mono text-foreground">{log.email || 'N/A'}</div>
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<div className="text-muted-foreground/70 font-medium uppercase tracking-wide">
|
||||
<div className="font-medium uppercase tracking-wide text-muted-foreground/70">
|
||||
{_(msg`IP Address`)}
|
||||
</div>
|
||||
|
||||
<div className="text-foreground mt-1 font-mono">{log.ipAddress || 'N/A'}</div>
|
||||
<div className="mt-1 font-mono text-foreground">{log.ipAddress || 'N/A'}</div>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2">
|
||||
<div className="text-muted-foreground/70 font-medium uppercase tracking-wide">
|
||||
<div className="font-medium uppercase tracking-wide text-muted-foreground/70">
|
||||
{_(msg`User Agent`)}
|
||||
</div>
|
||||
|
||||
<div className="text-foreground mt-1">
|
||||
<div className="mt-1 text-foreground">
|
||||
{_(formatUserAgent(log.userAgent, userAgentInfo))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -82,7 +82,9 @@ export const OrganisationGroupsDataTable = () => {
|
||||
cell: ({ row }) => (
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button asChild variant="outline">
|
||||
<Link to={`/o/${organisation.url}/settings/groups/${row.original.id}`}>Manage</Link>
|
||||
<Link to={`/o/${organisation.url}/settings/groups/${row.original.id}`}>
|
||||
<Trans>Manage</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
<OrganisationGroupDeleteDialog
|
||||
|
||||
@@ -40,8 +40,8 @@ export default function OrganisationSettingsTeamsPage() {
|
||||
if (organisation.teams.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center px-4 py-16">
|
||||
<div className="bg-muted mb-6 flex h-20 w-20 items-center justify-center rounded-full">
|
||||
<UsersIcon className="text-muted-foreground h-10 w-10" />
|
||||
<div className="mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-muted">
|
||||
<UsersIcon className="h-10 w-10 text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
<h2 className="mb-2 text-xl font-semibold">
|
||||
@@ -53,7 +53,7 @@ export default function OrganisationSettingsTeamsPage() {
|
||||
organisation.currentOrganisationRole,
|
||||
) ? (
|
||||
<>
|
||||
<p className="text-muted-foreground mb-8 max-w-md text-center text-sm">
|
||||
<p className="mb-8 max-w-md text-center text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
Teams help you organise your work and collaborate with others. Create your first
|
||||
team to get started.
|
||||
@@ -73,21 +73,21 @@ export default function OrganisationSettingsTeamsPage() {
|
||||
<h3 className="mb-2 font-medium">
|
||||
<Trans>What you can do with teams:</Trans>
|
||||
</h3>
|
||||
<ul className="text-muted-foreground space-y-2 text-sm">
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
<li className="flex flex-row items-center gap-2">
|
||||
<div className="bg-muted mt-0.5 flex h-5 w-5 items-center justify-center rounded-full font-bold">
|
||||
<div className="mt-0.5 flex h-5 w-5 items-center justify-center rounded-full bg-muted font-bold">
|
||||
<span className="text-xs">1</span>
|
||||
</div>
|
||||
<Trans>Organize your documents and templates</Trans>
|
||||
</li>
|
||||
<li className="flex flex-row items-center gap-2">
|
||||
<div className="bg-muted mt-0.5 flex h-5 w-5 items-center justify-center rounded-full font-bold">
|
||||
<div className="mt-0.5 flex h-5 w-5 items-center justify-center rounded-full bg-muted font-bold">
|
||||
<span className="text-xs">2</span>
|
||||
</div>
|
||||
<Trans>Invite team members to collaborate</Trans>
|
||||
</li>
|
||||
<li className="flex flex-row items-center gap-2">
|
||||
<div className="bg-muted mt-0.5 flex h-5 w-5 items-center justify-center rounded-full font-bold">
|
||||
<div className="mt-0.5 flex h-5 w-5 items-center justify-center rounded-full bg-muted font-bold">
|
||||
<span className="text-xs">3</span>
|
||||
</div>
|
||||
<Trans>Manage permissions and access controls</Trans>
|
||||
@@ -96,7 +96,7 @@ export default function OrganisationSettingsTeamsPage() {
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-muted-foreground mb-8 max-w-md text-center text-sm">
|
||||
<p className="mb-8 max-w-md text-center text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
You currently have no access to any teams within this organisation. Please contact
|
||||
your organisation to request access.
|
||||
@@ -114,20 +114,22 @@ export default function OrganisationSettingsTeamsPage() {
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
<Trans>{organisation.name} Teams</Trans>
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-1 text-sm">
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
<Trans>Select a team to view its dashboard</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button asChild>
|
||||
<Link to={`/o/${organisation.url}/settings`}>Manage Organisation</Link>
|
||||
<Link to={`/o/${organisation.url}/settings`}>
|
||||
<Trans>Manage Organisation</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{organisation.teams.map((team) => (
|
||||
<Link to={`/t/${team.url}`} key={team.id}>
|
||||
<Card className="hover:bg-muted/50 border-border h-full border transition-all">
|
||||
<Card className="h-full border border-border transition-all hover:bg-muted/50">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-10 w-10 border-2 border-solid">
|
||||
@@ -143,7 +145,7 @@ export default function OrganisationSettingsTeamsPage() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-medium">{team.name}</h3>
|
||||
<div className="text-muted-foreground truncate text-xs">
|
||||
<div className="truncate text-xs text-muted-foreground">
|
||||
{formatTeamUrl(team.url)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -152,11 +154,11 @@ export default function OrganisationSettingsTeamsPage() {
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex items-center gap-4">
|
||||
<div className="text-muted-foreground flex items-center gap-1 text-xs">
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<CalendarIcon className="h-3 w-3" />
|
||||
{i18n.date(team.createdAt, { dateStyle: 'short' })}
|
||||
</div>
|
||||
<div className="text-muted-foreground flex items-center gap-1 text-xs">
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<UserIcon className="h-3 w-3" />
|
||||
<span>{t(TEAM_MEMBER_ROLE_MAP[team.currentTeamRole])}</span>
|
||||
</div>
|
||||
|
||||
@@ -79,7 +79,7 @@ export default function OrganisationSettingsBrandingPage() {
|
||||
if (isLoadingOrganisation || !organisationWithSettings) {
|
||||
return (
|
||||
<div className="flex items-center justify-center rounded-lg py-32">
|
||||
<Loader className="text-muted-foreground h-6 w-6 animate-spin" />
|
||||
<Loader className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ export default function OrganisationSettingsDocumentPage() {
|
||||
includeSigningCertificate,
|
||||
includeAuditLog,
|
||||
signatureTypes,
|
||||
delegateDocumentOwnership,
|
||||
aiFeaturesEnabled,
|
||||
} = data;
|
||||
|
||||
@@ -85,6 +86,7 @@ export default function OrganisationSettingsDocumentPage() {
|
||||
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
|
||||
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
|
||||
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
|
||||
delegateDocumentOwnership: delegateDocumentOwnership,
|
||||
aiFeaturesEnabled,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -100,7 +100,7 @@ export default function DocumentPage({ params }: Route.ComponentProps) {
|
||||
<DocumentRecipientLinkCopyDialog recipients={envelope.recipients} />
|
||||
)}
|
||||
|
||||
<Link to={documentRootPath} className="flex items-center text-[#7AC455] hover:opacity-80">
|
||||
<Link to={documentRootPath} className="flex items-center text-documenso-700 hover:opacity-80">
|
||||
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
|
||||
<Trans>Documents</Trans>
|
||||
</Link>
|
||||
|
||||
@@ -84,7 +84,7 @@ export default function DocumentEditPage() {
|
||||
|
||||
return (
|
||||
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
|
||||
<Link to={documentRootPath} className="flex items-center text-[#7AC455] hover:opacity-80">
|
||||
<Link to={documentRootPath} className="flex items-center text-documenso-700 hover:opacity-80">
|
||||
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
|
||||
<Trans>Documents</Trans>
|
||||
</Link>
|
||||
|
||||
@@ -75,11 +75,12 @@ export async function loader({ params, request }: Route.LoaderArgs) {
|
||||
},
|
||||
recipients: envelope.recipients,
|
||||
documentRootPath,
|
||||
userId: user.id,
|
||||
};
|
||||
}
|
||||
|
||||
export default function DocumentsLogsPage({ loaderData }: Route.ComponentProps) {
|
||||
const { document, recipients, documentRootPath } = loaderData;
|
||||
const { document, recipients, documentRootPath, userId } = loaderData;
|
||||
|
||||
const { _, i18n } = useLingui();
|
||||
|
||||
@@ -133,7 +134,7 @@ export default function DocumentsLogsPage({ loaderData }: Route.ComponentProps)
|
||||
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
|
||||
<Link
|
||||
to={`${documentRootPath}/${document.envelopeId}`}
|
||||
className="flex items-center text-[#7AC455] hover:opacity-80"
|
||||
className="flex items-center text-documenso-700 hover:opacity-80"
|
||||
>
|
||||
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
|
||||
<Trans>Document</Trans>
|
||||
@@ -171,15 +172,15 @@ export default function DocumentsLogsPage({ loaderData }: Route.ComponentProps)
|
||||
<section className="mt-6">
|
||||
<Card className="grid grid-cols-1 gap-4 p-4 sm:grid-cols-2" degrees={45} gradient>
|
||||
{documentInformation.map((info, i) => (
|
||||
<div className="text-foreground text-sm" key={i}>
|
||||
<div className="text-sm text-foreground" key={i}>
|
||||
<h3 className="font-semibold">{_(info.description)}</h3>
|
||||
<p className="text-muted-foreground truncate">{info.value}</p>
|
||||
<p className="truncate text-muted-foreground">{info.value}</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="text-foreground text-sm">
|
||||
<div className="text-sm text-foreground">
|
||||
<h3 className="font-semibold">Recipients</h3>
|
||||
<ul className="text-muted-foreground list-inside list-disc">
|
||||
<ul className="list-inside list-disc text-muted-foreground">
|
||||
{recipients.map((recipient) => (
|
||||
<li key={`recipient-${recipient.id}`}>
|
||||
<span>{formatRecipientText(recipient)}</span>
|
||||
@@ -191,7 +192,7 @@ export default function DocumentsLogsPage({ loaderData }: Route.ComponentProps)
|
||||
</section>
|
||||
|
||||
<section className="mt-6">
|
||||
<DocumentLogsTable documentId={document.id} />
|
||||
<DocumentLogsTable documentId={document.id} userId={userId} />
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -50,6 +50,7 @@ export default function TeamsSettingsPage() {
|
||||
includeSigningCertificate,
|
||||
includeAuditLog,
|
||||
signatureTypes,
|
||||
delegateDocumentOwnership,
|
||||
aiFeaturesEnabled,
|
||||
} = data;
|
||||
|
||||
@@ -75,6 +76,7 @@ export default function TeamsSettingsPage() {
|
||||
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
|
||||
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
|
||||
}),
|
||||
delegateDocumentOwnership: delegateDocumentOwnership,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ export default function TemplatePage({ params }: Route.ComponentProps) {
|
||||
|
||||
return (
|
||||
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
|
||||
<Link to={templateRootPath} className="flex items-center text-[#7AC455] hover:opacity-80">
|
||||
<Link to={templateRootPath} className="flex items-center text-documenso-700 hover:opacity-80">
|
||||
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
|
||||
<Trans>Templates</Trans>
|
||||
</Link>
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function TemplateEditPage() {
|
||||
<div>
|
||||
<Link
|
||||
to={`${templateRootPath}/${template.envelopeId}`}
|
||||
className="flex items-center text-[#7AC455] hover:opacity-80"
|
||||
className="flex items-center text-documenso-700 hover:opacity-80"
|
||||
>
|
||||
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
|
||||
<Trans>Template</Trans>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-
|
||||
import { unsafeGetEntireEnvelope } from '@documenso/lib/server-only/admin/get-entire-document';
|
||||
import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt';
|
||||
import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs';
|
||||
import { getOrganisationClaimByTeamId } from '@documenso/lib/server-only/organisation/get-organisation-claims';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
import { getTranslations } from '@documenso/lib/utils/i18n';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
@@ -53,6 +54,8 @@ export async function loader({ request }: Route.LoaderArgs) {
|
||||
throw redirect('/');
|
||||
}
|
||||
|
||||
const organisationClaim = await getOrganisationClaimByTeamId({ teamId: envelope.teamId });
|
||||
|
||||
const documentLanguage = ZSupportedLanguageCodeSchema.parse(envelope.documentMeta?.language);
|
||||
|
||||
const { data: auditLogs } = await findDocumentAuditLogs({
|
||||
@@ -81,6 +84,7 @@ export async function loader({ request }: Route.LoaderArgs) {
|
||||
deletedAt: envelope.deletedAt,
|
||||
documentMeta: envelope.documentMeta,
|
||||
},
|
||||
hidePoweredBy: organisationClaim.flags.hidePoweredBy,
|
||||
documentLanguage,
|
||||
messages,
|
||||
};
|
||||
@@ -95,7 +99,7 @@ export async function loader({ request }: Route.LoaderArgs) {
|
||||
* Update: Maybe <Trans> tags work now after RR7 migration.
|
||||
*/
|
||||
export default function AuditLog({ loaderData }: Route.ComponentProps) {
|
||||
const { auditLogs, document, documentLanguage, messages } = loaderData;
|
||||
const { auditLogs, document, documentLanguage, hidePoweredBy, messages } = loaderData;
|
||||
|
||||
const { i18n, _ } = useLingui();
|
||||
|
||||
@@ -145,7 +149,7 @@ export default function AuditLog({ loaderData }: Route.ComponentProps) {
|
||||
<span className="mt-1 block">
|
||||
{DateTime.fromJSDate(document.createdAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toFormat('yyyy-mm-dd hh:mm:ss a (ZZZZ)')}
|
||||
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
@@ -155,7 +159,7 @@ export default function AuditLog({ loaderData }: Route.ComponentProps) {
|
||||
<span className="mt-1 block">
|
||||
{DateTime.fromJSDate(document.updatedAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toFormat('yyyy-mm-dd hh:mm:ss a (ZZZZ)')}
|
||||
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
@@ -188,11 +192,13 @@ export default function AuditLog({ loaderData }: Route.ComponentProps) {
|
||||
<InternalAuditLogTable logs={auditLogs} />
|
||||
</div>
|
||||
|
||||
<div className="my-8 flex-row-reverse">
|
||||
<div className="flex items-end justify-end gap-x-4">
|
||||
<BrandingLogo className="max-h-6 print:max-h-4" />
|
||||
{!hidePoweredBy && (
|
||||
<div className="my-8 flex-row-reverse">
|
||||
<div className="flex items-end justify-end gap-x-4">
|
||||
<BrandingLogo className="max-h-6 print:max-h-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -355,16 +355,16 @@ const SigningPageV1 = ({ data }: { data: Awaited<ReturnType<typeof handleV1Loade
|
||||
</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="text-muted-foreground/60 mt-2.5 max-w-[60ch] text-center text-sm font-medium md:text-base">
|
||||
<p className="mt-2.5 max-w-[60ch] text-center text-sm font-medium text-muted-foreground/60 md:text-base">
|
||||
<Trans>This document has been cancelled by the owner.</Trans>
|
||||
</p>
|
||||
|
||||
{user ? (
|
||||
<Link to="/" className="text-documenso-700 hover:text-documenso-600 mt-36">
|
||||
<Link to="/" className="mt-36 text-documenso-700 hover:text-documenso-600">
|
||||
<Trans>Go Back Home</Trans>
|
||||
</Link>
|
||||
) : (
|
||||
<p className="text-muted-foreground/60 mt-36 text-sm">
|
||||
<p className="mt-36 text-sm text-muted-foreground/60">
|
||||
<Trans>
|
||||
Want to send slick signing links like this one?{' '}
|
||||
<Link
|
||||
@@ -455,16 +455,16 @@ const SigningPageV2 = ({ data }: { data: Awaited<ReturnType<typeof handleV2Loade
|
||||
</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="text-muted-foreground/60 mt-2.5 max-w-[60ch] text-center text-sm font-medium md:text-base">
|
||||
<p className="mt-2.5 max-w-[60ch] text-center text-sm font-medium text-muted-foreground/60 md:text-base">
|
||||
<Trans>This document has been cancelled by the owner.</Trans>
|
||||
</p>
|
||||
|
||||
{user ? (
|
||||
<Link to="/" className="text-documenso-700 hover:text-documenso-600 mt-36">
|
||||
<Link to="/" className="mt-36 text-documenso-700 hover:text-documenso-600">
|
||||
<Trans>Go Back Home</Trans>
|
||||
</Link>
|
||||
) : (
|
||||
<p className="text-muted-foreground/60 mt-36 text-sm">
|
||||
<p className="mt-36 text-sm text-muted-foreground/60">
|
||||
<Trans>
|
||||
Want to send slick signing links like this one?{' '}
|
||||
<Link
|
||||
|
||||
@@ -91,31 +91,33 @@ export default function RejectedSigningPage({ loaderData }: Route.ComponentProps
|
||||
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<XCircle className="text-destructive h-10 w-10" />
|
||||
<XCircle className="h-10 w-10 text-destructive" />
|
||||
|
||||
<h2 className="max-w-[35ch] text-center text-2xl font-semibold leading-normal md:text-3xl lg:text-4xl">
|
||||
<Trans>Document Rejected</Trans>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="text-destructive mt-4 flex items-center text-center text-sm">
|
||||
<div className="mt-4 flex items-center text-center text-sm text-destructive">
|
||||
<Trans>You have rejected this document</Trans>
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground mt-6 max-w-[60ch] text-center text-sm">
|
||||
<p className="mt-6 max-w-[60ch] text-center text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
The document owner has been notified of your decision. They may contact you with further
|
||||
instructions if necessary.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<p className="text-muted-foreground mt-2 max-w-[60ch] text-center text-sm">
|
||||
<p className="mt-2 max-w-[60ch] text-center text-sm text-muted-foreground">
|
||||
<Trans>No further action is required from you at this time.</Trans>
|
||||
</p>
|
||||
|
||||
{user && (
|
||||
<Button className="mt-6" asChild>
|
||||
<Link to={`/`}>Return Home</Link>
|
||||
<Link to={`/`}>
|
||||
<Trans>Return Home</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -78,14 +78,14 @@ export default function WaitingForTurnToSignPage({ loaderData }: Route.Component
|
||||
<Trans>Waiting for Your Turn</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
It's currently not your turn to sign. You will receive an email with instructions once
|
||||
it's your turn to sign the document.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<p className="text-muted-foreground mt-4 text-sm">
|
||||
<p className="mt-4 text-sm text-muted-foreground">
|
||||
<Trans>Please check your email for updates.</Trans>
|
||||
</p>
|
||||
|
||||
@@ -98,7 +98,9 @@ export default function WaitingForTurnToSignPage({ loaderData }: Route.Component
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="link" asChild>
|
||||
<Link to="/">Return Home</Link>
|
||||
<Link to="/">
|
||||
<Trans>Return Home</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -193,11 +193,11 @@ export default function OrganisationSsoConfirmationTokenPage({ loaderData }: Rou
|
||||
<CardContent className="space-y-6">
|
||||
{/* Current User Section */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-muted-foreground flex items-center gap-2 font-semibold">
|
||||
<h3 className="flex items-center gap-2 font-semibold text-muted-foreground">
|
||||
<UserCircle2 className="h-4 w-4" />
|
||||
<Trans>Your Account</Trans>
|
||||
</h3>
|
||||
<div className="bg-muted/50 flex items-center justify-between gap-3 rounded-lg p-3">
|
||||
<div className="flex items-center justify-between gap-3 rounded-lg bg-muted/50 p-3">
|
||||
<AvatarWithText
|
||||
avatarSrc={formatAvatarUrl(user.avatar)}
|
||||
avatarFallback={extractInitials(user.name || user.email)}
|
||||
@@ -215,11 +215,11 @@ export default function OrganisationSsoConfirmationTokenPage({ loaderData }: Rou
|
||||
|
||||
{/* Organisation Section */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-muted-foreground flex items-center gap-2 font-semibold">
|
||||
<h3 className="flex items-center gap-2 font-semibold text-muted-foreground">
|
||||
<Building2 className="h-4 w-4" />
|
||||
<Trans>Requesting Organisation</Trans>
|
||||
</h3>
|
||||
<div className="bg-muted/50 flex items-center justify-between gap-3 rounded-lg p-3">
|
||||
<div className="flex items-center justify-between gap-3 rounded-lg bg-muted/50 p-3">
|
||||
<AvatarWithText
|
||||
avatarSrc={formatAvatarUrl(organisation.avatar)}
|
||||
avatarFallback={extractInitials(organisation.name)}
|
||||
@@ -237,7 +237,7 @@ export default function OrganisationSsoConfirmationTokenPage({ loaderData }: Rou
|
||||
|
||||
{/* Warnings Section */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-muted-foreground flex items-center gap-2 font-semibold">
|
||||
<h3 className="flex items-center gap-2 font-semibold text-muted-foreground">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<Trans>Important: What This Means</Trans>
|
||||
</h3>
|
||||
@@ -253,7 +253,7 @@ export default function OrganisationSsoConfirmationTokenPage({ loaderData }: Rou
|
||||
<Eye className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
<span>
|
||||
<Trans>
|
||||
<span className="text-muted-foreground font-semibold">
|
||||
<span className="font-semibold text-muted-foreground">
|
||||
Full account access:
|
||||
</span>{' '}
|
||||
View all your profile information, settings, and activity
|
||||
@@ -264,7 +264,7 @@ export default function OrganisationSsoConfirmationTokenPage({ loaderData }: Rou
|
||||
<Settings className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
<span>
|
||||
<Trans>
|
||||
<span className="text-muted-foreground font-semibold">
|
||||
<span className="font-semibold text-muted-foreground">
|
||||
Account management:
|
||||
</span>{' '}
|
||||
Modify your account settings, permissions, and preferences
|
||||
@@ -275,7 +275,7 @@ export default function OrganisationSsoConfirmationTokenPage({ loaderData }: Rou
|
||||
<Database className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
<span>
|
||||
<Trans>
|
||||
<span className="text-muted-foreground font-semibold">Data access:</span>{' '}
|
||||
<span className="font-semibold text-muted-foreground">Data access:</span>{' '}
|
||||
Access all data associated with your account
|
||||
</Trans>
|
||||
</span>
|
||||
@@ -304,7 +304,7 @@ export default function OrganisationSsoConfirmationTokenPage({ loaderData }: Rou
|
||||
/>
|
||||
|
||||
<label
|
||||
className="text-muted-foreground ml-2 flex flex-row items-center text-sm"
|
||||
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
|
||||
htmlFor={`accept-conditions`}
|
||||
>
|
||||
<Trans>I agree to link my account with this organization</Trans>
|
||||
|
||||
@@ -107,5 +107,5 @@
|
||||
"vite-plugin-babel-macros": "^1.0.6",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"version": "2.2.7"
|
||||
"version": "2.4.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
###########################
|
||||
# BASE CONTAINER #
|
||||
###########################
|
||||
FROM node:22-alpine3.20 AS base
|
||||
FROM node:22-alpine3.22 AS base
|
||||
|
||||
RUN apk add --no-cache openssl
|
||||
RUN apk add --no-cache font-freefont
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
ARG TAG=latest
|
||||
FROM documenso/documenso:${TAG}
|
||||
|
||||
# Install @playwright/browser-chromium which bundles Playwright + Chromium
|
||||
RUN npm install @playwright/browser-chromium
|
||||
@@ -5,7 +5,7 @@
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
],
|
||||
"version": "2.2.7",
|
||||
"version": "2.4.0",
|
||||
"scripts": {
|
||||
"postinstall": "patch-package",
|
||||
"build": "turbo run build",
|
||||
@@ -51,9 +51,9 @@
|
||||
"@commitlint/config-conventional": "^20.0.0",
|
||||
"@lingui/cli": "^5.6.0",
|
||||
"@prisma/client": "^6.19.0",
|
||||
"@trpc/client": "11.7.1",
|
||||
"@trpc/react-query": "11.7.1",
|
||||
"@trpc/server": "11.7.1",
|
||||
"@trpc/client": "11.8.1",
|
||||
"@trpc/react-query": "11.8.1",
|
||||
"@trpc/server": "11.8.1",
|
||||
"@ts-rest/core": "^3.52.1",
|
||||
"@ts-rest/open-api": "^3.52.1",
|
||||
"@ts-rest/serverless": "^3.52.1",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { type APIRequestContext, expect, test } from '@playwright/test';
|
||||
import type { Team, User } from '@prisma/client';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
@@ -27,6 +27,7 @@ import type {
|
||||
import type { TDistributeEnvelopeRequest } from '@documenso/trpc/server/envelope-router/distribute-envelope.types';
|
||||
import type { TCreateEnvelopeRecipientsRequest } from '@documenso/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.types';
|
||||
import type { TUpdateEnvelopeRecipientsRequest } from '@documenso/trpc/server/envelope-router/envelope-recipients/update-envelope-recipients.types';
|
||||
import type { TFindEnvelopesResponse } from '@documenso/trpc/server/envelope-router/find-envelopes.types';
|
||||
import type { TGetEnvelopeResponse } from '@documenso/trpc/server/envelope-router/get-envelope.types';
|
||||
import type { TUpdateEnvelopeRequest } from '@documenso/trpc/server/envelope-router/update-envelope.types';
|
||||
|
||||
@@ -562,6 +563,200 @@ test.describe('API V2 Envelopes', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Envelope find endpoint', () => {
|
||||
const createEnvelope = async (
|
||||
request: APIRequestContext,
|
||||
token: string,
|
||||
payload: TCreateEnvelopePayload,
|
||||
) => {
|
||||
const formData = new FormData();
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
|
||||
const pdfData = fs.readFileSync(
|
||||
path.join(__dirname, '../../../../../assets/field-font-alignment.pdf'),
|
||||
);
|
||||
formData.append('files', new File([pdfData], 'test.pdf', { type: 'application/pdf' }));
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
return (await res.json()) as TCreateEnvelopeResponse;
|
||||
};
|
||||
|
||||
test('should find envelopes with pagination', async ({ request }) => {
|
||||
// Create 3 envelopes
|
||||
await createEnvelope(request, tokenA, {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document 1',
|
||||
});
|
||||
await createEnvelope(request, tokenA, {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document 2',
|
||||
});
|
||||
await createEnvelope(request, tokenA, {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template 1',
|
||||
});
|
||||
|
||||
// Find all envelopes
|
||||
const res = await request.get(`${baseUrl}/envelope`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TFindEnvelopesResponse;
|
||||
|
||||
expect(response.data.length).toBe(3);
|
||||
expect(response.count).toBe(3);
|
||||
expect(response.currentPage).toBe(1);
|
||||
expect(response.totalPages).toBe(1);
|
||||
|
||||
// Test pagination
|
||||
const paginatedRes = await request.get(`${baseUrl}/envelope?perPage=2&page=1`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
});
|
||||
|
||||
expect(paginatedRes.ok()).toBeTruthy();
|
||||
const paginatedResponse = (await paginatedRes.json()) as TFindEnvelopesResponse;
|
||||
|
||||
expect(paginatedResponse.data.length).toBe(2);
|
||||
expect(paginatedResponse.count).toBe(3);
|
||||
expect(paginatedResponse.totalPages).toBe(2);
|
||||
});
|
||||
|
||||
test('should filter envelopes by type', async ({ request }) => {
|
||||
await createEnvelope(request, tokenA, {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document Only',
|
||||
});
|
||||
await createEnvelope(request, tokenA, {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template Only',
|
||||
});
|
||||
|
||||
// Filter by DOCUMENT type
|
||||
const documentRes = await request.get(`${baseUrl}/envelope?type=DOCUMENT`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
});
|
||||
|
||||
expect(documentRes.ok()).toBeTruthy();
|
||||
const documentResponse = (await documentRes.json()) as TFindEnvelopesResponse;
|
||||
|
||||
expect(documentResponse.data.every((e) => e.type === EnvelopeType.DOCUMENT)).toBe(true);
|
||||
|
||||
// Filter by TEMPLATE type
|
||||
const templateRes = await request.get(`${baseUrl}/envelope?type=TEMPLATE`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
});
|
||||
|
||||
expect(templateRes.ok()).toBeTruthy();
|
||||
const templateResponse = (await templateRes.json()) as TFindEnvelopesResponse;
|
||||
|
||||
expect(templateResponse.data.every((e) => e.type === EnvelopeType.TEMPLATE)).toBe(true);
|
||||
});
|
||||
|
||||
test('should filter envelopes by status', async ({ request }) => {
|
||||
await createEnvelope(request, tokenA, {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Draft Document',
|
||||
});
|
||||
|
||||
// Filter by DRAFT status (default for new envelopes)
|
||||
const res = await request.get(`${baseUrl}/envelope?status=DRAFT`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
const response = (await res.json()) as TFindEnvelopesResponse;
|
||||
|
||||
expect(response.data.every((e) => e.status === DocumentStatus.DRAFT)).toBe(true);
|
||||
});
|
||||
|
||||
test('should search envelopes by query', async ({ request }) => {
|
||||
await createEnvelope(request, tokenA, {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Unique Searchable Title',
|
||||
});
|
||||
await createEnvelope(request, tokenA, {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Another Document',
|
||||
});
|
||||
|
||||
const res = await request.get(`${baseUrl}/envelope?query=Unique%20Searchable`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
const response = (await res.json()) as TFindEnvelopesResponse;
|
||||
|
||||
expect(response.data.length).toBe(1);
|
||||
expect(response.data[0].title).toBe('Unique Searchable Title');
|
||||
});
|
||||
|
||||
test('should not return envelopes from other users', async ({ request }) => {
|
||||
// Create envelope for userA
|
||||
await createEnvelope(request, tokenA, {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'UserA Document',
|
||||
});
|
||||
|
||||
// Create envelope for userB
|
||||
await createEnvelope(request, tokenB, {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'UserB Document',
|
||||
});
|
||||
|
||||
// userA should only see their own envelopes
|
||||
const resA = await request.get(`${baseUrl}/envelope`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
});
|
||||
|
||||
expect(resA.ok()).toBeTruthy();
|
||||
const responseA = (await resA.json()) as TFindEnvelopesResponse;
|
||||
|
||||
expect(responseA.data.every((e) => e.title !== 'UserB Document')).toBe(true);
|
||||
|
||||
// userB should only see their own envelopes
|
||||
const resB = await request.get(`${baseUrl}/envelope`, {
|
||||
headers: { Authorization: `Bearer ${tokenB}` },
|
||||
});
|
||||
|
||||
expect(resB.ok()).toBeTruthy();
|
||||
const responseB = (await resB.json()) as TFindEnvelopesResponse;
|
||||
|
||||
expect(responseB.data.every((e) => e.title !== 'UserA Document')).toBe(true);
|
||||
});
|
||||
|
||||
test('should return envelope with expected schema fields', async ({ request }) => {
|
||||
await createEnvelope(request, tokenA, {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Schema Test Document',
|
||||
});
|
||||
|
||||
const res = await request.get(`${baseUrl}/envelope`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
const response = (await res.json()) as TFindEnvelopesResponse;
|
||||
|
||||
const envelope = response.data.find((e) => e.title === 'Schema Test Document');
|
||||
|
||||
expect(envelope).toBeDefined();
|
||||
expect(envelope?.id).toBeDefined();
|
||||
expect(envelope?.type).toBe(EnvelopeType.DOCUMENT);
|
||||
expect(envelope?.status).toBe(DocumentStatus.DRAFT);
|
||||
expect(envelope?.recipients).toBeDefined();
|
||||
expect(envelope?.user).toBeDefined();
|
||||
expect(envelope?.team).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Empty recipient tests', () => {
|
||||
test('Create template envelope with empty email recipient', async ({ request }) => {
|
||||
const payload = {
|
||||
|
||||
@@ -12,14 +12,17 @@ import {
|
||||
} from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
DocumentStatus,
|
||||
DocumentVisibility,
|
||||
EnvelopeType,
|
||||
FieldType,
|
||||
FolderType,
|
||||
Prisma,
|
||||
ReadStatus,
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
TeamMemberRole,
|
||||
} from '@documenso/prisma/client';
|
||||
import {
|
||||
seedBlankDocument,
|
||||
@@ -28,14 +31,18 @@ import {
|
||||
seedPendingDocument,
|
||||
} from '@documenso/prisma/seed/documents';
|
||||
import { seedBlankFolder } from '@documenso/prisma/seed/folders';
|
||||
import { seedTeamMember } from '@documenso/prisma/seed/teams';
|
||||
import { seedBlankTemplate, seedTemplate } from '@documenso/prisma/seed/templates';
|
||||
import { seedUser } from '@documenso/prisma/seed/users';
|
||||
import type { TCreateEnvelopeItemsPayload } from '@documenso/trpc/server/envelope-router/create-envelope-items.types';
|
||||
import type { TFindEnvelopesResponse } from '@documenso/trpc/server/envelope-router/find-envelopes.types';
|
||||
import type {
|
||||
TUseEnvelopePayload,
|
||||
TUseEnvelopeResponse,
|
||||
} from '@documenso/trpc/server/envelope-router/use-envelope.types';
|
||||
|
||||
import { apiSignin } from '../../fixtures/authentication';
|
||||
|
||||
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
|
||||
|
||||
test.describe.configure({
|
||||
@@ -2990,6 +2997,566 @@ test.describe('Document API V2', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Envelope get-many endpoint', () => {
|
||||
test('should block unauthorized access to envelope get-many endpoint', async ({
|
||||
request,
|
||||
}) => {
|
||||
const doc1 = await seedBlankDocument(userA, teamA.id);
|
||||
const doc2 = await seedBlankDocument(userA, teamA.id);
|
||||
|
||||
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
|
||||
headers: { Authorization: `Bearer ${tokenB}` },
|
||||
data: {
|
||||
ids: {
|
||||
type: 'envelopeId',
|
||||
ids: [doc1.id, doc2.id],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const { data } = await res.json();
|
||||
expect(data).toEqual([]);
|
||||
});
|
||||
|
||||
test('should allow authorized access to envelope get-many endpoint', async ({ request }) => {
|
||||
const doc1 = await seedBlankDocument(userA, teamA.id);
|
||||
const doc2 = await seedBlankDocument(userA, teamA.id);
|
||||
|
||||
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
data: {
|
||||
ids: {
|
||||
type: 'envelopeId',
|
||||
ids: [doc1.id, doc2.id],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const { data } = await res.json();
|
||||
expect(data.length).toBe(2);
|
||||
expect(data.map((d: { id: string }) => d.id).sort()).toEqual([doc1.id, doc2.id].sort());
|
||||
});
|
||||
|
||||
test('should only return authorized envelopes when mixing owned and unowned', async ({
|
||||
request,
|
||||
}) => {
|
||||
const docA = await seedBlankDocument(userA, teamA.id);
|
||||
const docB = await seedBlankDocument(userB, teamB.id);
|
||||
|
||||
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
data: {
|
||||
ids: {
|
||||
type: 'envelopeId',
|
||||
ids: [docA.id, docB.id],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const { data } = await res.json();
|
||||
expect(data.length).toBe(1);
|
||||
expect(data[0].id).toBe(docA.id);
|
||||
});
|
||||
|
||||
test('should block unauthorized access with documentId type', async ({ request }) => {
|
||||
const doc1 = await seedBlankDocument(userA, teamA.id);
|
||||
const doc2 = await seedBlankDocument(userA, teamA.id);
|
||||
|
||||
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
|
||||
headers: { Authorization: `Bearer ${tokenB}` },
|
||||
data: {
|
||||
ids: {
|
||||
type: 'documentId',
|
||||
ids: [
|
||||
mapSecondaryIdToDocumentId(doc1.secondaryId),
|
||||
mapSecondaryIdToDocumentId(doc2.secondaryId),
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const { data } = await res.json();
|
||||
expect(data).toEqual([]);
|
||||
});
|
||||
|
||||
test('should allow authorized access with documentId type', async ({ request }) => {
|
||||
const doc1 = await seedBlankDocument(userA, teamA.id);
|
||||
const doc2 = await seedBlankDocument(userA, teamA.id);
|
||||
|
||||
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
data: {
|
||||
ids: {
|
||||
type: 'documentId',
|
||||
ids: [
|
||||
mapSecondaryIdToDocumentId(doc1.secondaryId),
|
||||
mapSecondaryIdToDocumentId(doc2.secondaryId),
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const { data } = await res.json();
|
||||
expect(data.length).toBe(2);
|
||||
});
|
||||
|
||||
test('should block unauthorized access with templateId type', async ({ request }) => {
|
||||
const template1 = await seedBlankTemplate(userA, teamA.id);
|
||||
const template2 = await seedBlankTemplate(userA, teamA.id);
|
||||
|
||||
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
|
||||
headers: { Authorization: `Bearer ${tokenB}` },
|
||||
data: {
|
||||
ids: {
|
||||
type: 'templateId',
|
||||
ids: [
|
||||
mapSecondaryIdToTemplateId(template1.secondaryId),
|
||||
mapSecondaryIdToTemplateId(template2.secondaryId),
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const { data } = await res.json();
|
||||
expect(data).toEqual([]);
|
||||
});
|
||||
|
||||
test('should allow authorized access with templateId type', async ({ request }) => {
|
||||
const template1 = await seedBlankTemplate(userA, teamA.id);
|
||||
const template2 = await seedBlankTemplate(userA, teamA.id);
|
||||
|
||||
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
data: {
|
||||
ids: {
|
||||
type: 'templateId',
|
||||
ids: [
|
||||
mapSecondaryIdToTemplateId(template1.secondaryId),
|
||||
mapSecondaryIdToTemplateId(template2.secondaryId),
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const { data } = await res.json();
|
||||
expect(data.length).toBe(2);
|
||||
});
|
||||
|
||||
test('should reject requests exceeding max ID limit', async ({ request }) => {
|
||||
const ids = Array.from({ length: 21 }, () => 'envelope_fake123');
|
||||
|
||||
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/get-many`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
data: {
|
||||
ids: {
|
||||
type: 'envelopeId',
|
||||
ids,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeFalsy();
|
||||
expect(res.status()).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Envelope get-many tRPC endpoint (teamId manipulation)', () => {
|
||||
test('should block access when user manipulates x-team-id to another team', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Create documents for userA in teamA
|
||||
const doc1 = await seedBlankDocument(userA, teamA.id);
|
||||
const doc2 = await seedBlankDocument(userA, teamA.id);
|
||||
|
||||
// Sign in as userB
|
||||
await apiSignin({ page, email: userB.email });
|
||||
|
||||
const res = await page
|
||||
.context()
|
||||
.request.post(`${WEBAPP_BASE_URL}/api/trpc/envelope.getMany`, {
|
||||
headers: {
|
||||
'x-team-id': String(teamA.id),
|
||||
},
|
||||
data: {
|
||||
json: {
|
||||
ids: {
|
||||
type: 'envelopeId',
|
||||
ids: [doc1.id, doc2.id],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Make tRPC request with manipulated x-team-id pointing to teamA (which userB doesn't belong to)
|
||||
expect(res.ok()).toBeFalsy();
|
||||
// Team not found
|
||||
expect(res.status()).toBe(404);
|
||||
});
|
||||
|
||||
test('should allow access when user uses their own team id', async ({ page }) => {
|
||||
// Create documents for userA in teamA
|
||||
const doc1 = await seedBlankDocument(userA, teamA.id);
|
||||
const doc2 = await seedBlankDocument(userA, teamA.id);
|
||||
|
||||
// Sign in as userA
|
||||
await apiSignin({ page, email: userA.email });
|
||||
|
||||
const res = await page
|
||||
.context()
|
||||
.request.post(`${WEBAPP_BASE_URL}/api/trpc/envelope.getMany`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-team-id': String(teamA.id),
|
||||
},
|
||||
data: {
|
||||
json: {
|
||||
ids: {
|
||||
type: 'envelopeId',
|
||||
ids: [doc1.id, doc2.id],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
const items = data.result.data.json.data;
|
||||
|
||||
expect(items.length).toBe(2);
|
||||
expect(items.map((d: { id: string }) => d.id).sort()).toEqual([doc1.id, doc2.id].sort());
|
||||
});
|
||||
|
||||
test('should block access when switching team id mid-request to access other team data', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Create a document for userA in teamA
|
||||
const docA = await seedBlankDocument(userA, teamA.id);
|
||||
// Create a document for userB in teamB
|
||||
const docB = await seedBlankDocument(userB, teamB.id);
|
||||
|
||||
// Sign in as userB
|
||||
await apiSignin({ page, email: userB.email });
|
||||
|
||||
const res = await page
|
||||
.context()
|
||||
.request.post(`${WEBAPP_BASE_URL}/api/trpc/envelope.getMany`, {
|
||||
headers: {
|
||||
'x-team-id': String(teamA.id),
|
||||
},
|
||||
data: {
|
||||
json: {
|
||||
ids: {
|
||||
type: 'envelopeId',
|
||||
ids: [docA.id, docB.id],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// UserB tries to access both documents by manipulating teamId to teamA
|
||||
// Should fail - userB is not a member of teamA
|
||||
expect(res.ok()).toBeFalsy();
|
||||
// Team not found
|
||||
expect(res.status()).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Envelope find endpoint', () => {
|
||||
test('should block unauthorized access to envelope find endpoint', async ({ request }) => {
|
||||
await seedBlankDocument(userA, teamA.id);
|
||||
|
||||
const res = await request.get(`${WEBAPP_BASE_URL}/api/v2-beta/envelope`, {
|
||||
headers: { Authorization: `Bearer ${tokenB}` },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const data = (await res.json()) as TFindEnvelopesResponse;
|
||||
expect(data.data.every((doc) => doc.userId !== userA.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('should allow authorized access to envelope find endpoint', async ({ request }) => {
|
||||
await seedBlankDocument(userA, teamA.id);
|
||||
|
||||
const res = await request.get(`${WEBAPP_BASE_URL}/api/v2-beta/envelope`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const data = (await res.json()) as TFindEnvelopesResponse;
|
||||
expect(data.data.length).toBeGreaterThan(0);
|
||||
expect(data.data.some((doc) => doc.userId === userA.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('should respect team document visibility for ADMIN role', async ({ request }) => {
|
||||
const adminMember = await seedTeamMember({
|
||||
teamId: teamA.id,
|
||||
role: TeamMemberRole.ADMIN,
|
||||
});
|
||||
|
||||
const { token: adminToken } = await createApiToken({
|
||||
userId: adminMember.id,
|
||||
teamId: teamA.id,
|
||||
tokenName: 'adminMember',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
visibility: DocumentVisibility.ADMIN,
|
||||
title: 'Admin Only Document',
|
||||
},
|
||||
});
|
||||
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
visibility: DocumentVisibility.MANAGER_AND_ABOVE,
|
||||
title: 'Manager and Above Document',
|
||||
},
|
||||
});
|
||||
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
visibility: DocumentVisibility.EVERYONE,
|
||||
title: 'Everyone Document',
|
||||
},
|
||||
});
|
||||
|
||||
const res = await request.get(`${WEBAPP_BASE_URL}/api/v2-beta/envelope`, {
|
||||
headers: { Authorization: `Bearer ${adminToken}` },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
const data = (await res.json()) as TFindEnvelopesResponse;
|
||||
|
||||
const titles = data.data.map((doc) => doc.title);
|
||||
expect(titles).toContain('Admin Only Document');
|
||||
expect(titles).toContain('Manager and Above Document');
|
||||
expect(titles).toContain('Everyone Document');
|
||||
});
|
||||
|
||||
test('should respect team document visibility for MANAGER role', async ({ request }) => {
|
||||
const managerMember = await seedTeamMember({
|
||||
teamId: teamA.id,
|
||||
role: TeamMemberRole.MANAGER,
|
||||
});
|
||||
|
||||
const { token: managerToken } = await createApiToken({
|
||||
userId: managerMember.id,
|
||||
teamId: teamA.id,
|
||||
tokenName: 'managerMember',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
visibility: DocumentVisibility.ADMIN,
|
||||
title: 'Admin Only Document',
|
||||
},
|
||||
});
|
||||
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
visibility: DocumentVisibility.MANAGER_AND_ABOVE,
|
||||
title: 'Manager and Above Document',
|
||||
},
|
||||
});
|
||||
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
visibility: DocumentVisibility.EVERYONE,
|
||||
title: 'Everyone Document',
|
||||
},
|
||||
});
|
||||
|
||||
const res = await request.get(`${WEBAPP_BASE_URL}/api/v2-beta/envelope`, {
|
||||
headers: { Authorization: `Bearer ${managerToken}` },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
const data = (await res.json()) as TFindEnvelopesResponse;
|
||||
|
||||
const titles = data.data.map((doc) => doc.title);
|
||||
expect(titles).not.toContain('Admin Only Document');
|
||||
expect(titles).toContain('Manager and Above Document');
|
||||
expect(titles).toContain('Everyone Document');
|
||||
});
|
||||
|
||||
test('should filter envelopes by folderId with authorization', async ({ request }) => {
|
||||
const folder = await prisma.folder.create({
|
||||
data: {
|
||||
userId: userA.id,
|
||||
teamId: teamA.id,
|
||||
name: 'Test Folder',
|
||||
type: FolderType.DOCUMENT,
|
||||
},
|
||||
});
|
||||
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
folderId: folder.id,
|
||||
title: 'Document in Folder',
|
||||
},
|
||||
});
|
||||
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
title: 'Document Not in Folder',
|
||||
},
|
||||
});
|
||||
|
||||
const resWithFolder = await request.get(
|
||||
`${WEBAPP_BASE_URL}/api/v2-beta/envelope?folderId=${folder.id}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
},
|
||||
);
|
||||
|
||||
expect(resWithFolder.ok()).toBeTruthy();
|
||||
const dataWithFolder = (await resWithFolder.json()) as TFindEnvelopesResponse;
|
||||
expect(dataWithFolder.data.every((doc) => doc.folderId === folder.id)).toBe(true);
|
||||
expect(dataWithFolder.data.some((doc) => doc.title === 'Document in Folder')).toBe(true);
|
||||
|
||||
const resUnauthorized = await request.get(
|
||||
`${WEBAPP_BASE_URL}/api/v2-beta/envelope?folderId=${folder.id}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${tokenB}` },
|
||||
},
|
||||
);
|
||||
|
||||
expect(resUnauthorized.ok()).toBeTruthy();
|
||||
const dataUnauthorized = (await resUnauthorized.json()) as TFindEnvelopesResponse;
|
||||
expect(
|
||||
dataUnauthorized.data.every(
|
||||
(doc) => doc.folderId !== folder.id || doc.userId !== userA.id,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('should filter envelopes by type with authorization', async ({ request }) => {
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'UserA Document',
|
||||
},
|
||||
});
|
||||
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'UserA Template',
|
||||
},
|
||||
});
|
||||
|
||||
await seedBlankDocument(userB, teamB.id, {
|
||||
createDocumentOptions: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'UserB Document',
|
||||
},
|
||||
});
|
||||
|
||||
const res = await request.get(`${WEBAPP_BASE_URL}/api/v2-beta/envelope?type=DOCUMENT`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
const data = (await res.json()) as TFindEnvelopesResponse;
|
||||
expect(data.data.every((doc) => doc.type === EnvelopeType.DOCUMENT)).toBe(true);
|
||||
expect(data.data.every((doc) => doc.userId === userA.id)).toBe(true);
|
||||
expect(data.data.some((doc) => doc.title === 'UserA Document')).toBe(true);
|
||||
expect(data.data.every((doc) => doc.title !== 'UserB Document')).toBe(true);
|
||||
});
|
||||
|
||||
test('should filter envelopes by status with authorization', async ({ request }) => {
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
title: 'Draft Document',
|
||||
status: DocumentStatus.DRAFT,
|
||||
},
|
||||
});
|
||||
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
title: 'Completed Document',
|
||||
status: DocumentStatus.COMPLETED,
|
||||
},
|
||||
});
|
||||
|
||||
await seedBlankDocument(userB, teamB.id, {
|
||||
createDocumentOptions: {
|
||||
title: 'UserB Draft',
|
||||
status: DocumentStatus.DRAFT,
|
||||
},
|
||||
});
|
||||
|
||||
const res = await request.get(`${WEBAPP_BASE_URL}/api/v2-beta/envelope?status=DRAFT`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
const data = (await res.json()) as TFindEnvelopesResponse;
|
||||
expect(data.data.every((doc) => doc.status === DocumentStatus.DRAFT)).toBe(true);
|
||||
expect(data.data.every((doc) => doc.userId === userA.id)).toBe(true);
|
||||
expect(data.data.some((doc) => doc.title === 'Draft Document')).toBe(true);
|
||||
expect(data.data.every((doc) => doc.title !== 'UserB Draft')).toBe(true);
|
||||
expect(data.data.every((doc) => doc.title !== 'Completed Document')).toBe(true);
|
||||
});
|
||||
|
||||
test('should search envelopes by query with authorization', async ({ request }) => {
|
||||
await seedBlankDocument(userA, teamA.id, {
|
||||
createDocumentOptions: {
|
||||
title: 'Unique Searchable Title UserA',
|
||||
},
|
||||
});
|
||||
|
||||
await seedBlankDocument(userB, teamB.id, {
|
||||
createDocumentOptions: {
|
||||
title: 'Unique Searchable Title UserB',
|
||||
},
|
||||
});
|
||||
|
||||
const res = await request.get(
|
||||
`${WEBAPP_BASE_URL}/api/v2-beta/envelope?query=Unique%20Searchable`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
},
|
||||
);
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
const data = (await res.json()) as TFindEnvelopesResponse;
|
||||
expect(data.data.every((doc) => doc.userId === userA.id)).toBe(true);
|
||||
expect(data.data.some((doc) => doc.title.includes('UserA'))).toBe(true);
|
||||
expect(data.data.every((doc) => !doc.title.includes('UserB'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Envelope update endpoint', () => {
|
||||
test('should block unauthorized access to envelope update endpoint', async ({ request }) => {
|
||||
const doc = await seedBlankDocument(userA, teamA.id);
|
||||
|
||||
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 163 KiB |
@@ -3,3 +3,8 @@ Copyright (c) 2023 Documenso, Inc
|
||||
|
||||
- The Stripe Billing Module
|
||||
- Document Action Reauthentication (Passkeys and 2FA)
|
||||
- 21 CFR
|
||||
- Email domains
|
||||
- Embed authoring
|
||||
- Embed authoring white label
|
||||
- Enterprise level support + possible SLAs and license changes
|
||||
|
||||
@@ -113,7 +113,7 @@ export const ConfirmTeamEmailTemplate = ({
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
<Button
|
||||
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
className="inline-flex items-center justify-center rounded-lg bg-documenso-500 px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={`${baseUrl}/team/verify/email/${token}`}
|
||||
>
|
||||
<Trans>Accept</Trans>
|
||||
|
||||
@@ -72,7 +72,7 @@ export const ResetPasswordTemplate = ({
|
||||
<Trans>
|
||||
Didn't request a password change? We are here to help you secure your account,
|
||||
just{' '}
|
||||
<Link className="text-documenso-700 font-normal" href="mailto:hi@documenso.com">
|
||||
<Link className="font-normal text-documenso-700" href="mailto:hi@documenso.com">
|
||||
contact us
|
||||
</Link>
|
||||
.
|
||||
|
||||
@@ -21,3 +21,12 @@ export const USE_INTERNAL_URL_BROWSERLESS = () =>
|
||||
|
||||
export const IS_AI_FEATURES_CONFIGURED = () =>
|
||||
!!env('GOOGLE_VERTEX_PROJECT_ID') && !!env('GOOGLE_VERTEX_API_KEY');
|
||||
|
||||
/**
|
||||
* Temporary flag to toggle between Playwright-based and Konva-based PDF generation
|
||||
* for audit logs during sealing.
|
||||
*
|
||||
* @deprecated This is a temporary flag and will be removed once Konva-based generation is stable.
|
||||
*/
|
||||
export const NEXT_PRIVATE_USE_PLAYWRIGHT_PDF = () =>
|
||||
env('NEXT_PRIVATE_USE_PLAYWRIGHT_PDF') === 'true';
|
||||
|
||||
@@ -8,3 +8,8 @@ export const MIN_STANDARD_FONT_SIZE = 8;
|
||||
export const MIN_HANDWRITING_FONT_SIZE = 20;
|
||||
|
||||
export const CAVEAT_FONT_PATH = () => `${NEXT_PUBLIC_WEBAPP_URL()}/fonts/caveat.ttf`;
|
||||
|
||||
export const PDF_SIZE_A4_72PPI = {
|
||||
width: 595,
|
||||
height: 842,
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
rotateDegrees,
|
||||
translate,
|
||||
} from '@cantoo/pdf-lib';
|
||||
import type { DocumentData, DocumentMeta, Envelope, EnvelopeItem, Field } from '@prisma/client';
|
||||
import type { DocumentData, Envelope, EnvelopeItem, Field } from '@prisma/client';
|
||||
import {
|
||||
DocumentStatus,
|
||||
EnvelopeType,
|
||||
@@ -20,9 +20,13 @@ import path from 'node:path';
|
||||
import { groupBy } from 'remeda';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { generateAuditLogPdf } from '@documenso/lib/server-only/pdf/generate-audit-log-pdf';
|
||||
import { generateCertificatePdf } from '@documenso/lib/server-only/pdf/generate-certificate-pdf';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { signPdf } from '@documenso/signing';
|
||||
|
||||
import { NEXT_PRIVATE_USE_PLAYWRIGHT_PDF } from '../../../constants/app';
|
||||
import { PDF_SIZE_A4_72PPI } from '../../../constants/pdf';
|
||||
import { AppError, AppErrorCode } from '../../../errors/app-error';
|
||||
import { sendCompletedEmail } from '../../../server-only/document/send-completed-email';
|
||||
import { getAuditLogsPdf } from '../../../server-only/htmltopdf/get-audit-logs-pdf';
|
||||
@@ -48,7 +52,7 @@ import { putPdfFileServerSide } from '../../../universal/upload/put-file.server'
|
||||
import { fieldsContainUnsignedRequiredField } from '../../../utils/advanced-fields-helpers';
|
||||
import { isDocumentCompleted } from '../../../utils/document';
|
||||
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
|
||||
import { mapDocumentIdToSecondaryId, mapSecondaryIdToDocumentId } from '../../../utils/envelope';
|
||||
import { mapDocumentIdToSecondaryId } from '../../../utils/envelope';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TSealDocumentJobDefinition } from './seal-document';
|
||||
|
||||
@@ -68,8 +72,19 @@ export const run = async ({
|
||||
secondaryId: mapDocumentIdToSecondaryId(documentId),
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
fields: {
|
||||
include: {
|
||||
signature: true,
|
||||
},
|
||||
},
|
||||
envelopeItems: {
|
||||
include: {
|
||||
documentData: true,
|
||||
@@ -116,23 +131,20 @@ export const run = async ({
|
||||
});
|
||||
}
|
||||
|
||||
let envelopeItems = envelope.envelopeItems;
|
||||
let { envelopeItems } = envelope;
|
||||
|
||||
const fields = envelope.fields;
|
||||
|
||||
if (envelopeItems.length < 1) {
|
||||
throw new Error(`Document ${envelope.id} has no envelope items`);
|
||||
}
|
||||
|
||||
const recipients = await prisma.recipient.findMany({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
},
|
||||
});
|
||||
const recipientsWithoutCCers = envelope.recipients.filter(
|
||||
(recipient) => recipient.role !== RecipientRole.CC,
|
||||
);
|
||||
|
||||
// Determine if the document has been rejected by checking if any recipient has rejected it
|
||||
const rejectedRecipient = recipients.find(
|
||||
const rejectedRecipient = recipientsWithoutCCers.find(
|
||||
(recipient) => recipient.signingStatus === SigningStatus.REJECTED,
|
||||
);
|
||||
|
||||
@@ -141,15 +153,6 @@ export const run = async ({
|
||||
// Get the rejection reason from the rejected recipient
|
||||
const rejectionReason = rejectedRecipient?.rejectionReason ?? '';
|
||||
|
||||
const fields = await prisma.field.findMany({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
include: {
|
||||
signature: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Skip the field check if the document is rejected
|
||||
if (!isRejected && fieldsContainUnsignedRequiredField(fields)) {
|
||||
throw new Error(`Document ${envelope.id} has unsigned required fields`);
|
||||
@@ -178,13 +181,52 @@ export const run = async ({
|
||||
});
|
||||
}
|
||||
|
||||
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
|
||||
let certificateDoc: PDFDocument | null = null;
|
||||
let auditLogDoc: PDFDocument | null = null;
|
||||
|
||||
const { certificateData, auditLogData } = await getCertificateAndAuditLogData({
|
||||
legacyDocumentId,
|
||||
documentMeta: envelope.documentMeta,
|
||||
settings,
|
||||
});
|
||||
if (settings.includeSigningCertificate || settings.includeAuditLog) {
|
||||
const certificatePayload = {
|
||||
envelope,
|
||||
recipients: envelope.recipients, // Need to use the recipients from envelope which contains ALL recipients.
|
||||
fields,
|
||||
language: envelope.documentMeta.language,
|
||||
envelopeOwner: {
|
||||
email: envelope.user.email,
|
||||
name: envelope.user.name || '',
|
||||
},
|
||||
envelopeItems: envelopeItems.map((item) => item.title),
|
||||
pageWidth: PDF_SIZE_A4_72PPI.width,
|
||||
pageHeight: PDF_SIZE_A4_72PPI.height,
|
||||
};
|
||||
|
||||
// Use Playwright-based PDF generation if enabled, otherwise use Konva-based generation.
|
||||
// This is a temporary toggle while we validate the Konva-based approach.
|
||||
const usePlaywrightPdf = NEXT_PRIVATE_USE_PLAYWRIGHT_PDF();
|
||||
|
||||
const makeCertificatePdf = async () =>
|
||||
usePlaywrightPdf
|
||||
? getCertificatePdf({
|
||||
documentId,
|
||||
language: envelope.documentMeta.language,
|
||||
}).then(async (buffer) => PDFDocument.load(buffer))
|
||||
: generateCertificatePdf(certificatePayload);
|
||||
|
||||
const makeAuditLogPdf = async () =>
|
||||
usePlaywrightPdf
|
||||
? getAuditLogsPdf({
|
||||
documentId,
|
||||
language: envelope.documentMeta.language,
|
||||
}).then(async (buffer) => PDFDocument.load(buffer))
|
||||
: generateAuditLogPdf(certificatePayload);
|
||||
|
||||
const [createdCertificatePdf, createdAuditLogPdf] = await Promise.all([
|
||||
settings.includeSigningCertificate ? makeCertificatePdf() : null,
|
||||
settings.includeAuditLog ? makeAuditLogPdf() : null,
|
||||
]);
|
||||
|
||||
certificateDoc = createdCertificatePdf;
|
||||
auditLogDoc = createdAuditLogPdf;
|
||||
}
|
||||
|
||||
const newDocumentData: Array<{ oldDocumentDataId: string; newDocumentDataId: string }> = [];
|
||||
|
||||
@@ -203,8 +245,8 @@ export const run = async ({
|
||||
envelopeItemFields,
|
||||
isRejected,
|
||||
rejectionReason,
|
||||
certificateData,
|
||||
auditLogData,
|
||||
certificateDoc,
|
||||
auditLogDoc,
|
||||
});
|
||||
|
||||
newDocumentData.push(result);
|
||||
@@ -300,8 +342,8 @@ type DecorateAndSignPdfOptions = {
|
||||
envelopeItemFields: Field[];
|
||||
isRejected: boolean;
|
||||
rejectionReason: string;
|
||||
certificateData: Buffer | null;
|
||||
auditLogData: Buffer | null;
|
||||
certificateDoc: PDFDocument | null;
|
||||
auditLogDoc: PDFDocument | null;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -313,8 +355,8 @@ const decorateAndSignPdf = async ({
|
||||
envelopeItemFields,
|
||||
isRejected,
|
||||
rejectionReason,
|
||||
certificateData,
|
||||
auditLogData,
|
||||
certificateDoc,
|
||||
auditLogDoc,
|
||||
}: DecorateAndSignPdfOptions) => {
|
||||
const pdfData = await getFileServerSide(envelopeItem.documentData);
|
||||
|
||||
@@ -330,9 +372,7 @@ const decorateAndSignPdf = async ({
|
||||
await addRejectionStampToPdf(pdfDoc, rejectionReason);
|
||||
}
|
||||
|
||||
if (certificateData) {
|
||||
const certificateDoc = await PDFDocument.load(certificateData);
|
||||
|
||||
if (certificateDoc) {
|
||||
const certificatePages = await pdfDoc.copyPages(
|
||||
certificateDoc,
|
||||
certificateDoc.getPageIndices(),
|
||||
@@ -343,9 +383,7 @@ const decorateAndSignPdf = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (auditLogData) {
|
||||
const auditLogDoc = await PDFDocument.load(auditLogData);
|
||||
|
||||
if (auditLogDoc) {
|
||||
const auditLogPages = await pdfDoc.copyPages(auditLogDoc, auditLogDoc.getPageIndices());
|
||||
|
||||
auditLogPages.forEach((page) => {
|
||||
@@ -470,47 +508,3 @@ const decorateAndSignPdf = async ({
|
||||
newDocumentDataId: newDocumentData.id,
|
||||
};
|
||||
};
|
||||
|
||||
export const getCertificateAndAuditLogData = async ({
|
||||
legacyDocumentId,
|
||||
documentMeta,
|
||||
settings,
|
||||
}: {
|
||||
legacyDocumentId: number;
|
||||
documentMeta: DocumentMeta;
|
||||
settings: { includeSigningCertificate: boolean; includeAuditLog: boolean };
|
||||
}) => {
|
||||
const getCertificateDataPromise = settings.includeSigningCertificate
|
||||
? getCertificatePdf({
|
||||
documentId: legacyDocumentId,
|
||||
language: documentMeta.language,
|
||||
}).catch((e) => {
|
||||
console.log('Failed to get certificate PDF');
|
||||
console.error(e);
|
||||
|
||||
return null;
|
||||
})
|
||||
: null;
|
||||
|
||||
const getAuditLogDataPromise = settings.includeAuditLog
|
||||
? getAuditLogsPdf({
|
||||
documentId: legacyDocumentId,
|
||||
language: documentMeta.language,
|
||||
}).catch((e) => {
|
||||
console.log('Failed to get audit logs PDF');
|
||||
console.error(e);
|
||||
|
||||
return null;
|
||||
})
|
||||
: null;
|
||||
|
||||
const [certificateData, auditLogData] = await Promise.all([
|
||||
getCertificateDataPromise,
|
||||
getAuditLogDataPromise,
|
||||
]);
|
||||
|
||||
return {
|
||||
certificateData,
|
||||
auditLogData,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -286,7 +286,7 @@ const detectFieldsFromPage = async ({
|
||||
});
|
||||
|
||||
const result = await generateObject({
|
||||
model: vertex('gemini-3-pro-preview'),
|
||||
model: vertex('gemini-3-flash-preview'),
|
||||
system: SYSTEM_PROMPT,
|
||||
schema: ZSubmitDetectedFieldsInputSchema,
|
||||
messages,
|
||||
|
||||
@@ -207,7 +207,7 @@ const detectRecipientsFromImages = async ({
|
||||
});
|
||||
|
||||
const result = await generateObject({
|
||||
model: vertex('gemini-2.5-flash'),
|
||||
model: vertex('gemini-3-flash-preview'),
|
||||
system: SYSTEM_PROMPT,
|
||||
schema: ZDetectedRecipientsSchema,
|
||||
messages,
|
||||
|
||||
@@ -9,7 +9,10 @@ globalThis.Image = Image;
|
||||
|
||||
class SkiaCanvasFactory {
|
||||
_createCanvas(width: number, height: number) {
|
||||
return new Canvas(width, height);
|
||||
const canvas = new Canvas(width, height);
|
||||
canvas.gpu = false;
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
create(width: number, height: number) {
|
||||
@@ -60,6 +63,8 @@ export const pdfToImages = async (pdfBytes: Uint8Array, options: PdfToImagesOpti
|
||||
const viewport = page.getViewport({ scale });
|
||||
|
||||
const canvas = new Canvas(viewport.width, viewport.height);
|
||||
canvas.gpu = false;
|
||||
|
||||
const canvasContext = canvas.getContext('2d');
|
||||
|
||||
await page.render({
|
||||
|
||||
@@ -81,6 +81,7 @@ export type CreateEnvelopeOptions = {
|
||||
globalActionAuth?: TDocumentActionAuthTypes[];
|
||||
recipients?: CreateEnvelopeRecipientOptions[];
|
||||
folderId?: string;
|
||||
delegatedDocumentOwner?: string;
|
||||
};
|
||||
attachments?: Array<{
|
||||
label: string;
|
||||
@@ -114,6 +115,7 @@ export const createEnvelope = async ({
|
||||
publicTitle,
|
||||
publicDescription,
|
||||
visibility: visibilityOverride,
|
||||
delegatedDocumentOwner,
|
||||
} = data;
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
@@ -256,6 +258,43 @@ export const createEnvelope = async ({
|
||||
? await incrementDocumentId().then((v) => v.formattedDocumentId)
|
||||
: await incrementTemplateId().then((v) => v.formattedTemplateId);
|
||||
|
||||
const getValidatedDelegatedOwner = async () => {
|
||||
if (
|
||||
!settings.delegateDocumentOwnership ||
|
||||
!delegatedDocumentOwner ||
|
||||
requestMetadata.source === 'app'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const delegatedOwner = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: delegatedDocumentOwner,
|
||||
},
|
||||
});
|
||||
|
||||
if (!delegatedOwner) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Delegated document owner must be a member of the team',
|
||||
});
|
||||
}
|
||||
|
||||
const isTeamMember = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery({ teamId, userId: delegatedOwner.id }),
|
||||
});
|
||||
|
||||
if (!isTeamMember) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Delegated document owner must be a member of the team',
|
||||
});
|
||||
}
|
||||
|
||||
return delegatedOwner;
|
||||
};
|
||||
|
||||
const delegatedOwner = await getValidatedDelegatedOwner();
|
||||
const envelopeOwnerId = delegatedOwner?.id ?? userId;
|
||||
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const envelope = await tx.envelope.create({
|
||||
data: {
|
||||
@@ -285,7 +324,7 @@ export const createEnvelope = async ({
|
||||
})),
|
||||
},
|
||||
},
|
||||
userId,
|
||||
userId: envelopeOwnerId,
|
||||
teamId,
|
||||
authOptions,
|
||||
visibility,
|
||||
@@ -393,6 +432,9 @@ export const createEnvelope = async ({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
|
||||
envelopeId: envelope.id,
|
||||
user: {
|
||||
id: envelopeOwnerId,
|
||||
},
|
||||
metadata: requestMetadata,
|
||||
data: {
|
||||
title,
|
||||
@@ -403,6 +445,25 @@ export const createEnvelope = async ({
|
||||
}),
|
||||
});
|
||||
|
||||
// Create audit log for delegated owner if validation passed
|
||||
if (delegatedOwner) {
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELEGATED_OWNER_CREATED,
|
||||
envelopeId: envelope.id,
|
||||
user: {
|
||||
id: userId,
|
||||
},
|
||||
metadata: requestMetadata,
|
||||
data: {
|
||||
delegatedOwnerName: delegatedOwner.name,
|
||||
delegatedOwnerEmail: delegatedOwner.email,
|
||||
teamName: team.name,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
await triggerWebhook({
|
||||
event: WebhookTriggerEvents.DOCUMENT_CREATED,
|
||||
data: ZWebhookDocumentSchema.parse(mapEnvelopeToWebhookDocumentPayload(createdEnvelope)),
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import type {
|
||||
DocumentSource,
|
||||
DocumentStatus,
|
||||
Envelope,
|
||||
EnvelopeType,
|
||||
Prisma,
|
||||
} from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-tokens-for-document';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
|
||||
export type FindEnvelopesOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
type?: EnvelopeType;
|
||||
templateId?: number;
|
||||
source?: DocumentSource;
|
||||
status?: DocumentStatus;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
orderBy?: {
|
||||
column: keyof Pick<Envelope, 'createdAt'>;
|
||||
direction: 'asc' | 'desc';
|
||||
};
|
||||
query?: string;
|
||||
folderId?: string;
|
||||
};
|
||||
|
||||
export const findEnvelopes = async ({
|
||||
userId,
|
||||
teamId,
|
||||
type,
|
||||
templateId,
|
||||
source,
|
||||
status,
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
orderBy,
|
||||
query = '',
|
||||
folderId,
|
||||
}: FindEnvelopesOptions) => {
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
|
||||
const team = await getTeamById({
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
const orderByColumn = orderBy?.column ?? 'createdAt';
|
||||
const orderByDirection = orderBy?.direction ?? 'desc';
|
||||
|
||||
const searchFilter: Prisma.EnvelopeWhereInput = query
|
||||
? {
|
||||
OR: [
|
||||
{ title: { contains: query, mode: 'insensitive' } },
|
||||
{ externalId: { contains: query, mode: 'insensitive' } },
|
||||
{ recipients: { some: { name: { contains: query, mode: 'insensitive' } } } },
|
||||
{ recipients: { some: { email: { contains: query, mode: 'insensitive' } } } },
|
||||
],
|
||||
}
|
||||
: {};
|
||||
|
||||
const visibilityFilter: Prisma.EnvelopeWhereInput = {
|
||||
visibility: {
|
||||
in: TEAM_DOCUMENT_VISIBILITY_MAP[team.currentTeamRole],
|
||||
},
|
||||
};
|
||||
|
||||
const teamEmailFilters: Prisma.EnvelopeWhereInput[] = [];
|
||||
|
||||
if (team.teamEmail) {
|
||||
teamEmailFilters.push(
|
||||
{
|
||||
user: {
|
||||
email: team.teamEmail.email,
|
||||
},
|
||||
},
|
||||
{
|
||||
recipients: {
|
||||
some: {
|
||||
email: team.teamEmail.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const whereClause: Prisma.EnvelopeWhereInput = {
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
teamId: team.id,
|
||||
...visibilityFilter,
|
||||
},
|
||||
{
|
||||
userId,
|
||||
},
|
||||
...teamEmailFilters,
|
||||
],
|
||||
},
|
||||
{
|
||||
folderId: folderId ?? null,
|
||||
deletedAt: null,
|
||||
},
|
||||
searchFilter,
|
||||
],
|
||||
};
|
||||
|
||||
if (type) {
|
||||
whereClause.type = type;
|
||||
}
|
||||
|
||||
if (templateId) {
|
||||
whereClause.templateId = templateId;
|
||||
}
|
||||
|
||||
if (source) {
|
||||
whereClause.source = source;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.envelope.findMany({
|
||||
where: whereClause,
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
[orderByColumn]: orderByDirection,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
recipients: {
|
||||
orderBy: {
|
||||
id: 'asc',
|
||||
},
|
||||
},
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
url: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.envelope.count({
|
||||
where: whereClause,
|
||||
}),
|
||||
]);
|
||||
|
||||
const maskedData = data.map((envelope) =>
|
||||
maskRecipientTokensForDocument({
|
||||
document: envelope,
|
||||
user,
|
||||
}),
|
||||
);
|
||||
|
||||
const mappedData = maskedData.map((envelope) => ({
|
||||
...envelope,
|
||||
recipients: envelope.Recipient,
|
||||
user: {
|
||||
id: envelope.user.id,
|
||||
name: envelope.user.name || '',
|
||||
email: envelope.user.email,
|
||||
},
|
||||
}));
|
||||
|
||||
return {
|
||||
data: mappedData,
|
||||
count,
|
||||
currentPage: Math.max(page, 1),
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
} satisfies FindResultResponse<typeof mappedData>;
|
||||
};
|
||||
@@ -0,0 +1,213 @@
|
||||
import type { EnvelopeType, Prisma } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { EnvelopeIdsOptions } from '../../utils/envelope';
|
||||
import { unsafeBuildEnvelopeIdsQuery } from '../../utils/envelope';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
|
||||
export type GetEnvelopesByIdsOptions = {
|
||||
/**
|
||||
* The envelope IDs to fetch with their type.
|
||||
*/
|
||||
ids: EnvelopeIdsOptions;
|
||||
|
||||
/**
|
||||
* The user ID who has been authenticated.
|
||||
*/
|
||||
userId: number;
|
||||
|
||||
/**
|
||||
* The unvalidated team ID from the request.
|
||||
*/
|
||||
teamId: number;
|
||||
|
||||
/**
|
||||
* The type of envelope to get.
|
||||
*
|
||||
* Set to null to bypass check.
|
||||
*/
|
||||
type: EnvelopeType | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches multiple envelopes by their IDs with proper access control.
|
||||
*
|
||||
* Only returns envelopes that the user has valid access to based on:
|
||||
* 1. Document ownership (userId matches)
|
||||
* 2. Team membership with appropriate visibility level
|
||||
* 3. Team email ownership
|
||||
*
|
||||
* NOTE: Be extremely careful when modifying this function. Needs at minimum two reviewers to approve any changes.
|
||||
*/
|
||||
export const getEnvelopesByIds = async ({
|
||||
ids,
|
||||
userId,
|
||||
teamId,
|
||||
type,
|
||||
}: GetEnvelopesByIdsOptions) => {
|
||||
const { envelopeWhereInput } = await getMultipleEnvelopeWhereInput({
|
||||
ids,
|
||||
userId,
|
||||
teamId,
|
||||
type,
|
||||
});
|
||||
|
||||
const envelopes = await prisma.envelope.findMany({
|
||||
where: envelopeWhereInput,
|
||||
include: {
|
||||
envelopeItems: {
|
||||
include: {
|
||||
documentData: true,
|
||||
},
|
||||
orderBy: {
|
||||
order: 'asc',
|
||||
},
|
||||
},
|
||||
folder: true,
|
||||
documentMeta: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
recipients: {
|
||||
orderBy: {
|
||||
id: 'asc',
|
||||
},
|
||||
},
|
||||
fields: true,
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
url: true,
|
||||
},
|
||||
},
|
||||
directLink: {
|
||||
select: {
|
||||
directTemplateRecipientId: true,
|
||||
enabled: true,
|
||||
id: true,
|
||||
token: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return envelopes.map((envelope) => ({
|
||||
...envelope,
|
||||
user: {
|
||||
id: envelope.user.id,
|
||||
name: envelope.user.name || '',
|
||||
email: envelope.user.email,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
export type GetEnvelopesByIdsResponse = Awaited<ReturnType<typeof getEnvelopesByIds>>;
|
||||
|
||||
export type GetMultipleEnvelopeWhereInputOptions = {
|
||||
/**
|
||||
* The envelope IDs to fetch with their type.
|
||||
*/
|
||||
ids: EnvelopeIdsOptions;
|
||||
|
||||
/**
|
||||
* The user ID who has been authenticated.
|
||||
*/
|
||||
userId: number;
|
||||
|
||||
/**
|
||||
* The unknown teamId from the request.
|
||||
*/
|
||||
teamId: number;
|
||||
|
||||
/**
|
||||
* The type of envelope to get.
|
||||
*
|
||||
* Set to null to bypass check.
|
||||
*/
|
||||
type: EnvelopeType | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate the where input for a multiple envelope Prisma query.
|
||||
*
|
||||
* This will return a query that allows a user to get documents if they have valid access to them.
|
||||
*
|
||||
* NOTE: Be extremely careful when modifying this function. Needs at minimum two reviewers to approve any changes.
|
||||
*/
|
||||
export const getMultipleEnvelopeWhereInput = async ({
|
||||
ids,
|
||||
userId,
|
||||
teamId,
|
||||
type,
|
||||
}: GetMultipleEnvelopeWhereInputOptions) => {
|
||||
// Backup validation incase something goes wrong.
|
||||
if (!ids.ids || !userId || !teamId || type === undefined) {
|
||||
console.error(`[CRTICAL ERROR]: MUST NEVER HAPPEN`);
|
||||
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Envelope IDs not found',
|
||||
});
|
||||
}
|
||||
|
||||
// Validate that the user belongs to the team provided.
|
||||
const team = await getTeamById({ teamId, userId });
|
||||
|
||||
const envelopeOrInput: Prisma.EnvelopeWhereInput[] = [
|
||||
// Allow access if they own the document.
|
||||
{
|
||||
userId,
|
||||
},
|
||||
// Or, if they belong to the team that the document is associated with.
|
||||
{
|
||||
visibility: {
|
||||
in: TEAM_DOCUMENT_VISIBILITY_MAP[team.currentTeamRole],
|
||||
},
|
||||
teamId: team.id,
|
||||
},
|
||||
];
|
||||
|
||||
// Allow access to documents sent from the team email.
|
||||
if (team.teamEmail) {
|
||||
envelopeOrInput.push({
|
||||
user: {
|
||||
email: team.teamEmail.email,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
// NOTE: DO NOT PUT ANY CODE AFTER THIS POINT.
|
||||
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
|
||||
const envelopeWhereInput: Prisma.EnvelopeWhereInput = {
|
||||
...unsafeBuildEnvelopeIdsQuery(ids, type),
|
||||
OR: envelopeOrInput,
|
||||
};
|
||||
|
||||
// Final backup validation incase something goes wrong.
|
||||
if (
|
||||
!envelopeWhereInput.OR ||
|
||||
envelopeWhereInput.OR.length < 2 ||
|
||||
!userId ||
|
||||
!teamId ||
|
||||
!team.id ||
|
||||
teamId !== team.id
|
||||
) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Query not valid',
|
||||
});
|
||||
}
|
||||
|
||||
// Do not modify this return directly, all adjustments need to be made prior to the above if statement.
|
||||
return {
|
||||
envelopeWhereInput,
|
||||
team,
|
||||
};
|
||||
};
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @deprecated We use Konva to generate the audit logs PDF now.
|
||||
*/
|
||||
import { DateTime } from 'luxon';
|
||||
import type { Browser } from 'playwright';
|
||||
|
||||
@@ -33,7 +36,9 @@ export const getAuditLogsPdf = async ({ documentId, language }: GetAuditLogsPdfO
|
||||
// !: Previously we would have to keep the playwright version in sync with the browserless version to avoid errors.
|
||||
browser = await chromium.connectOverCDP(browserlessUrl);
|
||||
} else {
|
||||
browser = await chromium.launch();
|
||||
browser = await chromium.launch({
|
||||
executablePath: env('PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH') || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
if (!browser) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @deprecated We use Konva to generate the certificate PDF now.
|
||||
*/
|
||||
import { DateTime } from 'luxon';
|
||||
import type { Browser } from 'playwright';
|
||||
|
||||
@@ -33,7 +36,9 @@ export const getCertificatePdf = async ({ documentId, language }: GetCertificate
|
||||
// !: Previously we would have to keep the playwright version in sync with the browserless version to avoid errors.
|
||||
browser = await chromium.connectOverCDP(browserlessUrl);
|
||||
} else {
|
||||
browser = await chromium.launch();
|
||||
browser = await chromium.launch({
|
||||
executablePath: env('PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH') || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
if (!browser) {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* !: This is a workaround to fix the memory leak in the skia-canvas library.
|
||||
* !: Internals are ported from the original `konva/skia-backend.js` file.
|
||||
*/
|
||||
import { Konva } from 'konva/lib/_CoreInternals';
|
||||
import { Canvas, DOMMatrix, Image, Path2D } from 'skia-canvas';
|
||||
|
||||
// @ts-expect-error skia-canvas satisfies the requirements
|
||||
global.DOMMatrix = DOMMatrix;
|
||||
|
||||
// @ts-expect-error skia-canvas satisfies the requirements
|
||||
global.Path2D = Path2D;
|
||||
Path2D.prototype.toString = () => '[object Path2D]';
|
||||
|
||||
Konva.Util['createCanvasElement'] = () => {
|
||||
const node = new Canvas(300, 300);
|
||||
node.gpu = false;
|
||||
|
||||
if (!('style' in node) || !node['style']) {
|
||||
Object.assign(node, { style: {} });
|
||||
}
|
||||
|
||||
node.toString = () => '[object HTMLCanvasElement]';
|
||||
const ctx = node.getContext('2d');
|
||||
|
||||
Object.defineProperty(ctx, 'canvas', {
|
||||
get: () => node,
|
||||
});
|
||||
|
||||
return node as unknown as HTMLCanvasElement;
|
||||
};
|
||||
|
||||
Konva.Util.createImageElement = () => {
|
||||
const node = new Image();
|
||||
node.toString = () => '[object HTMLImageElement]';
|
||||
|
||||
return node as unknown as HTMLImageElement;
|
||||
};
|
||||
|
||||
Konva._renderBackend = 'skia-canvas';
|
||||
|
||||
export default Konva;
|
||||
@@ -4,8 +4,10 @@ import {
|
||||
PDFDict,
|
||||
type PDFDocument,
|
||||
PDFName,
|
||||
PDFNumber,
|
||||
PDFRadioGroup,
|
||||
PDFRef,
|
||||
PDFStream,
|
||||
drawObject,
|
||||
popGraphicsState,
|
||||
pushGraphicsState,
|
||||
@@ -103,6 +105,36 @@ const getAppearanceRefForWidget = (field: PDFField, widget: PDFWidgetAnnotation)
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensures that an appearance stream has the required dictionary entries to be
|
||||
* used as a Form XObject. Some PDFs have appearance streams that are missing
|
||||
* the /Subtype /Form entry, which causes Adobe Reader to fail to render them.
|
||||
*
|
||||
* Per PDF spec, a Form XObject stream requires:
|
||||
* - /Subtype /Form (required)
|
||||
* - /BBox (required, but should already exist for appearance streams)
|
||||
* - /FormType 1 (optional, defaults to 1)
|
||||
*/
|
||||
const normalizeAppearanceStream = (document: PDFDocument, appearanceRef: PDFRef) => {
|
||||
const appearanceStream = document.context.lookup(appearanceRef);
|
||||
|
||||
if (!(appearanceStream instanceof PDFStream)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dict = appearanceStream.dict;
|
||||
|
||||
// Ensure /Subtype /Form is set (required for XObject Form)
|
||||
if (!dict.has(PDFName.of('Subtype'))) {
|
||||
dict.set(PDFName.of('Subtype'), PDFName.of('Form'));
|
||||
}
|
||||
|
||||
// Ensure /FormType is set (optional, but good practice)
|
||||
if (!dict.has(PDFName.of('FormType'))) {
|
||||
dict.set(PDFName.of('FormType'), PDFNumber.of(1));
|
||||
}
|
||||
};
|
||||
|
||||
const flattenWidget = (document: PDFDocument, field: PDFField, widget: PDFWidgetAnnotation) => {
|
||||
try {
|
||||
const page = getPageForWidget(document, widget);
|
||||
@@ -117,6 +149,9 @@ const flattenWidget = (document: PDFDocument, field: PDFField, widget: PDFWidget
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the appearance stream has required XObject Form dictionary entries
|
||||
normalizeAppearanceStream(document, appearanceRef);
|
||||
|
||||
const xObjectKey = page.node.newXObject('FlatWidget', appearanceRef);
|
||||
|
||||
const rectangle = widget.getRectangle();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { i18n } from '@lingui/core';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { ZSupportedLanguageCodeSchema } from '../../constants/i18n';
|
||||
import { parseDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
import { getTranslations } from '../../utils/i18n';
|
||||
import { getOrganisationClaimByTeamId } from '../organisation/get-organisation-claims';
|
||||
import type { GenerateCertificatePdfOptions } from './generate-certificate-pdf';
|
||||
import { mergeFilesIntoPdf } from './generate-certificate-pdf';
|
||||
import { renderAuditLogs } from './render-audit-logs';
|
||||
|
||||
type GenerateAuditLogPdfOptions = GenerateCertificatePdfOptions & {
|
||||
envelopeItems: string[];
|
||||
};
|
||||
|
||||
export const generateAuditLogPdf = async (options: GenerateAuditLogPdfOptions) => {
|
||||
const { envelope, envelopeOwner, envelopeItems, recipients, language, pageWidth, pageHeight } =
|
||||
options;
|
||||
|
||||
const documentLanguage = ZSupportedLanguageCodeSchema.parse(language);
|
||||
|
||||
const [organisationClaim, auditLogs, messages] = await Promise.all([
|
||||
getOrganisationClaimByTeamId({ teamId: envelope.teamId }),
|
||||
getAuditLogs(envelope.id),
|
||||
getTranslations(documentLanguage),
|
||||
]);
|
||||
|
||||
i18n.loadAndActivate({
|
||||
locale: documentLanguage,
|
||||
messages,
|
||||
});
|
||||
|
||||
const auditLogPages = await renderAuditLogs({
|
||||
envelope,
|
||||
envelopeOwner,
|
||||
envelopeItems,
|
||||
recipients,
|
||||
auditLogs,
|
||||
hidePoweredBy: organisationClaim.flags.hidePoweredBy ?? false,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
i18n,
|
||||
});
|
||||
|
||||
return await mergeFilesIntoPdf(auditLogPages);
|
||||
};
|
||||
|
||||
const getAuditLogs = async (envelopeId: string) => {
|
||||
const auditLogs = await prisma.documentAuditLog.findMany({
|
||||
where: {
|
||||
envelopeId,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
});
|
||||
|
||||
return auditLogs.map((auditLog) => parseDocumentAuditLogData(auditLog));
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
import { PDFDocument } from '@cantoo/pdf-lib';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import type { DocumentMeta } from '@prisma/client';
|
||||
import type { Envelope, Field, Recipient, Signature } from '@prisma/client';
|
||||
import { FieldType } from '@prisma/client';
|
||||
import { prop, sortBy } from 'remeda';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { ZSupportedLanguageCodeSchema } from '../../constants/i18n';
|
||||
import type { TDocumentAuditLogBaseSchema } from '../../types/document-audit-logs';
|
||||
import { extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||
import { getTranslations } from '../../utils/i18n';
|
||||
import { getDocumentCertificateAuditLogs } from '../document/get-document-certificate-audit-logs';
|
||||
import { getOrganisationClaimByTeamId } from '../organisation/get-organisation-claims';
|
||||
import { renderCertificate } from './render-certificate';
|
||||
|
||||
export type GenerateCertificatePdfOptions = {
|
||||
envelope: Envelope & {
|
||||
documentMeta: DocumentMeta;
|
||||
};
|
||||
envelopeOwner: {
|
||||
name: string;
|
||||
email: string;
|
||||
};
|
||||
recipients: Recipient[];
|
||||
fields: (Pick<Field, 'id' | 'type' | 'secondaryId' | 'recipientId'> & {
|
||||
signature?: Pick<Signature, 'signatureImageAsBase64' | 'typedSignature'> | null;
|
||||
})[];
|
||||
language?: string;
|
||||
pageWidth: number;
|
||||
pageHeight: number;
|
||||
};
|
||||
|
||||
export const generateCertificatePdf = async (options: GenerateCertificatePdfOptions) => {
|
||||
const { envelope, envelopeOwner, recipients, fields, language, pageWidth, pageHeight } = options;
|
||||
|
||||
const documentLanguage = ZSupportedLanguageCodeSchema.parse(language);
|
||||
|
||||
const [organisationClaim, auditLogs, messages] = await Promise.all([
|
||||
getOrganisationClaimByTeamId({ teamId: envelope.teamId }),
|
||||
getDocumentCertificateAuditLogs({
|
||||
envelopeId: envelope.id,
|
||||
}),
|
||||
getTranslations(documentLanguage),
|
||||
]);
|
||||
|
||||
i18n.loadAndActivate({
|
||||
locale: documentLanguage,
|
||||
messages,
|
||||
});
|
||||
|
||||
const payload = {
|
||||
recipients: recipients.map((recipient) => {
|
||||
const recipientId = recipient.id;
|
||||
|
||||
const signatureField = fields.find(
|
||||
(field) => field.recipientId === recipient.id && field.type === FieldType.SIGNATURE,
|
||||
);
|
||||
|
||||
const emailSent: TDocumentAuditLogBaseSchema | undefined = auditLogs['EMAIL_SENT'].find(
|
||||
(log) => log.type === 'EMAIL_SENT' && log.data.recipientId === recipientId,
|
||||
);
|
||||
|
||||
const documentSent: TDocumentAuditLogBaseSchema | undefined = auditLogs['DOCUMENT_SENT'].find(
|
||||
(log) => log.type === 'DOCUMENT_SENT',
|
||||
);
|
||||
|
||||
const documentOpened: TDocumentAuditLogBaseSchema | undefined = auditLogs[
|
||||
'DOCUMENT_OPENED'
|
||||
].find((log) => log.type === 'DOCUMENT_OPENED' && log.data.recipientId === recipientId);
|
||||
|
||||
const documentRecipientCompleted: TDocumentAuditLogBaseSchema | undefined = auditLogs[
|
||||
'DOCUMENT_RECIPIENT_COMPLETED'
|
||||
].find(
|
||||
(log) =>
|
||||
log.type === 'DOCUMENT_RECIPIENT_COMPLETED' && log.data.recipientId === recipientId,
|
||||
);
|
||||
|
||||
const documentRecipientRejected: TDocumentAuditLogBaseSchema | undefined = auditLogs[
|
||||
'DOCUMENT_RECIPIENT_REJECTED'
|
||||
].find(
|
||||
(log) => log.type === 'DOCUMENT_RECIPIENT_REJECTED' && log.data.recipientId === recipientId,
|
||||
);
|
||||
|
||||
const extractedAuthMethods = extractDocumentAuthMethods({
|
||||
documentAuth: envelope.authOptions,
|
||||
recipientAuth: recipient.authOptions,
|
||||
});
|
||||
|
||||
const insertedAuditLogsWithFieldAuth = sortBy(
|
||||
auditLogs.DOCUMENT_FIELD_INSERTED.filter(
|
||||
(log) => log.data.recipientId === recipient.id && log.data.fieldSecurity,
|
||||
),
|
||||
[prop('createdAt'), 'desc'],
|
||||
);
|
||||
|
||||
const actionAuthMethod = insertedAuditLogsWithFieldAuth.at(0)?.data?.fieldSecurity?.type;
|
||||
|
||||
let authLevel = match(actionAuthMethod)
|
||||
.with('ACCOUNT', () => i18n._(msg`Account Re-Authentication`))
|
||||
.with('TWO_FACTOR_AUTH', () => i18n._(msg`Two-Factor Re-Authentication`))
|
||||
.with('PASSWORD', () => i18n._(msg`Password Re-Authentication`))
|
||||
.with('PASSKEY', () => i18n._(msg`Passkey Re-Authentication`))
|
||||
.with('EXPLICIT_NONE', () => i18n._(msg`Email`))
|
||||
.with(undefined, () => null)
|
||||
.exhaustive();
|
||||
|
||||
if (!authLevel) {
|
||||
const accessAuthMethod = extractedAuthMethods.derivedRecipientAccessAuth.at(0);
|
||||
|
||||
authLevel = match(accessAuthMethod)
|
||||
.with('ACCOUNT', () => i18n._(msg`Account Authentication`))
|
||||
.with('TWO_FACTOR_AUTH', () => i18n._(msg`Two-Factor Authentication`))
|
||||
.with(undefined, () => i18n._(msg`Email`))
|
||||
.exhaustive();
|
||||
}
|
||||
|
||||
return {
|
||||
id: recipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
signingStatus: recipient.signingStatus,
|
||||
signatureField,
|
||||
rejectionReason: recipient.rejectionReason,
|
||||
authLevel,
|
||||
logs: {
|
||||
emailed: emailSent ?? null,
|
||||
sent: documentSent ?? null,
|
||||
opened: documentOpened ?? null,
|
||||
completed: documentRecipientCompleted ?? null,
|
||||
rejected: documentRecipientRejected ?? null,
|
||||
},
|
||||
};
|
||||
}),
|
||||
envelopeOwner,
|
||||
qrToken: envelope.qrToken,
|
||||
hidePoweredBy: organisationClaim.flags.hidePoweredBy ?? false,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
i18n,
|
||||
};
|
||||
|
||||
const certificatePages = await renderCertificate(payload);
|
||||
|
||||
return await mergeFilesIntoPdf(certificatePages);
|
||||
};
|
||||
|
||||
export async function mergeFilesIntoPdf(buffers: Uint8Array[]) {
|
||||
const mergedPdf = await PDFDocument.create();
|
||||
|
||||
for (const buffer of buffers) {
|
||||
const pdf = await PDFDocument.load(buffer);
|
||||
const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
|
||||
pages.forEach((p) => mergedPdf.addPage(p));
|
||||
}
|
||||
|
||||
return mergedPdf;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// sort-imports-ignore
|
||||
import 'konva/skia-backend';
|
||||
import '../konva/skia-backend';
|
||||
|
||||
import Konva from 'konva';
|
||||
import path from 'node:path';
|
||||
@@ -23,6 +23,7 @@ export const insertFieldInPDFV2 = async ({
|
||||
}: InsertFieldInPDFV2Options) => {
|
||||
const fontPath = path.join(process.cwd(), 'public/fonts');
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
FontLibrary.use({
|
||||
['Caveat']: [path.join(fontPath, 'caveat.ttf')],
|
||||
['Noto Sans']: [path.join(fontPath, 'noto-sans.ttf')],
|
||||
@@ -31,8 +32,8 @@ export const insertFieldInPDFV2 = async ({
|
||||
['Noto Sans Korean']: [path.join(fontPath, 'noto-sans-korean.ttf')],
|
||||
});
|
||||
|
||||
const stage = new Konva.Stage({ width: pageWidth, height: pageHeight });
|
||||
const layer = new Konva.Layer();
|
||||
let stage: Konva.Stage | null = new Konva.Stage({ width: pageWidth, height: pageHeight });
|
||||
let layer: Konva.Layer | null = new Konva.Layer();
|
||||
|
||||
// Render the fields onto the layer.
|
||||
for (const field of fields) {
|
||||
@@ -60,5 +61,13 @@ export const insertFieldInPDFV2 = async ({
|
||||
const canvas = layer.canvas._canvas as unknown as Canvas;
|
||||
|
||||
// Embed the SVG into the PDF
|
||||
return await canvas.toBuffer('pdf');
|
||||
const pdf = await canvas.toBuffer('pdf');
|
||||
|
||||
stage.destroy();
|
||||
layer.destroy();
|
||||
|
||||
stage = null;
|
||||
layer = null;
|
||||
|
||||
return pdf;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,721 @@
|
||||
import type { I18n } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import type { DocumentMeta } from '@prisma/client';
|
||||
import type { Envelope, RecipientRole } from '@prisma/client';
|
||||
import Konva from 'konva';
|
||||
import 'konva/skia-backend';
|
||||
import type { DateTimeFormatOptions } from 'luxon';
|
||||
import { DateTime } from 'luxon';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import type { Canvas } from 'skia-canvas';
|
||||
import { FontLibrary } from 'skia-canvas';
|
||||
import { Image as SkiaImage } from 'skia-canvas';
|
||||
import { match } from 'ts-pattern';
|
||||
import { P } from 'ts-pattern';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
|
||||
import { DOCUMENT_STATUS } from '../../constants/document';
|
||||
import { APP_I18N_OPTIONS } from '../../constants/i18n';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '../../constants/recipient-roles';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import type { TDocumentAuditLog } from '../../types/document-audit-logs';
|
||||
import { formatDocumentAuditLogAction } from '../../utils/document-audit-logs';
|
||||
|
||||
export type AuditLogRecipient = {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
role: RecipientRole;
|
||||
};
|
||||
|
||||
type GenerateAuditLogsOptions = {
|
||||
envelope: Envelope & {
|
||||
documentMeta: DocumentMeta;
|
||||
};
|
||||
envelopeItems: string[];
|
||||
recipients: AuditLogRecipient[];
|
||||
auditLogs: TDocumentAuditLog[];
|
||||
hidePoweredBy: boolean;
|
||||
pageWidth: number;
|
||||
pageHeight: number;
|
||||
i18n: I18n;
|
||||
envelopeOwner: {
|
||||
email: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
const parser = new UAParser();
|
||||
|
||||
const textMutedForegroundLight = '#929DAE';
|
||||
const textForeground = '#000';
|
||||
const textMutedForeground = '#64748B';
|
||||
const textBase = 10;
|
||||
const textSm = 9;
|
||||
const textXs = 8;
|
||||
const fontMedium = '500';
|
||||
|
||||
const pageTopMargin = 60;
|
||||
const pageBottomMargin = 15;
|
||||
const contentMaxWidth = 768;
|
||||
const rowPadding = 10;
|
||||
const titleFontSize = 18;
|
||||
|
||||
type RenderOverviewCardLabelAndTextOptions = {
|
||||
label: string;
|
||||
text: string | string[];
|
||||
width: number;
|
||||
groupX?: number;
|
||||
};
|
||||
|
||||
const renderOverviewCardLabels = (options: RenderOverviewCardLabelAndTextOptions) => {
|
||||
const { width, text } = options;
|
||||
|
||||
const labelYSpacing = 4;
|
||||
|
||||
const group = new Konva.Group({
|
||||
x: options.groupX ?? 0,
|
||||
});
|
||||
|
||||
const label = new Konva.Text({
|
||||
x: 0,
|
||||
y: 0,
|
||||
text: options.label,
|
||||
fontStyle: fontMedium,
|
||||
fontFamily: 'Inter',
|
||||
fill: textForeground,
|
||||
fontSize: textSm,
|
||||
});
|
||||
|
||||
group.add(label);
|
||||
|
||||
if (typeof text === 'string') {
|
||||
const value = new Konva.Text({
|
||||
x: 0,
|
||||
y: label.height() + labelYSpacing,
|
||||
width: width - label.width(),
|
||||
fontFamily: 'Inter',
|
||||
text,
|
||||
fill: textForeground,
|
||||
wrap: 'char',
|
||||
fontSize: textSm,
|
||||
});
|
||||
|
||||
group.add(value);
|
||||
} else {
|
||||
for (const textValue of text) {
|
||||
const value = new Konva.Text({
|
||||
x: 0,
|
||||
y: group.getClientRect().height + 4,
|
||||
width: width - label.width(),
|
||||
fontFamily: 'Inter',
|
||||
text: '• ' + textValue,
|
||||
fill: textForeground,
|
||||
wrap: 'char',
|
||||
fontSize: textSm,
|
||||
});
|
||||
|
||||
group.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return group;
|
||||
};
|
||||
|
||||
type RenderVerticalLabelAndTextOptions = {
|
||||
label: string;
|
||||
text: string;
|
||||
width?: number;
|
||||
align?: 'left' | 'right';
|
||||
x?: number;
|
||||
y?: number;
|
||||
textFontFamily?: string;
|
||||
};
|
||||
|
||||
const renderVerticalLabelAndText = (options: RenderVerticalLabelAndTextOptions) => {
|
||||
const { label, text, width, align, x, y, textFontFamily } = options;
|
||||
|
||||
const group = new Konva.Group({
|
||||
x: x ?? 0,
|
||||
y: y ?? 0,
|
||||
});
|
||||
|
||||
const konvaLabel = new Konva.Text({
|
||||
align: align ?? 'left',
|
||||
fontFamily: 'Inter',
|
||||
width,
|
||||
text: label,
|
||||
fontSize: textXs,
|
||||
fill: textMutedForegroundLight,
|
||||
});
|
||||
|
||||
group.add(konvaLabel);
|
||||
|
||||
const konvaText = new Konva.Text({
|
||||
y: group.getClientRect().height + 6,
|
||||
align: align ?? 'left',
|
||||
fontFamily: textFontFamily ?? 'Inter',
|
||||
width,
|
||||
text: text,
|
||||
fontSize: textXs,
|
||||
fill: textForeground,
|
||||
});
|
||||
|
||||
group.add(konvaText);
|
||||
|
||||
return group;
|
||||
};
|
||||
|
||||
type RenderOverviewCardOptions = {
|
||||
envelope: Envelope & {
|
||||
documentMeta: DocumentMeta;
|
||||
};
|
||||
envelopeItems: string[];
|
||||
envelopeOwner: {
|
||||
email: string;
|
||||
name: string;
|
||||
};
|
||||
recipients: AuditLogRecipient[];
|
||||
width: number;
|
||||
i18n: I18n;
|
||||
};
|
||||
|
||||
const renderOverviewCard = (options: RenderOverviewCardOptions) => {
|
||||
const { envelope, envelopeItems, envelopeOwner, recipients, width, i18n } = options;
|
||||
const cardPadding = 16;
|
||||
|
||||
const overviewCard = new Konva.Group();
|
||||
|
||||
const columnSpacing = 10;
|
||||
const columnWidth = (width - columnSpacing) / 2;
|
||||
const rowVerticalSpacing = 32;
|
||||
|
||||
const rowOne = new Konva.Group({
|
||||
x: cardPadding,
|
||||
y: cardPadding,
|
||||
});
|
||||
|
||||
const envelopeIdLabel = renderOverviewCardLabels({
|
||||
label: i18n._(msg`Envelope ID`),
|
||||
text: envelope.id,
|
||||
width: columnWidth,
|
||||
});
|
||||
const ownerLabel = renderOverviewCardLabels({
|
||||
label: i18n._(msg`Owner`),
|
||||
text: `${envelopeOwner.name} (${envelopeOwner.email})`,
|
||||
width: columnWidth,
|
||||
groupX: columnWidth + columnSpacing,
|
||||
});
|
||||
|
||||
rowOne.add(envelopeIdLabel);
|
||||
rowOne.add(ownerLabel);
|
||||
overviewCard.add(rowOne);
|
||||
|
||||
const rowTwo = new Konva.Group({
|
||||
x: cardPadding,
|
||||
y: overviewCard.getClientRect().height + rowVerticalSpacing,
|
||||
});
|
||||
|
||||
const statusLabel = renderOverviewCardLabels({
|
||||
label: i18n._(msg`Status`),
|
||||
text: i18n
|
||||
._(envelope.deletedAt ? msg`Deleted` : DOCUMENT_STATUS[envelope.status].description)
|
||||
.toUpperCase(),
|
||||
width: columnWidth,
|
||||
});
|
||||
const timeZoneLabel = renderOverviewCardLabels({
|
||||
label: i18n._(msg`Time Zone`),
|
||||
text: envelope.documentMeta?.timezone || 'N/A',
|
||||
width: columnWidth,
|
||||
groupX: columnWidth + columnSpacing,
|
||||
});
|
||||
|
||||
rowTwo.add(statusLabel);
|
||||
rowTwo.add(timeZoneLabel);
|
||||
overviewCard.add(rowTwo);
|
||||
|
||||
const rowThree = new Konva.Group({
|
||||
x: cardPadding,
|
||||
y: overviewCard.getClientRect().height + rowVerticalSpacing,
|
||||
});
|
||||
|
||||
const createdAtLabel = renderOverviewCardLabels({
|
||||
label: i18n._(msg`Created At`),
|
||||
text: DateTime.fromJSDate(envelope.createdAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)'),
|
||||
width: columnWidth,
|
||||
});
|
||||
const lastUpdatedLabel = renderOverviewCardLabels({
|
||||
label: i18n._(msg`Last Updated`),
|
||||
text: DateTime.fromJSDate(envelope.updatedAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)'),
|
||||
width: columnWidth,
|
||||
groupX: columnWidth + columnSpacing,
|
||||
});
|
||||
|
||||
rowThree.add(createdAtLabel);
|
||||
rowThree.add(lastUpdatedLabel);
|
||||
overviewCard.add(rowThree);
|
||||
|
||||
const rowFour = new Konva.Group({
|
||||
x: cardPadding,
|
||||
y: overviewCard.getClientRect().height + rowVerticalSpacing,
|
||||
});
|
||||
|
||||
const enclosedDocumentsLabel = renderOverviewCardLabels({
|
||||
label: i18n._(msg`Enclosed Documents`),
|
||||
text: envelopeItems,
|
||||
width: columnWidth,
|
||||
});
|
||||
|
||||
const recipientsLabel = renderOverviewCardLabels({
|
||||
label: i18n._(msg`Recipients`),
|
||||
text: recipients.map(
|
||||
(recipient) =>
|
||||
`[${i18n._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}] ${recipient.name} (${recipient.email})`,
|
||||
),
|
||||
width: columnWidth,
|
||||
groupX: columnWidth + columnSpacing,
|
||||
});
|
||||
|
||||
rowFour.add(enclosedDocumentsLabel);
|
||||
rowFour.add(recipientsLabel);
|
||||
overviewCard.add(rowFour);
|
||||
|
||||
// Create rect border around the overview card
|
||||
const cardRect = new Konva.Rect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height: overviewCard.getClientRect().height + cardPadding * 2,
|
||||
stroke: '#e5e7eb',
|
||||
strokeWidth: 1.5,
|
||||
cornerRadius: 8,
|
||||
});
|
||||
|
||||
overviewCard.add(cardRect);
|
||||
|
||||
return overviewCard;
|
||||
};
|
||||
|
||||
type RenderRowOptions = {
|
||||
auditLog: TDocumentAuditLog;
|
||||
width: number;
|
||||
i18n: I18n;
|
||||
};
|
||||
|
||||
const renderRow = (options: RenderRowOptions) => {
|
||||
const { auditLog, width, i18n } = options;
|
||||
|
||||
const paddingWithinCard = 12;
|
||||
|
||||
const columnSpacing = 10;
|
||||
const columnWidth = (width - paddingWithinCard * 2 - columnSpacing) / 2;
|
||||
|
||||
const indicatorWidth = 3;
|
||||
const indicatorPaddingRight = 10;
|
||||
const rowGroup = new Konva.Group();
|
||||
|
||||
const rowHeaderGroup = new Konva.Group();
|
||||
|
||||
const auditLogIndicatorColor = new Konva.Circle({
|
||||
x: indicatorWidth,
|
||||
y: indicatorWidth + 3,
|
||||
radius: indicatorWidth,
|
||||
fill: getAuditLogIndicatorColor(auditLog.type),
|
||||
});
|
||||
|
||||
const auditLogTypeText = new Konva.Text({
|
||||
x: indicatorWidth + indicatorPaddingRight,
|
||||
y: 0,
|
||||
width: columnWidth - indicatorWidth - indicatorPaddingRight,
|
||||
text: auditLog.type.replace(/_/g, ' '),
|
||||
fontFamily: 'Inter',
|
||||
fontSize: textSm,
|
||||
fontStyle: fontMedium,
|
||||
fill: textMutedForeground,
|
||||
});
|
||||
|
||||
const auditLogDescriptionText = new Konva.Text({
|
||||
x: indicatorWidth + indicatorPaddingRight,
|
||||
y: auditLogTypeText.height() + 4,
|
||||
width: columnWidth - indicatorWidth - indicatorPaddingRight,
|
||||
text: formatDocumentAuditLogAction(i18n, auditLog).description,
|
||||
fontFamily: 'Inter',
|
||||
fontSize: textSm,
|
||||
fill: textForeground,
|
||||
});
|
||||
|
||||
const auditLogTimestampText = new Konva.Text({
|
||||
x: columnWidth + columnSpacing,
|
||||
width: columnWidth,
|
||||
text: DateTime.fromJSDate(auditLog.createdAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toLocaleString(dateFormat),
|
||||
fontFamily: 'Inter',
|
||||
align: 'right',
|
||||
fontSize: textSm,
|
||||
fill: textMutedForeground,
|
||||
});
|
||||
|
||||
rowHeaderGroup.add(auditLogIndicatorColor);
|
||||
rowHeaderGroup.add(auditLogTypeText);
|
||||
rowHeaderGroup.add(auditLogDescriptionText);
|
||||
rowHeaderGroup.add(auditLogTimestampText);
|
||||
|
||||
rowHeaderGroup.setAttrs({
|
||||
x: paddingWithinCard,
|
||||
y: paddingWithinCard,
|
||||
} satisfies Partial<Konva.GroupConfig>);
|
||||
|
||||
rowGroup.add(rowHeaderGroup);
|
||||
|
||||
// Draw border line.
|
||||
const borderLine = new Konva.Line({
|
||||
points: [0, 0, width - paddingWithinCard * 2, 0],
|
||||
stroke: '#e5e7eb',
|
||||
strokeWidth: 1,
|
||||
x: paddingWithinCard,
|
||||
y: rowGroup.getClientRect().height + paddingWithinCard + 12,
|
||||
});
|
||||
|
||||
rowGroup.add(borderLine);
|
||||
|
||||
const bottomSection = new Konva.Group({
|
||||
x: paddingWithinCard,
|
||||
y: rowGroup.getClientRect().height + paddingWithinCard + 12,
|
||||
});
|
||||
|
||||
// Row 1 Column 1
|
||||
const userLabel = renderVerticalLabelAndText({
|
||||
label: i18n._(msg`User`).toUpperCase(),
|
||||
text: auditLog.email || 'N/A',
|
||||
align: 'left',
|
||||
width: columnWidth,
|
||||
textFontFamily: 'ui-monospace',
|
||||
});
|
||||
|
||||
// Row 1 Column 2
|
||||
const ipAddressLabel = renderVerticalLabelAndText({
|
||||
label: i18n._(msg`IP Address`).toUpperCase(),
|
||||
text: auditLog.ipAddress || 'N/A',
|
||||
align: 'right',
|
||||
x: columnWidth + columnSpacing,
|
||||
width: columnWidth,
|
||||
textFontFamily: 'ui-monospace',
|
||||
});
|
||||
|
||||
bottomSection.add(userLabel);
|
||||
bottomSection.add(ipAddressLabel);
|
||||
|
||||
parser.setUA(auditLog.userAgent || '');
|
||||
const userAgentInfo = parser.getResult();
|
||||
|
||||
// Row 2 Column 1
|
||||
const userAgentLabel = renderVerticalLabelAndText({
|
||||
label: i18n._(msg`User Agent`).toUpperCase(),
|
||||
text: i18n._(formatUserAgent(auditLog.userAgent, userAgentInfo)),
|
||||
align: 'left',
|
||||
width,
|
||||
y: bottomSection.getClientRect().height + 16,
|
||||
});
|
||||
|
||||
bottomSection.add(userAgentLabel);
|
||||
rowGroup.add(bottomSection);
|
||||
|
||||
const cardRect = new Konva.Rect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: rowGroup.getClientRect().width,
|
||||
height: rowGroup.getClientRect().height + paddingWithinCard * 2,
|
||||
stroke: '#e5e7eb',
|
||||
strokeWidth: 1,
|
||||
cornerRadius: 8,
|
||||
});
|
||||
|
||||
rowGroup.add(cardRect);
|
||||
|
||||
return rowGroup;
|
||||
};
|
||||
|
||||
const renderBranding = () => {
|
||||
const branding = new Konva.Group();
|
||||
|
||||
const brandingHeight = 16;
|
||||
|
||||
const logoPath = path.join(process.cwd(), 'public/static/logo.png');
|
||||
const logo = fs.readFileSync(logoPath);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const img = new SkiaImage(logo) as unknown as HTMLImageElement;
|
||||
|
||||
const brandingImage = new Konva.Image({
|
||||
image: img,
|
||||
height: brandingHeight,
|
||||
width: brandingHeight * (img.width / img.height),
|
||||
});
|
||||
|
||||
branding.add(brandingImage);
|
||||
return branding;
|
||||
};
|
||||
|
||||
type GroupRowsIntoPagesOptions = {
|
||||
auditLogs: TDocumentAuditLog[];
|
||||
maxHeight: number;
|
||||
contentWidth: number;
|
||||
i18n: I18n;
|
||||
overviewCard: Konva.Group;
|
||||
};
|
||||
|
||||
const groupRowsIntoPages = (options: GroupRowsIntoPagesOptions) => {
|
||||
const { auditLogs, maxHeight, contentWidth, i18n, overviewCard } = options;
|
||||
|
||||
const groupedRows: Konva.Group[][] = [[]];
|
||||
|
||||
const overviewCardHeight = overviewCard.getClientRect().height;
|
||||
|
||||
// First page has title + overview card
|
||||
let availableHeight = maxHeight - pageTopMargin - overviewCardHeight;
|
||||
let currentGroupedRowIndex = 0;
|
||||
|
||||
// Group rows into pages.
|
||||
for (const auditLog of auditLogs) {
|
||||
const row = renderRow({ auditLog, width: contentWidth, i18n });
|
||||
|
||||
const rowHeight = row.getClientRect().height;
|
||||
const requiredHeight = rowHeight + rowPadding;
|
||||
|
||||
if (requiredHeight > availableHeight) {
|
||||
currentGroupedRowIndex++;
|
||||
groupedRows[currentGroupedRowIndex] = [row];
|
||||
|
||||
// Subsequent pages only have title (no overview card)
|
||||
availableHeight = maxHeight - pageTopMargin;
|
||||
} else {
|
||||
groupedRows[currentGroupedRowIndex].push(row);
|
||||
}
|
||||
|
||||
// Reduce available height by the row height.
|
||||
availableHeight -= requiredHeight;
|
||||
}
|
||||
|
||||
return groupedRows;
|
||||
};
|
||||
|
||||
type RenderPagesOptions = {
|
||||
groupedRows: Konva.Group[][];
|
||||
margin: number;
|
||||
pageTopMargin: number;
|
||||
i18n: I18n;
|
||||
overviewCard: Konva.Group;
|
||||
};
|
||||
|
||||
const renderPages = (options: RenderPagesOptions) => {
|
||||
const { groupedRows, margin, pageTopMargin, i18n, overviewCard } = options;
|
||||
|
||||
const rowPadding = 10;
|
||||
const pages: Konva.Group[] = [];
|
||||
|
||||
// Render the rows for each page.
|
||||
for (const [pageIndex, rows] of groupedRows.entries()) {
|
||||
const pageGroup = new Konva.Group();
|
||||
|
||||
// Add title to each page
|
||||
const pageTitle = new Konva.Text({
|
||||
x: margin,
|
||||
y: 0,
|
||||
height: pageTopMargin,
|
||||
verticalAlign: 'middle',
|
||||
text: i18n._(msg`Audit Log`),
|
||||
fill: textForeground,
|
||||
fontFamily: 'Inter',
|
||||
fontSize: titleFontSize,
|
||||
fontStyle: '700',
|
||||
});
|
||||
pageGroup.add(pageTitle);
|
||||
|
||||
// Add overview card only on first page
|
||||
if (pageIndex === 0) {
|
||||
overviewCard.setAttrs({
|
||||
x: margin,
|
||||
y: pageGroup.getClientRect().height,
|
||||
});
|
||||
pageGroup.add(overviewCard);
|
||||
}
|
||||
|
||||
// Add rows to the page
|
||||
for (const row of rows) {
|
||||
const yPosition = pageGroup.getClientRect().height + rowPadding;
|
||||
|
||||
row.setAttrs({
|
||||
x: margin,
|
||||
y: yPosition,
|
||||
});
|
||||
|
||||
pageGroup.add(row);
|
||||
}
|
||||
|
||||
pages.push(pageGroup);
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
export async function renderAuditLogs({
|
||||
envelope,
|
||||
envelopeOwner,
|
||||
envelopeItems,
|
||||
recipients,
|
||||
auditLogs,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
i18n,
|
||||
hidePoweredBy,
|
||||
}: GenerateAuditLogsOptions) {
|
||||
const fontPath = path.join(process.cwd(), 'public/fonts');
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
FontLibrary.use({
|
||||
['Caveat']: [path.join(fontPath, 'caveat.ttf')],
|
||||
['Inter']: [path.join(fontPath, 'inter-variablefont_opsz,wght.ttf')],
|
||||
});
|
||||
|
||||
const minimumMargin = 10;
|
||||
|
||||
const contentWidth = Math.min(pageWidth - minimumMargin * 2, contentMaxWidth);
|
||||
const margin = (pageWidth - contentWidth) / 2;
|
||||
|
||||
let stage: Konva.Stage | null = new Konva.Stage({ width: pageWidth, height: pageHeight });
|
||||
|
||||
const overviewCard = renderOverviewCard({
|
||||
envelope,
|
||||
envelopeOwner,
|
||||
envelopeItems,
|
||||
recipients,
|
||||
width: contentWidth,
|
||||
i18n,
|
||||
});
|
||||
|
||||
const groupedRows = groupRowsIntoPages({
|
||||
auditLogs,
|
||||
maxHeight: pageHeight,
|
||||
contentWidth,
|
||||
i18n,
|
||||
overviewCard,
|
||||
});
|
||||
|
||||
const pageGroups = renderPages({
|
||||
groupedRows,
|
||||
margin,
|
||||
pageTopMargin,
|
||||
i18n,
|
||||
overviewCard,
|
||||
});
|
||||
|
||||
const brandingGroup = renderBranding();
|
||||
const brandingRect = brandingGroup.getClientRect();
|
||||
const brandingTopPadding = 24;
|
||||
|
||||
const pages: Uint8Array[] = [];
|
||||
|
||||
let isBrandingPlaced = false;
|
||||
|
||||
// Render each page group to PDF
|
||||
for (const [index, pageGroup] of pageGroups.entries()) {
|
||||
stage.destroyChildren();
|
||||
const page = new Konva.Layer();
|
||||
|
||||
page.add(pageGroup);
|
||||
|
||||
// Add branding on the last page if there is space.
|
||||
if (index === pageGroups.length - 1 && !hidePoweredBy) {
|
||||
const remainingHeight = pageHeight - pageGroup.getClientRect().height - pageBottomMargin;
|
||||
|
||||
if (brandingRect.height + brandingTopPadding <= remainingHeight) {
|
||||
brandingGroup.setAttrs({
|
||||
x: pageWidth - brandingRect.width - margin,
|
||||
y: pageGroup.getClientRect().height + brandingTopPadding,
|
||||
} satisfies Partial<Konva.GroupConfig>);
|
||||
|
||||
page.add(brandingGroup);
|
||||
isBrandingPlaced = true;
|
||||
}
|
||||
}
|
||||
|
||||
stage.add(page);
|
||||
|
||||
// Export the page and save it.
|
||||
const canvas = page.canvas._canvas as unknown as Canvas; // eslint-disable-line @typescript-eslint/consistent-type-assertions
|
||||
const buffer = await canvas.toBuffer('pdf');
|
||||
pages.push(new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
// Need to create an empty page for the branding if it hasn't been placed yet.
|
||||
if (!hidePoweredBy && !isBrandingPlaced) {
|
||||
stage.destroyChildren();
|
||||
const page = new Konva.Layer();
|
||||
|
||||
brandingGroup.setAttrs({
|
||||
x: pageWidth - brandingRect.width - margin,
|
||||
y: pageTopMargin,
|
||||
} satisfies Partial<Konva.GroupConfig>);
|
||||
|
||||
page.add(brandingGroup);
|
||||
stage.add(page);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const canvas = page.canvas._canvas as unknown as Canvas;
|
||||
const buffer = await canvas.toBuffer('pdf');
|
||||
|
||||
pages.push(new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
stage.destroy();
|
||||
stage = null;
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
const dateFormat: DateTimeFormatOptions = {
|
||||
...DateTime.DATETIME_SHORT,
|
||||
hourCycle: 'h12',
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the color indicator for the audit log type
|
||||
*/
|
||||
const getAuditLogIndicatorColor = (type: string) =>
|
||||
match(type)
|
||||
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED, () => '#22c55e') // bg-green-500
|
||||
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED, () => '#ef4444') // bg-red-500
|
||||
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT, () => '#f97316') // bg-orange-500
|
||||
.with(
|
||||
P.union(
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED,
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_UNINSERTED,
|
||||
),
|
||||
() => '#3b82f6', // bg-blue-500
|
||||
)
|
||||
.otherwise(() => '#f1f5f9'); // bg-muted
|
||||
|
||||
const formatUserAgent = (userAgent: string | null | undefined, userAgentInfo: UAParser.IResult) => {
|
||||
if (!userAgent) {
|
||||
return msg`N/A`;
|
||||
}
|
||||
|
||||
const browser = userAgentInfo.browser.name;
|
||||
const version = userAgentInfo.browser.version;
|
||||
const os = userAgentInfo.os.name;
|
||||
|
||||
// If we can parse meaningful browser info, format it nicely
|
||||
if (browser && os) {
|
||||
const browserInfo = version ? `${browser} ${version}` : browser;
|
||||
|
||||
return msg`${browserInfo} on ${os}`;
|
||||
}
|
||||
|
||||
return msg`${userAgent}`;
|
||||
};
|
||||
@@ -0,0 +1,819 @@
|
||||
import type { I18n } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import type { Field, Signature } from '@prisma/client';
|
||||
import { SigningStatus } from '@prisma/client';
|
||||
import type { RecipientRole } from '@prisma/client';
|
||||
import Konva from 'konva';
|
||||
import 'konva/skia-backend';
|
||||
import { DateTime } from 'luxon';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import type { Canvas } from 'skia-canvas';
|
||||
import { FontLibrary } from 'skia-canvas';
|
||||
import { Image as SkiaImage } from 'skia-canvas';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import { renderSVG } from 'uqr';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { APP_I18N_OPTIONS } from '../../constants/i18n';
|
||||
import {
|
||||
RECIPIENT_ROLES_DESCRIPTION,
|
||||
RECIPIENT_ROLE_SIGNING_REASONS,
|
||||
} from '../../constants/recipient-roles';
|
||||
import type { TDocumentAuditLogBaseSchema } from '../../types/document-audit-logs';
|
||||
import { svgToPng } from '../../utils/images/svg-to-png';
|
||||
|
||||
type ColumnWidths = [number, number, number];
|
||||
|
||||
type BaseAuditLog = Pick<TDocumentAuditLogBaseSchema, 'createdAt' | 'ipAddress' | 'userAgent'>;
|
||||
|
||||
export type CertificateRecipient = {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
role: RecipientRole;
|
||||
rejectionReason: string | null;
|
||||
signingStatus: SigningStatus;
|
||||
signatureField?: Pick<Field, 'id' | 'secondaryId' | 'recipientId'> & {
|
||||
signature?: Pick<Signature, 'signatureImageAsBase64' | 'typedSignature'> | null;
|
||||
};
|
||||
authLevel: string;
|
||||
logs: {
|
||||
emailed: BaseAuditLog | null;
|
||||
sent: BaseAuditLog | null;
|
||||
opened: BaseAuditLog | null;
|
||||
completed: BaseAuditLog | null;
|
||||
rejected: BaseAuditLog | null;
|
||||
};
|
||||
};
|
||||
|
||||
type GenerateCertificateOptions = {
|
||||
recipients: CertificateRecipient[];
|
||||
qrToken: string | null;
|
||||
hidePoweredBy: boolean;
|
||||
i18n: I18n;
|
||||
envelopeOwner: {
|
||||
name: string;
|
||||
email: string;
|
||||
};
|
||||
pageWidth: number;
|
||||
pageHeight: number;
|
||||
};
|
||||
|
||||
// Helper function to get device info from user agent
|
||||
const getDevice = (userAgent?: string | null): string => {
|
||||
if (!userAgent) {
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
const parser = new UAParser(userAgent);
|
||||
|
||||
parser.setUA(userAgent);
|
||||
|
||||
const result = parser.getResult();
|
||||
|
||||
return `${result.os.name} - ${result.browser.name} ${result.browser.version}`;
|
||||
};
|
||||
|
||||
const textMutedForegroundLight = '#929DAE';
|
||||
const textForeground = '#000';
|
||||
const textMutedForeground = '#64748B';
|
||||
const textBase = 10;
|
||||
const textSm = 9;
|
||||
const textXs = 8;
|
||||
const fontMedium = '500';
|
||||
|
||||
const columnWidthPercentages = [30, 30, 40];
|
||||
const rowPadding = 12;
|
||||
const tableHeaderHeight = 38;
|
||||
const pageTopMargin = 72;
|
||||
const pageBottomMargin = 12;
|
||||
const contentMaxWidth = 768;
|
||||
|
||||
const titleFontSize = 18;
|
||||
|
||||
type RenderLabelAndTextOptions = {
|
||||
label: string;
|
||||
text: string;
|
||||
width: number;
|
||||
y?: number;
|
||||
};
|
||||
|
||||
const renderLabelAndText = (options: RenderLabelAndTextOptions) => {
|
||||
const { width, y } = options;
|
||||
|
||||
const group = new Konva.Group({
|
||||
y,
|
||||
});
|
||||
|
||||
const label = new Konva.Text({
|
||||
x: 0,
|
||||
y: 0,
|
||||
text: `${options.label}: `,
|
||||
fontStyle: fontMedium,
|
||||
fontFamily: 'Inter',
|
||||
fill: textMutedForeground,
|
||||
fontSize: textSm,
|
||||
});
|
||||
|
||||
group.add(label);
|
||||
|
||||
const value = new Konva.Text({
|
||||
x: label.width(),
|
||||
y: 0,
|
||||
width: width - label.width(),
|
||||
fontFamily: 'Inter',
|
||||
text: options.text,
|
||||
fill: textMutedForeground,
|
||||
wrap: 'char',
|
||||
fontSize: textSm,
|
||||
});
|
||||
|
||||
group.add(value);
|
||||
|
||||
return group;
|
||||
};
|
||||
|
||||
type RenderRowHeaderOptions = {
|
||||
columnWidths: number[];
|
||||
i18n: I18n;
|
||||
};
|
||||
|
||||
const renderRowHeader = (options: RenderRowHeaderOptions) => {
|
||||
const { columnWidths, i18n } = options;
|
||||
|
||||
const columnOneWidth = columnWidths[0];
|
||||
const columnTwoWidth = columnWidths[1];
|
||||
const columnThreeWidth = columnWidths[2];
|
||||
|
||||
const headerRow = new Konva.Group();
|
||||
|
||||
const headerFontStyling = {
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 11,
|
||||
fontStyle: fontMedium,
|
||||
verticalAlign: 'middle',
|
||||
fill: textMutedForeground,
|
||||
height: tableHeaderHeight,
|
||||
};
|
||||
|
||||
const header1 = new Konva.Text({
|
||||
x: rowPadding,
|
||||
width: columnOneWidth,
|
||||
text: i18n._(msg`Signer Events`),
|
||||
...headerFontStyling,
|
||||
});
|
||||
headerRow.add(header1);
|
||||
|
||||
const header2 = new Konva.Text({
|
||||
x: columnOneWidth + rowPadding,
|
||||
width: columnTwoWidth,
|
||||
text: i18n._(msg`Signature`),
|
||||
...headerFontStyling,
|
||||
});
|
||||
headerRow.add(header2);
|
||||
|
||||
const header3 = new Konva.Text({
|
||||
x: columnOneWidth + columnTwoWidth + rowPadding,
|
||||
width: columnThreeWidth,
|
||||
text: i18n._(msg`Details`),
|
||||
...headerFontStyling,
|
||||
});
|
||||
headerRow.add(header3);
|
||||
|
||||
return headerRow;
|
||||
};
|
||||
|
||||
const columnPadding = 10;
|
||||
|
||||
type RenderColumnOptions = {
|
||||
recipient: CertificateRecipient;
|
||||
width: number;
|
||||
i18n: I18n;
|
||||
envelopeOwner: {
|
||||
name: string;
|
||||
email: string;
|
||||
};
|
||||
};
|
||||
|
||||
const renderColumnOne = (options: RenderColumnOptions) => {
|
||||
const { recipient, width, i18n } = options;
|
||||
|
||||
const columnGroup = new Konva.Group();
|
||||
|
||||
const textSectionPadding = 8;
|
||||
|
||||
const textFontStyling = {
|
||||
x: 0,
|
||||
fontFamily: 'Inter',
|
||||
wrap: 'char',
|
||||
lineHeight: 1.2,
|
||||
fill: textMutedForeground,
|
||||
width: width - columnPadding,
|
||||
};
|
||||
|
||||
if (recipient.name) {
|
||||
const nameText = new Konva.Text({
|
||||
y: 0,
|
||||
text: recipient.name,
|
||||
fontSize: textBase,
|
||||
...textFontStyling,
|
||||
fontStyle: fontMedium,
|
||||
});
|
||||
|
||||
columnGroup.add(nameText);
|
||||
}
|
||||
|
||||
const emailText = new Konva.Text({
|
||||
y: columnGroup.getClientRect().height,
|
||||
text: recipient.email,
|
||||
fontSize: textBase,
|
||||
...textFontStyling,
|
||||
});
|
||||
|
||||
columnGroup.add(emailText);
|
||||
|
||||
const roleText = new Konva.Text({
|
||||
y: columnGroup.getClientRect().height + textSectionPadding,
|
||||
text: i18n._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName),
|
||||
fontSize: textSm,
|
||||
...textFontStyling,
|
||||
});
|
||||
columnGroup.add(roleText);
|
||||
|
||||
const authLabel = new Konva.Text({
|
||||
y: columnGroup.getClientRect().height + textSectionPadding,
|
||||
text: `${i18n._(msg`Authentication Level`)}:`,
|
||||
fontSize: textSm,
|
||||
fontStyle: fontMedium,
|
||||
...textFontStyling,
|
||||
});
|
||||
columnGroup.add(authLabel);
|
||||
|
||||
const authValue = new Konva.Text({
|
||||
y: columnGroup.getClientRect().height,
|
||||
text: recipient.authLevel,
|
||||
fontSize: textSm,
|
||||
...textFontStyling,
|
||||
});
|
||||
columnGroup.add(authValue);
|
||||
|
||||
return columnGroup;
|
||||
};
|
||||
|
||||
const renderColumnTwo = (options: RenderColumnOptions) => {
|
||||
const { recipient, width, i18n } = options;
|
||||
|
||||
// Column 2: Signature
|
||||
const column = new Konva.Group();
|
||||
|
||||
const columnWidth = width - columnPadding;
|
||||
|
||||
if (recipient.signatureField?.secondaryId) {
|
||||
// Signature container with green border
|
||||
const signatureContainer = new Konva.Group({ x: 0, y: 0 });
|
||||
|
||||
const minSignatureHeight = 40;
|
||||
const maxSignatureWidth = 100;
|
||||
|
||||
// Signature content
|
||||
if (recipient.signatureField?.signature?.signatureImageAsBase64) {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const img = new SkiaImage(
|
||||
recipient.signatureField?.signature?.signatureImageAsBase64,
|
||||
) as unknown as HTMLImageElement;
|
||||
|
||||
const signatureImage = new Konva.Image({
|
||||
image: img,
|
||||
x: 4,
|
||||
y: 4,
|
||||
width: maxSignatureWidth,
|
||||
height: maxSignatureWidth * (img.height / img.width),
|
||||
});
|
||||
|
||||
signatureContainer.add(signatureImage);
|
||||
} else if (recipient.signatureField?.signature?.typedSignature) {
|
||||
const typedSig = new Konva.Text({
|
||||
x: 2,
|
||||
text: recipient.signatureField?.signature?.typedSignature,
|
||||
padding: 4,
|
||||
fontFamily: 'Caveat',
|
||||
fontSize: 16,
|
||||
align: 'center',
|
||||
verticalAlign: 'middle',
|
||||
width: maxSignatureWidth,
|
||||
});
|
||||
|
||||
if (typedSig.getClientRect().height < minSignatureHeight) {
|
||||
typedSig.setAttrs({
|
||||
height: minSignatureHeight,
|
||||
});
|
||||
}
|
||||
|
||||
signatureContainer.add(typedSig);
|
||||
}
|
||||
|
||||
column.add(signatureContainer);
|
||||
|
||||
const signatureHeight = Math.max(signatureContainer.getClientRect().height, minSignatureHeight);
|
||||
|
||||
const signatureBorder = new Konva.Rect({
|
||||
x: 2,
|
||||
y: 2,
|
||||
width: maxSignatureWidth,
|
||||
height: signatureHeight,
|
||||
stroke: 'rgba(122, 196, 85, 0.6)',
|
||||
strokeWidth: 1,
|
||||
cornerRadius: 8,
|
||||
});
|
||||
signatureContainer.add(signatureBorder);
|
||||
|
||||
const signatureShadow = new Konva.Rect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: maxSignatureWidth + 4,
|
||||
height: signatureHeight + 4,
|
||||
stroke: 'rgba(122, 196, 85, 0.1)',
|
||||
strokeWidth: 4,
|
||||
cornerRadius: 8,
|
||||
});
|
||||
signatureContainer.add(signatureShadow);
|
||||
|
||||
// Signature ID
|
||||
const sigIdLabel = new Konva.Text({
|
||||
x: 0,
|
||||
y: signatureHeight + 10,
|
||||
text: `${i18n._(msg`Signature ID`)}:`,
|
||||
fill: textMutedForeground,
|
||||
width: columnWidth,
|
||||
fontFamily: 'Inter',
|
||||
fontSize: textSm,
|
||||
fontStyle: fontMedium,
|
||||
lineHeight: 1.4,
|
||||
});
|
||||
column.add(sigIdLabel);
|
||||
|
||||
const sigIdValue = new Konva.Text({
|
||||
x: 0,
|
||||
y: column.getClientRect().height,
|
||||
text: recipient.signatureField.secondaryId.toUpperCase(),
|
||||
fill: textMutedForeground,
|
||||
fontFamily: 'monospace',
|
||||
fontSize: textSm,
|
||||
width: columnWidth,
|
||||
wrap: 'char',
|
||||
});
|
||||
column.add(sigIdValue);
|
||||
} else {
|
||||
const naText = new Konva.Text({
|
||||
x: 0,
|
||||
y: 0,
|
||||
text: 'N/A',
|
||||
fill: textMutedForeground,
|
||||
fontFamily: 'Inter',
|
||||
fontSize: textSm,
|
||||
});
|
||||
column.add(naText);
|
||||
}
|
||||
|
||||
const ipLabelAndText = renderLabelAndText({
|
||||
label: i18n._(msg`IP Address`),
|
||||
text: recipient.logs.completed?.ipAddress ?? i18n._(msg`Unknown`),
|
||||
width,
|
||||
y: column.getClientRect().height + 6,
|
||||
});
|
||||
column.add(ipLabelAndText);
|
||||
|
||||
const deviceLabelAndText = renderLabelAndText({
|
||||
label: i18n._(msg`Device`),
|
||||
text: getDevice(recipient.logs.completed?.userAgent),
|
||||
width,
|
||||
y: column.getClientRect().height + 6,
|
||||
});
|
||||
column.add(deviceLabelAndText);
|
||||
|
||||
return column;
|
||||
};
|
||||
|
||||
const renderColumnThree = (options: RenderColumnOptions) => {
|
||||
const { recipient, width, i18n, envelopeOwner } = options;
|
||||
|
||||
const column = new Konva.Group();
|
||||
|
||||
const itemsToRender = [
|
||||
{
|
||||
label: i18n._(msg`Sent`),
|
||||
value: recipient.logs.emailed
|
||||
? DateTime.fromJSDate(recipient.logs.emailed.createdAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
|
||||
: recipient.logs.sent
|
||||
? DateTime.fromJSDate(recipient.logs.sent.createdAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
|
||||
: i18n._(msg`Unknown`),
|
||||
},
|
||||
{
|
||||
label: i18n._(msg`Viewed`),
|
||||
value: recipient.logs.opened
|
||||
? DateTime.fromJSDate(recipient.logs.opened.createdAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
|
||||
: i18n._(msg`Unknown`),
|
||||
},
|
||||
];
|
||||
|
||||
if (recipient.logs.rejected) {
|
||||
itemsToRender.push({
|
||||
label: i18n._(msg`Rejected`),
|
||||
value: DateTime.fromJSDate(recipient.logs.rejected.createdAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)'),
|
||||
});
|
||||
} else {
|
||||
itemsToRender.push({
|
||||
label: i18n._(msg`Signed`),
|
||||
value: recipient.logs.completed
|
||||
? DateTime.fromJSDate(recipient.logs.completed.createdAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
|
||||
: i18n._(msg`Unknown`),
|
||||
});
|
||||
}
|
||||
|
||||
const isOwner = recipient.email.toLowerCase() === envelopeOwner.email.toLowerCase();
|
||||
|
||||
itemsToRender.push({
|
||||
label: i18n._(msg`Reason`),
|
||||
value:
|
||||
recipient.signingStatus === SigningStatus.REJECTED
|
||||
? recipient.rejectionReason || ''
|
||||
: isOwner
|
||||
? i18n._(msg`I am the owner of this document`)
|
||||
: i18n._(RECIPIENT_ROLE_SIGNING_REASONS[recipient.role]),
|
||||
});
|
||||
|
||||
for (const [index, item] of itemsToRender.entries()) {
|
||||
const labelAndText = renderLabelAndText({
|
||||
label: item.label,
|
||||
text: item.value,
|
||||
width,
|
||||
y: column.getClientRect().height + (index === 0 ? 0 : 8),
|
||||
});
|
||||
column.add(labelAndText);
|
||||
}
|
||||
|
||||
return column;
|
||||
};
|
||||
|
||||
type RenderRowOptions = {
|
||||
recipient: CertificateRecipient;
|
||||
columnWidths: ColumnWidths;
|
||||
i18n: I18n;
|
||||
envelopeOwner: {
|
||||
name: string;
|
||||
email: string;
|
||||
};
|
||||
};
|
||||
|
||||
const renderRow = (options: RenderRowOptions) => {
|
||||
const { recipient, columnWidths, i18n, envelopeOwner } = options;
|
||||
|
||||
const rowGroup = new Konva.Group();
|
||||
|
||||
const width = columnWidths[0] + columnWidths[1] + columnWidths[2];
|
||||
|
||||
// Draw top border line.
|
||||
const borderLine = new Konva.Line({
|
||||
points: [0, 0, width + rowPadding * 2, 0],
|
||||
stroke: '#e5e7eb',
|
||||
strokeWidth: 1,
|
||||
});
|
||||
|
||||
rowGroup.add(borderLine);
|
||||
|
||||
// Column 1: Signer Events
|
||||
const columnGroup = renderColumnOne({
|
||||
recipient,
|
||||
width: columnWidths[0],
|
||||
i18n,
|
||||
envelopeOwner,
|
||||
});
|
||||
columnGroup.setAttrs({
|
||||
x: rowPadding,
|
||||
y: rowPadding,
|
||||
} satisfies Partial<Konva.GroupConfig>);
|
||||
rowGroup.add(columnGroup);
|
||||
|
||||
const columnTwoGroup = renderColumnTwo({
|
||||
recipient,
|
||||
width: columnWidths[1],
|
||||
i18n,
|
||||
envelopeOwner,
|
||||
});
|
||||
columnTwoGroup.setAttrs({
|
||||
x: rowPadding + columnWidths[0],
|
||||
y: rowPadding,
|
||||
} satisfies Partial<Konva.GroupConfig>);
|
||||
rowGroup.add(columnTwoGroup);
|
||||
|
||||
// Column 3: Details
|
||||
const columnThreeGroup = renderColumnThree({
|
||||
recipient,
|
||||
width: columnWidths[2],
|
||||
i18n,
|
||||
envelopeOwner,
|
||||
});
|
||||
columnThreeGroup.setAttrs({
|
||||
x: rowPadding + columnWidths[0] + columnWidths[1],
|
||||
y: rowPadding,
|
||||
} satisfies Partial<Konva.GroupConfig>);
|
||||
rowGroup.add(columnThreeGroup);
|
||||
|
||||
const rowBottomPadding = new Konva.Rect({
|
||||
x: 0,
|
||||
y: rowGroup.getClientRect().height,
|
||||
width: rowGroup.getClientRect().width,
|
||||
height: rowPadding,
|
||||
});
|
||||
rowGroup.add(rowBottomPadding);
|
||||
|
||||
return rowGroup;
|
||||
};
|
||||
|
||||
const renderBranding = async ({ qrToken, i18n }: { qrToken: string | null; i18n: I18n }) => {
|
||||
const branding = new Konva.Group();
|
||||
|
||||
const brandingHeight = 12;
|
||||
|
||||
const text = new Konva.Text({
|
||||
x: 0,
|
||||
verticalAlign: 'middle',
|
||||
text: i18n._(msg`Signing certificate provided by`) + ':',
|
||||
fontStyle: fontMedium,
|
||||
fontFamily: 'Inter',
|
||||
fontSize: textSm,
|
||||
height: brandingHeight,
|
||||
});
|
||||
|
||||
const logoPath = path.join(process.cwd(), 'public/static/logo.png');
|
||||
const logo = fs.readFileSync(logoPath);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const img = new SkiaImage(logo) as unknown as HTMLImageElement;
|
||||
|
||||
const documensoImage = new Konva.Image({
|
||||
image: img,
|
||||
height: brandingHeight,
|
||||
width: brandingHeight * (img.width / img.height),
|
||||
x: text.width() + 16,
|
||||
});
|
||||
|
||||
const qrSize = qrToken ? 72 : 0;
|
||||
|
||||
const logoGroup = new Konva.Group({
|
||||
y: qrSize + 16,
|
||||
});
|
||||
logoGroup.add(text);
|
||||
logoGroup.add(documensoImage);
|
||||
|
||||
branding.add(logoGroup);
|
||||
|
||||
if (qrToken) {
|
||||
const qrSvg = renderSVG(`${NEXT_PUBLIC_WEBAPP_URL()}/share/${qrToken}`, {
|
||||
ecc: 'Q',
|
||||
});
|
||||
|
||||
const svgImage = await svgToPng(qrSvg);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const qrSkiaImage = new SkiaImage(svgImage) as unknown as HTMLImageElement;
|
||||
const qrImage = new Konva.Image({
|
||||
image: qrSkiaImage,
|
||||
height: qrSize,
|
||||
width: qrSize,
|
||||
x: branding.getClientRect().width - qrSize,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
branding.add(qrImage);
|
||||
}
|
||||
|
||||
return branding;
|
||||
};
|
||||
|
||||
type GroupRowsIntoPagesOptions = {
|
||||
recipients: CertificateRecipient[];
|
||||
maxHeight: number;
|
||||
i18n: I18n;
|
||||
columnWidths: ColumnWidths;
|
||||
envelopeOwner: {
|
||||
name: string;
|
||||
email: string;
|
||||
};
|
||||
};
|
||||
|
||||
const groupRowsIntoPages = (options: GroupRowsIntoPagesOptions) => {
|
||||
const { recipients, maxHeight, i18n, columnWidths, envelopeOwner } = options;
|
||||
|
||||
const rowHeader = renderRowHeader({ columnWidths, i18n });
|
||||
const rowHeaderHeight = rowHeader.getClientRect().height;
|
||||
|
||||
const groupedRows: Konva.Group[][] = [[]];
|
||||
|
||||
let availablePageHeight = maxHeight - rowHeaderHeight;
|
||||
let currentGroupedRowIndex = 0;
|
||||
|
||||
// Group rows into pages.
|
||||
for (const recipient of recipients) {
|
||||
const row = renderRow({ recipient, columnWidths, i18n, envelopeOwner });
|
||||
|
||||
const rowHeight = row.getClientRect().height;
|
||||
|
||||
if (rowHeight > availablePageHeight) {
|
||||
currentGroupedRowIndex++;
|
||||
groupedRows[currentGroupedRowIndex] = [row];
|
||||
availablePageHeight = maxHeight - rowHeaderHeight;
|
||||
} else {
|
||||
groupedRows[currentGroupedRowIndex].push(row);
|
||||
}
|
||||
|
||||
// Reduce available height by the row height.
|
||||
availablePageHeight -= rowHeight;
|
||||
}
|
||||
|
||||
return groupedRows;
|
||||
};
|
||||
|
||||
type RenderTablesOptions = {
|
||||
groupedRows: Konva.Group[][];
|
||||
columnWidths: ColumnWidths;
|
||||
i18n: I18n;
|
||||
};
|
||||
|
||||
const renderTables = (options: RenderTablesOptions) => {
|
||||
const { groupedRows, columnWidths, i18n } = options;
|
||||
|
||||
const tables: Konva.Group[] = [];
|
||||
|
||||
// Render the rows for each page.
|
||||
for (const rows of groupedRows) {
|
||||
const table = new Konva.Group();
|
||||
const tableHeader = renderRowHeader({ columnWidths, i18n });
|
||||
|
||||
table.add(tableHeader);
|
||||
|
||||
for (const row of rows) {
|
||||
row.setAttrs({
|
||||
x: 0,
|
||||
y: table.getClientRect().height,
|
||||
} satisfies Partial<Konva.GroupConfig>);
|
||||
|
||||
table.add(row);
|
||||
}
|
||||
|
||||
// Add table background and border.
|
||||
const tableClientRect = table.getClientRect();
|
||||
const cardRect = new Konva.Rect({
|
||||
x: tableClientRect.x,
|
||||
y: tableClientRect.y,
|
||||
width: tableClientRect.width,
|
||||
height: tableClientRect.height,
|
||||
stroke: '#e5e7eb',
|
||||
strokeWidth: 1.5,
|
||||
cornerRadius: 8,
|
||||
});
|
||||
table.add(cardRect);
|
||||
|
||||
tables.push(table);
|
||||
}
|
||||
|
||||
return tables;
|
||||
};
|
||||
|
||||
export async function renderCertificate({
|
||||
recipients,
|
||||
qrToken,
|
||||
hidePoweredBy,
|
||||
i18n,
|
||||
envelopeOwner,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
}: GenerateCertificateOptions) {
|
||||
const fontPath = path.join(process.cwd(), 'public/fonts');
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
FontLibrary.use({
|
||||
['Caveat']: [path.join(fontPath, 'caveat.ttf')],
|
||||
['Inter']: [path.join(fontPath, 'inter-variablefont_opsz,wght.ttf')],
|
||||
});
|
||||
|
||||
const minimumMargin = 10;
|
||||
|
||||
const tableWidth = Math.min(pageWidth - minimumMargin * 2, contentMaxWidth);
|
||||
const tableContentWidth = tableWidth - rowPadding * 2;
|
||||
const margin = (pageWidth - tableWidth) / 2;
|
||||
|
||||
const columnOneWidth = (tableContentWidth * columnWidthPercentages[0]) / 100;
|
||||
const columnTwoWidth = (tableContentWidth * columnWidthPercentages[1]) / 100;
|
||||
const columnThreeWidth = (tableContentWidth * columnWidthPercentages[2]) / 100;
|
||||
|
||||
const columnWidths: ColumnWidths = [columnOneWidth, columnTwoWidth, columnThreeWidth];
|
||||
|
||||
// Helper to render a Konva stage to a PNG buffer
|
||||
let stage: Konva.Stage | null = new Konva.Stage({ width: pageWidth, height: pageHeight });
|
||||
|
||||
const maxTableHeight = pageHeight - pageTopMargin - pageBottomMargin;
|
||||
|
||||
const groupedRows = groupRowsIntoPages({
|
||||
recipients,
|
||||
maxHeight: maxTableHeight,
|
||||
columnWidths,
|
||||
i18n,
|
||||
envelopeOwner,
|
||||
});
|
||||
|
||||
const tables = renderTables({ groupedRows, columnWidths, i18n });
|
||||
|
||||
const brandingGroup = await renderBranding({ qrToken, i18n });
|
||||
const brandingRect = brandingGroup.getClientRect();
|
||||
const brandingTopPadding = 24;
|
||||
|
||||
const pages: Uint8Array[] = [];
|
||||
|
||||
let isQrPlaced = false;
|
||||
|
||||
// Add a table to each page.
|
||||
for (const [index, table] of tables.entries()) {
|
||||
stage.destroyChildren();
|
||||
const page = new Konva.Layer();
|
||||
|
||||
const group = new Konva.Group();
|
||||
|
||||
const titleText = new Konva.Text({
|
||||
x: margin,
|
||||
y: 0,
|
||||
height: pageTopMargin,
|
||||
verticalAlign: 'middle',
|
||||
text: i18n._(msg`Signing Certificate`),
|
||||
fontFamily: 'Inter',
|
||||
fontSize: titleFontSize,
|
||||
fontStyle: '700',
|
||||
});
|
||||
|
||||
table.setAttrs({
|
||||
x: margin,
|
||||
y: pageTopMargin,
|
||||
} satisfies Partial<Konva.GroupConfig>);
|
||||
|
||||
group.add(titleText);
|
||||
group.add(table);
|
||||
|
||||
// Add QR code and branding on the last page if there is space.
|
||||
if (index === tables.length - 1 && !hidePoweredBy) {
|
||||
const remainingHeight = pageHeight - group.getClientRect().height - pageBottomMargin;
|
||||
|
||||
if (brandingRect.height + brandingTopPadding <= remainingHeight) {
|
||||
brandingGroup.setAttrs({
|
||||
x: pageWidth - brandingRect.width - margin,
|
||||
y: group.getClientRect().height + brandingTopPadding,
|
||||
} satisfies Partial<Konva.GroupConfig>);
|
||||
|
||||
page.add(brandingGroup);
|
||||
isQrPlaced = true;
|
||||
}
|
||||
}
|
||||
|
||||
page.add(group);
|
||||
stage.add(page);
|
||||
|
||||
// Export the page and save it.
|
||||
const canvas = page.canvas._canvas as unknown as Canvas; // eslint-disable-line @typescript-eslint/consistent-type-assertions
|
||||
const buffer = await canvas.toBuffer('pdf');
|
||||
pages.push(new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
// Need to create an empty page for the QR code if it hasn't been placed yet.
|
||||
if (!hidePoweredBy && !isQrPlaced) {
|
||||
const page = new Konva.Layer();
|
||||
|
||||
brandingGroup.setAttrs({
|
||||
x: pageWidth - brandingRect.width - margin,
|
||||
y: pageTopMargin / 2, // Less padding since there's nothing else on this page.
|
||||
} satisfies Partial<Konva.GroupConfig>);
|
||||
|
||||
page.add(brandingGroup);
|
||||
stage.add(page);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const canvas = page.canvas._canvas as unknown as Canvas;
|
||||
const buffer = await canvas.toBuffer('pdf');
|
||||
|
||||
pages.push(new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
stage.destroy();
|
||||
stage = null;
|
||||
|
||||
return pages;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-12-15 02:39\n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@@ -244,6 +244,7 @@ msgid "{0} Teams"
|
||||
msgstr "{0} Teams"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{browserInfo} on {os}"
|
||||
msgstr "{browserInfo} auf {os}"
|
||||
|
||||
@@ -475,6 +476,7 @@ msgid "{teamName} has invited you to {action} {documentName}"
|
||||
msgstr "{teamName} hat Sie eingeladen, {action} {documentName}"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{userAgent}"
|
||||
msgstr "{userAgent}"
|
||||
|
||||
@@ -955,6 +957,7 @@ msgid "Account"
|
||||
msgstr "Konto"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Authentication"
|
||||
msgstr "Kontowauthentifizierung"
|
||||
|
||||
@@ -992,6 +995,7 @@ msgid "Account Linking Request"
|
||||
msgstr "Kontoverknüpfungsanfrage"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Re-Authentication"
|
||||
msgstr "Kontowiederauthentifizierung"
|
||||
|
||||
@@ -1939,6 +1943,7 @@ msgid "Attempts sealing the document again, useful for after a code change has o
|
||||
msgstr "Versuche, das Dokument erneut zu versiegeln, nützlich nach einer Codeänderung, um ein fehlerhaftes Dokument zu beheben."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Audit Log"
|
||||
msgstr "Audit-Protokoll"
|
||||
|
||||
@@ -1947,6 +1952,7 @@ msgid "Audit Logs"
|
||||
msgstr "Protokolle"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Authentication Level"
|
||||
msgstr "Authentifizierungsstufe"
|
||||
|
||||
@@ -2967,6 +2973,7 @@ msgstr "Erstellt"
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Created At"
|
||||
msgstr "Erstellt am"
|
||||
|
||||
@@ -3109,6 +3116,10 @@ msgstr "Standard-Zeitzone"
|
||||
msgid "Default Value"
|
||||
msgstr "Standardwert"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Delegate Document Ownership"
|
||||
msgstr "Dokumenteigentum delegieren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx
|
||||
msgid "delete"
|
||||
msgstr "löschen"
|
||||
@@ -3255,6 +3266,7 @@ msgstr "Löschen Sie Ihr Konto und alle Inhalte, einschließlich abgeschlossener
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Deleted"
|
||||
msgstr "Gelöscht"
|
||||
|
||||
@@ -3268,6 +3280,7 @@ msgstr "Ziel"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Details"
|
||||
msgstr "Einzelheiten"
|
||||
|
||||
@@ -3324,6 +3337,7 @@ msgstr "Entwicklermodus"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Device"
|
||||
msgstr "Gerät"
|
||||
|
||||
@@ -3672,6 +3686,11 @@ msgctxt "Audit log format"
|
||||
msgid "Document opened"
|
||||
msgstr "Dokument geöffnet"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgctxt "Audit log format"
|
||||
msgid "Document ownership delegated"
|
||||
msgstr "Dokumenteigentum delegiert"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
msgid "Document pending"
|
||||
msgstr "Dokument ausstehend"
|
||||
@@ -4013,6 +4032,7 @@ msgstr "Z. B. 100"
|
||||
#: apps/remix/app/components/general/document/document-page-view-button.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-dropdown.tsx
|
||||
#: apps/remix/app/components/general/teams/team-email-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-button.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
|
||||
@@ -4080,6 +4100,8 @@ msgstr "Offenlegung der elektronischen Unterschrift"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
@@ -4110,10 +4132,6 @@ msgstr "E-Mail bereits bestätigt"
|
||||
msgid "Email already exists"
|
||||
msgstr "E-Mail existiert bereits"
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
|
||||
msgid "Email cannot already exist in the template"
|
||||
msgstr "E-Mail darf nicht bereits in der Vorlage vorhanden sein"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Email Confirmed!"
|
||||
msgstr "E-Mail bestätigt!"
|
||||
@@ -4278,6 +4296,10 @@ msgstr "Aktiviere die Signaturreihenfolge"
|
||||
msgid "Enable SSO portal"
|
||||
msgstr "SSO-Portal aktivieren"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Enable team API tokens to delegate document ownership to another team member."
|
||||
msgstr "Team-API-Tokens aktivieren, um das Dokumenteigentum an ein anderes Teammitglied zu delegieren."
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
@@ -4295,6 +4317,10 @@ msgstr "Das Aktivieren des Kontos führt dazu, dass der Benutzer das Konto wiede
|
||||
msgid "Enclosed Document"
|
||||
msgstr "Beigefügte Dokument"
|
||||
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Beigefügte Dokumente"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Geben Sie einen Namen für Ihren neuen Ordner ein. Ordner helfen Ihnen, Ihre Dateien zu organisieren."
|
||||
@@ -4370,6 +4396,7 @@ msgid "Envelope Duplicated"
|
||||
msgstr "Umschlag dupliziert"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Envelope ID"
|
||||
msgstr "Umschlag-ID"
|
||||
|
||||
@@ -5067,6 +5094,7 @@ msgid "I am required to receive a copy of this document"
|
||||
msgstr "Ich bin verpflichtet, eine Kopie dieses Dokuments zu erhalten"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "I am the owner of this document"
|
||||
msgstr "Ich bin der Besitzer dieses Dokuments"
|
||||
|
||||
@@ -5162,6 +5190,7 @@ msgstr "Authentifizierungsmethode erben"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
msgid "Inherit from organisation"
|
||||
@@ -5293,6 +5322,8 @@ msgstr "Rechnung"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "IP Address"
|
||||
msgstr "IP-Adresse"
|
||||
|
||||
@@ -5405,6 +5436,7 @@ msgid "Last updated"
|
||||
msgstr "Zuletzt aktualisiert"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Last Updated"
|
||||
msgstr "Zuletzt aktualisiert"
|
||||
|
||||
@@ -5554,6 +5586,7 @@ msgid "Looking for signature fields"
|
||||
msgstr "Signaturfelder werden gesucht"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-groups-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-teams-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
msgid "Manage"
|
||||
@@ -5614,6 +5647,10 @@ msgstr "Verknüpfte Konten verwalten"
|
||||
msgid "Manage organisation"
|
||||
msgstr "Organisation verwalten"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Manage Organisation"
|
||||
msgstr "Organisation verwalten"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx
|
||||
msgid "Manage organisations"
|
||||
msgstr "Organisationen verwalten"
|
||||
@@ -5861,6 +5898,7 @@ msgid "My Folder"
|
||||
msgstr "Mein Ordner"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
@@ -5960,6 +5998,7 @@ msgstr "Nächster Empfängername"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "No"
|
||||
msgstr "Nein"
|
||||
|
||||
@@ -6386,6 +6425,7 @@ msgstr "Organisationseinstellungen überschreiben"
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Owner"
|
||||
msgstr "Besitzer"
|
||||
|
||||
@@ -6436,6 +6476,7 @@ msgid "Passkey name"
|
||||
msgstr "Passkey-Name"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Passkey Re-Authentication"
|
||||
msgstr "Passwortwiederauthentifizierung"
|
||||
|
||||
@@ -6462,6 +6503,7 @@ msgid "Password"
|
||||
msgstr "Passwort"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Password Re-Authentication"
|
||||
msgstr "Passwort-Neuauthentifizierung"
|
||||
|
||||
@@ -6932,6 +6974,7 @@ msgid "Ready"
|
||||
msgstr "Bereit"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Reason"
|
||||
msgstr "Grund"
|
||||
|
||||
@@ -7018,6 +7061,7 @@ msgstr "Empfänger aktualisiert"
|
||||
#: apps/remix/app/components/general/template/template-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Recipients"
|
||||
msgstr "Empfänger"
|
||||
|
||||
@@ -7095,6 +7139,7 @@ msgstr "Dokument Ablehnen"
|
||||
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Rejected"
|
||||
msgstr "Abgelehnt"
|
||||
|
||||
@@ -7332,6 +7377,11 @@ msgstr "Wiederholen"
|
||||
msgid "Return"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx
|
||||
msgid "Return Home"
|
||||
msgstr "Zur Startseite zurückkehren"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/o.$orgUrl.signin.tsx
|
||||
msgid "Return to Documenso sign in page here"
|
||||
msgstr "Hier zur Documenso-Anmeldeseite zurückkehren"
|
||||
@@ -7761,6 +7811,7 @@ msgstr "Senden..."
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Sent"
|
||||
msgstr "Gesendet"
|
||||
|
||||
@@ -7991,6 +8042,7 @@ msgstr "Initialen ins Feld signieren"
|
||||
#: apps/remix/app/components/general/envelope-signing/envelope-signer-form.tsx
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/types.ts
|
||||
@@ -7999,6 +8051,7 @@ msgid "Signature"
|
||||
msgstr "Unterschrift"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signature ID"
|
||||
msgstr "Signatur-ID"
|
||||
|
||||
@@ -8024,6 +8077,7 @@ msgstr "Gesammelte Unterschriften"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/ui/components/document/document-read-only-fields.tsx
|
||||
#: packages/ui/components/document/envelope-recipient-field-tooltip.tsx
|
||||
msgid "Signed"
|
||||
@@ -8049,6 +8103,7 @@ msgid "Signer"
|
||||
msgstr "Unterzeichner"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signer Events"
|
||||
msgstr "Signer-Ereignisse"
|
||||
|
||||
@@ -8063,10 +8118,12 @@ msgid "Signing"
|
||||
msgstr "Unterzeichnung"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing Certificate"
|
||||
msgstr "Unterzeichnungszertifikat"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing certificate provided by"
|
||||
msgstr "Unterzeichnungszertifikat bereitgestellt von"
|
||||
|
||||
@@ -8274,6 +8331,7 @@ msgstr "Statistiken"
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
@@ -8835,6 +8893,12 @@ msgstr "Der Dokumenteninhaber wurde über diese Ablehnung informiert. Es sind de
|
||||
msgid "The document owner has been notified of your decision. They may contact you with further instructions if necessary."
|
||||
msgstr "Der Dokumenteneigentümer wurde über Ihre Entscheidung informiert. Er kann Sie bei Bedarf mit weiteren Anweisungen kontaktieren."
|
||||
|
||||
#. placeholder {0}: data.delegatedOwnerName || data.delegatedOwnerEmail
|
||||
#. placeholder {1}: data.teamName
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "The document ownership was delegated to {0} on behalf of {1}"
|
||||
msgstr "Das Dokumenteigentum wurde im Namen von {1} an {0} delegiert"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "The document was created but could not be sent to recipients."
|
||||
msgstr "Das Dokument wurde erstellt, konnte aber nicht an die Empfänger versendet werden."
|
||||
@@ -9410,6 +9474,7 @@ msgstr "Zeitzone"
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Time Zone"
|
||||
@@ -9601,6 +9666,7 @@ msgstr "Wiederherstellungscodes für die Zwei-Faktor-Authentifizierung werden ve
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Authentication"
|
||||
msgstr "Zwei-Faktor-Authentifizierung"
|
||||
|
||||
@@ -9617,6 +9683,7 @@ msgid "Two-factor authentication has been disabled for your account. You will no
|
||||
msgstr "Die Zwei-Faktor-Authentifizierung wurde für Ihr Konto deaktiviert. Sie müssen beim Anmelden keinen Code aus Ihrer Authentifizierungs-App mehr eingeben."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Re-Authentication"
|
||||
msgstr "Zwei-Faktor-Wiederauthentifizierung"
|
||||
|
||||
@@ -9750,6 +9817,10 @@ msgstr "Rückgängig"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Unknown"
|
||||
msgstr "Unbekannt"
|
||||
|
||||
@@ -10052,10 +10123,12 @@ msgstr "Verwenden Sie Ihren Passkey zur Authentifizierung"
|
||||
|
||||
#: apps/remix/app/components/tables/document-logs-table.tsx
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User"
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User Agent"
|
||||
msgstr "Benutzer-Agent"
|
||||
|
||||
@@ -10299,6 +10372,7 @@ msgstr "DNS-Datensätze für diese E-Mail-Domain anzeigen"
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-list.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Viewed"
|
||||
msgstr "Betrachtet"
|
||||
|
||||
@@ -10842,6 +10916,7 @@ msgstr "Jährlich"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Yes"
|
||||
msgstr "Ja"
|
||||
|
||||
|
||||
@@ -239,6 +239,7 @@ msgid "{0} Teams"
|
||||
msgstr "{0} Teams"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{browserInfo} on {os}"
|
||||
msgstr "{browserInfo} on {os}"
|
||||
|
||||
@@ -470,6 +471,7 @@ msgid "{teamName} has invited you to {action} {documentName}"
|
||||
msgstr "{teamName} has invited you to {action} {documentName}"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{userAgent}"
|
||||
msgstr "{userAgent}"
|
||||
|
||||
@@ -950,6 +952,7 @@ msgid "Account"
|
||||
msgstr "Account"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Authentication"
|
||||
msgstr "Account Authentication"
|
||||
|
||||
@@ -987,6 +990,7 @@ msgid "Account Linking Request"
|
||||
msgstr "Account Linking Request"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Re-Authentication"
|
||||
msgstr "Account Re-Authentication"
|
||||
|
||||
@@ -1934,6 +1938,7 @@ msgid "Attempts sealing the document again, useful for after a code change has o
|
||||
msgstr "Attempts sealing the document again, useful for after a code change has occurred to resolve an erroneous document."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Audit Log"
|
||||
msgstr "Audit Log"
|
||||
|
||||
@@ -1942,6 +1947,7 @@ msgid "Audit Logs"
|
||||
msgstr "Audit Logs"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Authentication Level"
|
||||
msgstr "Authentication Level"
|
||||
|
||||
@@ -2962,6 +2968,7 @@ msgstr "Created"
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Created At"
|
||||
msgstr "Created At"
|
||||
|
||||
@@ -3104,6 +3111,10 @@ msgstr "Default Time Zone"
|
||||
msgid "Default Value"
|
||||
msgstr "Default Value"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Delegate Document Ownership"
|
||||
msgstr "Delegate Document Ownership"
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx
|
||||
msgid "delete"
|
||||
msgstr "delete"
|
||||
@@ -3250,6 +3261,7 @@ msgstr "Delete your account and all its contents, including completed documents.
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Deleted"
|
||||
msgstr "Deleted"
|
||||
|
||||
@@ -3263,6 +3275,7 @@ msgstr "Destination"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Details"
|
||||
msgstr "Details"
|
||||
|
||||
@@ -3319,6 +3332,7 @@ msgstr "Developer Mode"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Device"
|
||||
msgstr "Device"
|
||||
|
||||
@@ -3667,6 +3681,11 @@ msgctxt "Audit log format"
|
||||
msgid "Document opened"
|
||||
msgstr "Document opened"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgctxt "Audit log format"
|
||||
msgid "Document ownership delegated"
|
||||
msgstr "Document ownership delegated"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
msgid "Document pending"
|
||||
msgstr "Document pending"
|
||||
@@ -4008,6 +4027,7 @@ msgstr "E.g. 100"
|
||||
#: apps/remix/app/components/general/document/document-page-view-button.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-dropdown.tsx
|
||||
#: apps/remix/app/components/general/teams/team-email-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-button.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
|
||||
@@ -4075,6 +4095,8 @@ msgstr "Electronic Signature Disclosure"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
@@ -4105,10 +4127,6 @@ msgstr "Email already confirmed"
|
||||
msgid "Email already exists"
|
||||
msgstr "Email already exists"
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
|
||||
msgid "Email cannot already exist in the template"
|
||||
msgstr "Email cannot already exist in the template"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Email Confirmed!"
|
||||
msgstr "Email Confirmed!"
|
||||
@@ -4273,6 +4291,10 @@ msgstr "Enable signing order"
|
||||
msgid "Enable SSO portal"
|
||||
msgstr "Enable SSO portal"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Enable team API tokens to delegate document ownership to another team member."
|
||||
msgstr "Enable team API tokens to delegate document ownership to another team member."
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
@@ -4290,6 +4312,10 @@ msgstr "Enabling the account results in the user being able to use the account a
|
||||
msgid "Enclosed Document"
|
||||
msgstr "Enclosed Document"
|
||||
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Enclosed Documents"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Enter a name for your new folder. Folders help you organise your items."
|
||||
@@ -4365,6 +4391,7 @@ msgid "Envelope Duplicated"
|
||||
msgstr "Envelope Duplicated"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Envelope ID"
|
||||
msgstr "Envelope ID"
|
||||
|
||||
@@ -5062,6 +5089,7 @@ msgid "I am required to receive a copy of this document"
|
||||
msgstr "I am required to receive a copy of this document"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "I am the owner of this document"
|
||||
msgstr "I am the owner of this document"
|
||||
|
||||
@@ -5157,6 +5185,7 @@ msgstr "Inherit authentication method"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
msgid "Inherit from organisation"
|
||||
@@ -5288,6 +5317,8 @@ msgstr "Invoice"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "IP Address"
|
||||
msgstr "IP Address"
|
||||
|
||||
@@ -5400,6 +5431,7 @@ msgid "Last updated"
|
||||
msgstr "Last updated"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Last Updated"
|
||||
msgstr "Last Updated"
|
||||
|
||||
@@ -5549,6 +5581,7 @@ msgid "Looking for signature fields"
|
||||
msgstr "Looking for signature fields"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-groups-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-teams-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
msgid "Manage"
|
||||
@@ -5609,6 +5642,10 @@ msgstr "Manage linked accounts"
|
||||
msgid "Manage organisation"
|
||||
msgstr "Manage organisation"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Manage Organisation"
|
||||
msgstr "Manage Organisation"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx
|
||||
msgid "Manage organisations"
|
||||
msgstr "Manage organisations"
|
||||
@@ -5856,6 +5893,7 @@ msgid "My Folder"
|
||||
msgstr "My Folder"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
@@ -5955,6 +5993,7 @@ msgstr "Next Recipient Name"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
@@ -6381,6 +6420,7 @@ msgstr "Override organisation settings"
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Owner"
|
||||
msgstr "Owner"
|
||||
|
||||
@@ -6431,6 +6471,7 @@ msgid "Passkey name"
|
||||
msgstr "Passkey name"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Passkey Re-Authentication"
|
||||
msgstr "Passkey Re-Authentication"
|
||||
|
||||
@@ -6457,6 +6498,7 @@ msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Password Re-Authentication"
|
||||
msgstr "Password Re-Authentication"
|
||||
|
||||
@@ -6927,6 +6969,7 @@ msgid "Ready"
|
||||
msgstr "Ready"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Reason"
|
||||
msgstr "Reason"
|
||||
|
||||
@@ -7013,6 +7056,7 @@ msgstr "Recipient updated"
|
||||
#: apps/remix/app/components/general/template/template-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Recipients"
|
||||
msgstr "Recipients"
|
||||
|
||||
@@ -7090,6 +7134,7 @@ msgstr "Reject Document"
|
||||
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Rejected"
|
||||
msgstr "Rejected"
|
||||
|
||||
@@ -7327,6 +7372,11 @@ msgstr "Retry"
|
||||
msgid "Return"
|
||||
msgstr "Return"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx
|
||||
msgid "Return Home"
|
||||
msgstr "Return Home"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/o.$orgUrl.signin.tsx
|
||||
msgid "Return to Documenso sign in page here"
|
||||
msgstr "Return to Documenso sign in page here"
|
||||
@@ -7756,6 +7806,7 @@ msgstr "Sending..."
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Sent"
|
||||
msgstr "Sent"
|
||||
|
||||
@@ -7986,6 +8037,7 @@ msgstr "Sign your initials into the field"
|
||||
#: apps/remix/app/components/general/envelope-signing/envelope-signer-form.tsx
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/types.ts
|
||||
@@ -7994,6 +8046,7 @@ msgid "Signature"
|
||||
msgstr "Signature"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signature ID"
|
||||
msgstr "Signature ID"
|
||||
|
||||
@@ -8019,6 +8072,7 @@ msgstr "Signatures Collected"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/ui/components/document/document-read-only-fields.tsx
|
||||
#: packages/ui/components/document/envelope-recipient-field-tooltip.tsx
|
||||
msgid "Signed"
|
||||
@@ -8044,6 +8098,7 @@ msgid "Signer"
|
||||
msgstr "Signer"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signer Events"
|
||||
msgstr "Signer Events"
|
||||
|
||||
@@ -8058,10 +8113,12 @@ msgid "Signing"
|
||||
msgstr "Signing"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing Certificate"
|
||||
msgstr "Signing Certificate"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing certificate provided by"
|
||||
msgstr "Signing certificate provided by"
|
||||
|
||||
@@ -8269,6 +8326,7 @@ msgstr "Stats"
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
@@ -8830,6 +8888,12 @@ msgstr "The document owner has been notified of this rejection. No further actio
|
||||
msgid "The document owner has been notified of your decision. They may contact you with further instructions if necessary."
|
||||
msgstr "The document owner has been notified of your decision. They may contact you with further instructions if necessary."
|
||||
|
||||
#. placeholder {0}: data.delegatedOwnerName || data.delegatedOwnerEmail
|
||||
#. placeholder {1}: data.teamName
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "The document ownership was delegated to {0} on behalf of {1}"
|
||||
msgstr "The document ownership was delegated to {0} on behalf of {1}"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "The document was created but could not be sent to recipients."
|
||||
msgstr "The document was created but could not be sent to recipients."
|
||||
@@ -9405,6 +9469,7 @@ msgstr "Time zone"
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Time Zone"
|
||||
@@ -9596,6 +9661,7 @@ msgstr "Two factor authentication recovery codes are used to access your account
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Authentication"
|
||||
msgstr "Two-Factor Authentication"
|
||||
|
||||
@@ -9612,6 +9678,7 @@ msgid "Two-factor authentication has been disabled for your account. You will no
|
||||
msgstr "Two-factor authentication has been disabled for your account. You will no longer be required to enter a code from your authenticator app when signing in."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Re-Authentication"
|
||||
msgstr "Two-Factor Re-Authentication"
|
||||
|
||||
@@ -9745,6 +9812,10 @@ msgstr "Undo"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Unknown"
|
||||
msgstr "Unknown"
|
||||
|
||||
@@ -10047,10 +10118,12 @@ msgstr "Use your passkey for authentication"
|
||||
|
||||
#: apps/remix/app/components/tables/document-logs-table.tsx
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User"
|
||||
msgstr "User"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User Agent"
|
||||
msgstr "User Agent"
|
||||
|
||||
@@ -10294,6 +10367,7 @@ msgstr "View the DNS records for this email domain"
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-list.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Viewed"
|
||||
msgstr "Viewed"
|
||||
|
||||
@@ -10837,6 +10911,7 @@ msgstr "Yearly"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Yes"
|
||||
msgstr "Yes"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: es\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-12-15 02:39\n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@@ -244,6 +244,7 @@ msgid "{0} Teams"
|
||||
msgstr "{0} Equipos"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{browserInfo} on {os}"
|
||||
msgstr "{browserInfo} en {os}"
|
||||
|
||||
@@ -475,6 +476,7 @@ msgid "{teamName} has invited you to {action} {documentName}"
|
||||
msgstr "{teamName} te ha invitado a {action} {documentName}"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{userAgent}"
|
||||
msgstr "{userAgent}"
|
||||
|
||||
@@ -955,6 +957,7 @@ msgid "Account"
|
||||
msgstr "Cuenta"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Authentication"
|
||||
msgstr "Autenticación de Cuenta"
|
||||
|
||||
@@ -992,6 +995,7 @@ msgid "Account Linking Request"
|
||||
msgstr "Solicitud de Vinculación de Cuenta"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Re-Authentication"
|
||||
msgstr "Re-autenticación de Cuenta"
|
||||
|
||||
@@ -1939,6 +1943,7 @@ msgid "Attempts sealing the document again, useful for after a code change has o
|
||||
msgstr "Intenta sellar el documento de nuevo, útil después de que se haya producido un cambio de código para resolver un documento erróneo."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Audit Log"
|
||||
msgstr "Registro de Auditoría"
|
||||
|
||||
@@ -1947,6 +1952,7 @@ msgid "Audit Logs"
|
||||
msgstr "Registros de Auditoría"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Authentication Level"
|
||||
msgstr "Nivel de Autenticación"
|
||||
|
||||
@@ -2967,6 +2973,7 @@ msgstr "Creado"
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Created At"
|
||||
msgstr "Creado En"
|
||||
|
||||
@@ -3109,6 +3116,10 @@ msgstr "Zona horaria predeterminada"
|
||||
msgid "Default Value"
|
||||
msgstr "Valor Predeterminado"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Delegate Document Ownership"
|
||||
msgstr "Delegar la propiedad del documento"
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx
|
||||
msgid "delete"
|
||||
msgstr "eliminar"
|
||||
@@ -3255,6 +3266,7 @@ msgstr "Eliminar su cuenta y todo su contenido, incluidos documentos completados
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Deleted"
|
||||
msgstr "Eliminado"
|
||||
|
||||
@@ -3268,6 +3280,7 @@ msgstr "Destino"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Details"
|
||||
msgstr "Detalles"
|
||||
|
||||
@@ -3324,6 +3337,7 @@ msgstr "Modo desarrollador"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Device"
|
||||
msgstr "Dispositivo"
|
||||
|
||||
@@ -3672,6 +3686,11 @@ msgctxt "Audit log format"
|
||||
msgid "Document opened"
|
||||
msgstr "Documento abierto"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgctxt "Audit log format"
|
||||
msgid "Document ownership delegated"
|
||||
msgstr "Propiedad del documento delegada"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
msgid "Document pending"
|
||||
msgstr "Documento pendiente"
|
||||
@@ -4013,6 +4032,7 @@ msgstr "Ej.: 100"
|
||||
#: apps/remix/app/components/general/document/document-page-view-button.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-dropdown.tsx
|
||||
#: apps/remix/app/components/general/teams/team-email-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-button.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
|
||||
@@ -4080,6 +4100,8 @@ msgstr "Divulgación de Firma Electrónica"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
@@ -4110,10 +4132,6 @@ msgstr "Correo electrónico ya confirmado"
|
||||
msgid "Email already exists"
|
||||
msgstr "El correo ya existe"
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
|
||||
msgid "Email cannot already exist in the template"
|
||||
msgstr "El correo electrónico no puede existir ya en la plantilla"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Email Confirmed!"
|
||||
msgstr "¡Correo electrónico confirmado!"
|
||||
@@ -4278,6 +4296,10 @@ msgstr "Habilitar orden de firma"
|
||||
msgid "Enable SSO portal"
|
||||
msgstr "Habilitar portal SSO"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Enable team API tokens to delegate document ownership to another team member."
|
||||
msgstr "Habilita los tokens de API del equipo para delegar la propiedad del documento a otro miembro del equipo."
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
@@ -4295,6 +4317,10 @@ msgstr "Habilitar la cuenta permite al usuario usar la cuenta de nuevo, junto co
|
||||
msgid "Enclosed Document"
|
||||
msgstr "Documento Adjunto"
|
||||
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Documentos adjuntos"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Ingrese un nombre para su nueva carpeta. Las carpetas le ayudan a organizar sus elementos."
|
||||
@@ -4370,6 +4396,7 @@ msgid "Envelope Duplicated"
|
||||
msgstr "Sobre Duplicado"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Envelope ID"
|
||||
msgstr "ID de Sobre"
|
||||
|
||||
@@ -5067,6 +5094,7 @@ msgid "I am required to receive a copy of this document"
|
||||
msgstr "Se me requiere recibir una copia de este documento"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "I am the owner of this document"
|
||||
msgstr "Soy el propietario de este documento"
|
||||
|
||||
@@ -5162,6 +5190,7 @@ msgstr "Heredar método de autenticación"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
msgid "Inherit from organisation"
|
||||
@@ -5293,6 +5322,8 @@ msgstr "Factura"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "IP Address"
|
||||
msgstr "Dirección IP"
|
||||
|
||||
@@ -5405,6 +5436,7 @@ msgid "Last updated"
|
||||
msgstr "Última actualización"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Last Updated"
|
||||
msgstr "Última Actualización"
|
||||
|
||||
@@ -5554,6 +5586,7 @@ msgid "Looking for signature fields"
|
||||
msgstr "Buscando campos de firma"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-groups-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-teams-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
msgid "Manage"
|
||||
@@ -5614,6 +5647,10 @@ msgstr "Gestionar cuentas vinculadas"
|
||||
msgid "Manage organisation"
|
||||
msgstr "Administrar organización"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Manage Organisation"
|
||||
msgstr "Gestionar organización"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx
|
||||
msgid "Manage organisations"
|
||||
msgstr "Administrar organizaciones"
|
||||
@@ -5861,6 +5898,7 @@ msgid "My Folder"
|
||||
msgstr "Mi Carpeta"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
@@ -5960,6 +5998,7 @@ msgstr "Nombre del próximo destinatario"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
@@ -6386,6 +6425,7 @@ msgstr "Anular la configuración de la organización"
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Owner"
|
||||
msgstr "Propietario"
|
||||
|
||||
@@ -6436,6 +6476,7 @@ msgid "Passkey name"
|
||||
msgstr "Nombre de clave de acceso"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Passkey Re-Authentication"
|
||||
msgstr "Re-autenticación de Passkey"
|
||||
|
||||
@@ -6462,6 +6503,7 @@ msgid "Password"
|
||||
msgstr "Contraseña"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Password Re-Authentication"
|
||||
msgstr "Reautenticación de contraseña"
|
||||
|
||||
@@ -6932,6 +6974,7 @@ msgid "Ready"
|
||||
msgstr "Listo"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Reason"
|
||||
msgstr "Razón"
|
||||
|
||||
@@ -7018,6 +7061,7 @@ msgstr "Destinatario actualizado"
|
||||
#: apps/remix/app/components/general/template/template-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Recipients"
|
||||
msgstr "Destinatarios"
|
||||
|
||||
@@ -7095,6 +7139,7 @@ msgstr "Rechazar Documento"
|
||||
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Rejected"
|
||||
msgstr "Rejected"
|
||||
|
||||
@@ -7332,6 +7377,11 @@ msgstr "Reintentar"
|
||||
msgid "Return"
|
||||
msgstr "Regresar"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx
|
||||
msgid "Return Home"
|
||||
msgstr "Volver al inicio"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/o.$orgUrl.signin.tsx
|
||||
msgid "Return to Documenso sign in page here"
|
||||
msgstr "Regrese a la página de inicio de sesión de Documenso aquí"
|
||||
@@ -7761,6 +7811,7 @@ msgstr "Enviando..."
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Sent"
|
||||
msgstr "Enviado"
|
||||
|
||||
@@ -7991,6 +8042,7 @@ msgstr "Firme sus iniciales en el campo"
|
||||
#: apps/remix/app/components/general/envelope-signing/envelope-signer-form.tsx
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/types.ts
|
||||
@@ -7999,6 +8051,7 @@ msgid "Signature"
|
||||
msgstr "Firma"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signature ID"
|
||||
msgstr "ID de Firma"
|
||||
|
||||
@@ -8024,6 +8077,7 @@ msgstr "Firmas recolectadas"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/ui/components/document/document-read-only-fields.tsx
|
||||
#: packages/ui/components/document/envelope-recipient-field-tooltip.tsx
|
||||
msgid "Signed"
|
||||
@@ -8049,6 +8103,7 @@ msgid "Signer"
|
||||
msgstr "Firmante"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signer Events"
|
||||
msgstr "Eventos del Firmante"
|
||||
|
||||
@@ -8063,10 +8118,12 @@ msgid "Signing"
|
||||
msgstr "Firmando"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing Certificate"
|
||||
msgstr "Certificado de Firma"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing certificate provided by"
|
||||
msgstr "Certificado de firma proporcionado por"
|
||||
|
||||
@@ -8274,6 +8331,7 @@ msgstr "Estadísticas"
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Status"
|
||||
msgstr "Estado"
|
||||
|
||||
@@ -8835,6 +8893,12 @@ msgstr "El propietario del documento ha sido notificado de este rechazo. No se r
|
||||
msgid "The document owner has been notified of your decision. They may contact you with further instructions if necessary."
|
||||
msgstr "The document owner has been notified of your decision. They may contact you with further instructions if necessary."
|
||||
|
||||
#. placeholder {0}: data.delegatedOwnerName || data.delegatedOwnerEmail
|
||||
#. placeholder {1}: data.teamName
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "The document ownership was delegated to {0} on behalf of {1}"
|
||||
msgstr "La propiedad del documento fue delegada a {0} en nombre de {1}"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "The document was created but could not be sent to recipients."
|
||||
msgstr "El documento fue creado pero no se pudo enviar a los destinatarios."
|
||||
@@ -9410,6 +9474,7 @@ msgstr "Zona horaria"
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Time Zone"
|
||||
@@ -9601,6 +9666,7 @@ msgstr "Los códigos de recuperación de autenticación de dos factores se utili
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Authentication"
|
||||
msgstr "Autenticación de dos factores"
|
||||
|
||||
@@ -9617,6 +9683,7 @@ msgid "Two-factor authentication has been disabled for your account. You will no
|
||||
msgstr "La autenticación de dos factores ha sido desactivada para tu cuenta. Ya no se te pedirá ingresar un código de tu aplicación de autenticador al iniciar sesión."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Re-Authentication"
|
||||
msgstr "Re-autenticación de Doble Factor"
|
||||
|
||||
@@ -9750,6 +9817,10 @@ msgstr "Deshacer"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Unknown"
|
||||
msgstr "Desconocido"
|
||||
|
||||
@@ -10052,10 +10123,12 @@ msgstr "Utilice su clave de acceso para la autenticación"
|
||||
|
||||
#: apps/remix/app/components/tables/document-logs-table.tsx
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User"
|
||||
msgstr "Usuario"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User Agent"
|
||||
msgstr "Agente de usuario"
|
||||
|
||||
@@ -10299,6 +10372,7 @@ msgstr "Ver los registros DNS para este dominio de correo electrónico"
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-list.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Viewed"
|
||||
msgstr "Visto"
|
||||
|
||||
@@ -10842,6 +10916,7 @@ msgstr "Anual"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Yes"
|
||||
msgstr "Sí"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: fr\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-12-15 02:39\n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
@@ -244,6 +244,7 @@ msgid "{0} Teams"
|
||||
msgstr "{0} Équipes"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{browserInfo} on {os}"
|
||||
msgstr "{browserInfo} sur {os}"
|
||||
|
||||
@@ -475,6 +476,7 @@ msgid "{teamName} has invited you to {action} {documentName}"
|
||||
msgstr "{teamName} vous a invité à {action} {documentName}"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{userAgent}"
|
||||
msgstr "{userAgent}"
|
||||
|
||||
@@ -955,6 +957,7 @@ msgid "Account"
|
||||
msgstr "Compte"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Authentication"
|
||||
msgstr "Authentification de compte"
|
||||
|
||||
@@ -992,6 +995,7 @@ msgid "Account Linking Request"
|
||||
msgstr "Demande de liaison de compte"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Re-Authentication"
|
||||
msgstr "Ré-authentification de compte"
|
||||
|
||||
@@ -1939,6 +1943,7 @@ msgid "Attempts sealing the document again, useful for after a code change has o
|
||||
msgstr "Essaye de sceller le document à nouveau, utile après qu'un changement de code ait eu lieu pour résoudre un document erroné."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Audit Log"
|
||||
msgstr "Journal d'audit"
|
||||
|
||||
@@ -1947,6 +1952,7 @@ msgid "Audit Logs"
|
||||
msgstr "Journaux de vérification"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Authentication Level"
|
||||
msgstr "Niveau d'authentification"
|
||||
|
||||
@@ -2967,6 +2973,7 @@ msgstr "Créé"
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Created At"
|
||||
msgstr "Créé le"
|
||||
|
||||
@@ -3109,6 +3116,10 @@ msgstr "Fuseau horaire par défaut"
|
||||
msgid "Default Value"
|
||||
msgstr "Valeur par défaut"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Delegate Document Ownership"
|
||||
msgstr "Déléguer la propriété du document"
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx
|
||||
msgid "delete"
|
||||
msgstr "supprimer"
|
||||
@@ -3255,6 +3266,7 @@ msgstr "Supprimez votre compte et tout son contenu, y compris les documents comp
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Deleted"
|
||||
msgstr "Supprimé"
|
||||
|
||||
@@ -3268,6 +3280,7 @@ msgstr "Destination"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Details"
|
||||
msgstr "Détails"
|
||||
|
||||
@@ -3324,6 +3337,7 @@ msgstr "Mode développeur"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Device"
|
||||
msgstr "Appareil"
|
||||
|
||||
@@ -3672,6 +3686,11 @@ msgctxt "Audit log format"
|
||||
msgid "Document opened"
|
||||
msgstr "Document ouvert"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgctxt "Audit log format"
|
||||
msgid "Document ownership delegated"
|
||||
msgstr "Propriété du document déléguée"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
msgid "Document pending"
|
||||
msgstr "Document en attente"
|
||||
@@ -4013,6 +4032,7 @@ msgstr "Par ex. 100"
|
||||
#: apps/remix/app/components/general/document/document-page-view-button.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-dropdown.tsx
|
||||
#: apps/remix/app/components/general/teams/team-email-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-button.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
|
||||
@@ -4080,6 +4100,8 @@ msgstr "Divulgation de signature électronique"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
@@ -4110,10 +4132,6 @@ msgstr "E-mail déjà confirmé"
|
||||
msgid "Email already exists"
|
||||
msgstr "L'e-mail existe déjà"
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
|
||||
msgid "Email cannot already exist in the template"
|
||||
msgstr "L'e-mail ne peut déjà exister dans le modèle"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Email Confirmed!"
|
||||
msgstr "Email confirmé !"
|
||||
@@ -4278,6 +4296,10 @@ msgstr "Activer l'ordre de signature"
|
||||
msgid "Enable SSO portal"
|
||||
msgstr "Activer le portail SSO"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Enable team API tokens to delegate document ownership to another team member."
|
||||
msgstr "Activer les jetons d’API d’équipe pour déléguer la propriété du document à un autre membre de l’équipe."
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
@@ -4295,6 +4317,10 @@ msgstr "Activer le compte permet à l'utilisateur de pouvoir utiliser le compte
|
||||
msgid "Enclosed Document"
|
||||
msgstr "Document joint"
|
||||
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Documents joints"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Entrez un nom pour votre nouveau dossier. Les dossiers vous aident à organiser vos éléments."
|
||||
@@ -4370,6 +4396,7 @@ msgid "Envelope Duplicated"
|
||||
msgstr "Enveloppe dupliquée"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Envelope ID"
|
||||
msgstr "ID de l'enveloppe"
|
||||
|
||||
@@ -5067,6 +5094,7 @@ msgid "I am required to receive a copy of this document"
|
||||
msgstr "Je dois recevoir une copie de ce document"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "I am the owner of this document"
|
||||
msgstr "Je suis le propriétaire de ce document"
|
||||
|
||||
@@ -5162,6 +5190,7 @@ msgstr "Hériter de la méthode d'authentification"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
msgid "Inherit from organisation"
|
||||
@@ -5293,6 +5322,8 @@ msgstr "Facture"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "IP Address"
|
||||
msgstr "Adresse IP"
|
||||
|
||||
@@ -5405,6 +5436,7 @@ msgid "Last updated"
|
||||
msgstr "Dernière mise à jour"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Last Updated"
|
||||
msgstr "Dernière mise à jour"
|
||||
|
||||
@@ -5554,6 +5586,7 @@ msgid "Looking for signature fields"
|
||||
msgstr "Recherche de champs de signature"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-groups-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-teams-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
msgid "Manage"
|
||||
@@ -5614,6 +5647,10 @@ msgstr "Gérer les comptes liés"
|
||||
msgid "Manage organisation"
|
||||
msgstr "Gérer l'organisation"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Manage Organisation"
|
||||
msgstr "Gérer l’organisation"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx
|
||||
msgid "Manage organisations"
|
||||
msgstr "Gérer les organisations"
|
||||
@@ -5861,6 +5898,7 @@ msgid "My Folder"
|
||||
msgstr "Mon Dossier"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
@@ -5960,6 +5998,7 @@ msgstr "Nom du destinataire suivant"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "No"
|
||||
msgstr "Non"
|
||||
|
||||
@@ -6386,6 +6425,7 @@ msgstr "Ignorer les paramètres de l'organisation"
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Owner"
|
||||
msgstr "Propriétaire"
|
||||
|
||||
@@ -6436,6 +6476,7 @@ msgid "Passkey name"
|
||||
msgstr "Nom de la clé d'accès"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Passkey Re-Authentication"
|
||||
msgstr "Ré-authentification par clé d'accès"
|
||||
|
||||
@@ -6462,6 +6503,7 @@ msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Password Re-Authentication"
|
||||
msgstr "Ré-authentification par mot de passe"
|
||||
|
||||
@@ -6932,6 +6974,7 @@ msgid "Ready"
|
||||
msgstr "Prêt"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Reason"
|
||||
msgstr "Raison"
|
||||
|
||||
@@ -7018,6 +7061,7 @@ msgstr "Destinataire mis à jour"
|
||||
#: apps/remix/app/components/general/template/template-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Recipients"
|
||||
msgstr "Destinataires"
|
||||
|
||||
@@ -7095,6 +7139,7 @@ msgstr "Rejeter le Document"
|
||||
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Rejected"
|
||||
msgstr "Rejeté"
|
||||
|
||||
@@ -7332,6 +7377,11 @@ msgstr "Réessayer"
|
||||
msgid "Return"
|
||||
msgstr "Retour"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx
|
||||
msgid "Return Home"
|
||||
msgstr "Retour à l’accueil"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/o.$orgUrl.signin.tsx
|
||||
msgid "Return to Documenso sign in page here"
|
||||
msgstr "Revenir à la page de connexion de Documenso ici"
|
||||
@@ -7761,6 +7811,7 @@ msgstr "Envoi..."
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Sent"
|
||||
msgstr "Envoyé"
|
||||
|
||||
@@ -7991,6 +8042,7 @@ msgstr "Signez vos initiales dans le champ"
|
||||
#: apps/remix/app/components/general/envelope-signing/envelope-signer-form.tsx
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/types.ts
|
||||
@@ -7999,6 +8051,7 @@ msgid "Signature"
|
||||
msgstr "Signature"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signature ID"
|
||||
msgstr "ID de signature"
|
||||
|
||||
@@ -8024,6 +8077,7 @@ msgstr "Signatures collectées"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/ui/components/document/document-read-only-fields.tsx
|
||||
#: packages/ui/components/document/envelope-recipient-field-tooltip.tsx
|
||||
msgid "Signed"
|
||||
@@ -8049,6 +8103,7 @@ msgid "Signer"
|
||||
msgstr "Signataire"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signer Events"
|
||||
msgstr "Événements de signataire"
|
||||
|
||||
@@ -8063,10 +8118,12 @@ msgid "Signing"
|
||||
msgstr "En train de signer"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing Certificate"
|
||||
msgstr "Certificat de signature"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing certificate provided by"
|
||||
msgstr "Certificat de signature fourni par"
|
||||
|
||||
@@ -8274,6 +8331,7 @@ msgstr "Statistiques"
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Status"
|
||||
msgstr "Statut"
|
||||
|
||||
@@ -8835,6 +8893,12 @@ msgstr "Le propriétaire du document a été informé de ce rejet. Aucune action
|
||||
msgid "The document owner has been notified of your decision. They may contact you with further instructions if necessary."
|
||||
msgstr "Le propriétaire du document a été informé de votre décision. Il peut vous contacter pour des instructions supplémentaires si nécessaire."
|
||||
|
||||
#. placeholder {0}: data.delegatedOwnerName || data.delegatedOwnerEmail
|
||||
#. placeholder {1}: data.teamName
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "The document ownership was delegated to {0} on behalf of {1}"
|
||||
msgstr "La propriété du document a été déléguée à {0} au nom de {1}"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "The document was created but could not be sent to recipients."
|
||||
msgstr "Le document a été créé mais n'a pas pu être envoyé aux destinataires."
|
||||
@@ -9410,6 +9474,7 @@ msgstr "Fuseau horaire"
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Time Zone"
|
||||
@@ -9601,6 +9666,7 @@ msgstr "Les codes de récupération de l'authentification à deux facteurs sont
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Authentication"
|
||||
msgstr "Authentification à deux facteurs"
|
||||
|
||||
@@ -9617,6 +9683,7 @@ msgid "Two-factor authentication has been disabled for your account. You will no
|
||||
msgstr "L'authentification à deux facteurs a été désactivée pour votre compte. Vous ne serez plus tenu d'entrer un code de votre application d'authentification lors de la connexion."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Re-Authentication"
|
||||
msgstr "Ré-authentification à deux facteurs"
|
||||
|
||||
@@ -9750,6 +9817,10 @@ msgstr "Annuler"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Unknown"
|
||||
msgstr "Inconnu"
|
||||
|
||||
@@ -10052,10 +10123,12 @@ msgstr "Utilisez votre clé d'accès pour l'authentification"
|
||||
|
||||
#: apps/remix/app/components/tables/document-logs-table.tsx
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User"
|
||||
msgstr "Utilisateur"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User Agent"
|
||||
msgstr "Agent utilisateur"
|
||||
|
||||
@@ -10299,6 +10372,7 @@ msgstr "Voir les enregistrements DNS pour ce domaine de messagerie"
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-list.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Viewed"
|
||||
msgstr "Vu"
|
||||
|
||||
@@ -10842,6 +10916,7 @@ msgstr "Annuel"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Yes"
|
||||
msgstr "Oui"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: it\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-12-15 02:39\n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@@ -244,6 +244,7 @@ msgid "{0} Teams"
|
||||
msgstr "{0} Squadre"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{browserInfo} on {os}"
|
||||
msgstr "{browserInfo} su {os}"
|
||||
|
||||
@@ -475,6 +476,7 @@ msgid "{teamName} has invited you to {action} {documentName}"
|
||||
msgstr "{teamName} ti ha invitato a {action} {documentName}"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{userAgent}"
|
||||
msgstr "{userAgent}"
|
||||
|
||||
@@ -955,6 +957,7 @@ msgid "Account"
|
||||
msgstr "Account"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Authentication"
|
||||
msgstr "Autenticazione dell'account"
|
||||
|
||||
@@ -992,6 +995,7 @@ msgid "Account Linking Request"
|
||||
msgstr "Richiesta di Collegamento Account"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Re-Authentication"
|
||||
msgstr "Ri-autenticazione dell'account"
|
||||
|
||||
@@ -1939,6 +1943,7 @@ msgid "Attempts sealing the document again, useful for after a code change has o
|
||||
msgstr "Tenta nuovamente di sigillare il documento, utile dopo una modifica al codice per risolvere un documento errato."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Audit Log"
|
||||
msgstr "Registro di controllo"
|
||||
|
||||
@@ -1947,6 +1952,7 @@ msgid "Audit Logs"
|
||||
msgstr "Registri di Audit"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Authentication Level"
|
||||
msgstr "Livello di Autenticazione"
|
||||
|
||||
@@ -2967,6 +2973,7 @@ msgstr "Creato"
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Created At"
|
||||
msgstr "Creato il"
|
||||
|
||||
@@ -3109,6 +3116,10 @@ msgstr "Fuso Orario Predefinito"
|
||||
msgid "Default Value"
|
||||
msgstr "Valore predefinito"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Delegate Document Ownership"
|
||||
msgstr "Delega della proprietà del documento"
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx
|
||||
msgid "delete"
|
||||
msgstr "elimina"
|
||||
@@ -3255,6 +3266,7 @@ msgstr "Elimina il tuo account e tutti i suoi contenuti, inclusi i documenti com
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Deleted"
|
||||
msgstr "Eliminato"
|
||||
|
||||
@@ -3268,6 +3280,7 @@ msgstr "Destinazione"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Details"
|
||||
msgstr "Dettagli"
|
||||
|
||||
@@ -3324,6 +3337,7 @@ msgstr "Modalità sviluppatore"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Device"
|
||||
msgstr "Dispositivo"
|
||||
|
||||
@@ -3672,6 +3686,11 @@ msgctxt "Audit log format"
|
||||
msgid "Document opened"
|
||||
msgstr "Documento aperto"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgctxt "Audit log format"
|
||||
msgid "Document ownership delegated"
|
||||
msgstr "Proprietà del documento delegata"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
msgid "Document pending"
|
||||
msgstr "Documento in sospeso"
|
||||
@@ -4013,6 +4032,7 @@ msgstr "Es. 100"
|
||||
#: apps/remix/app/components/general/document/document-page-view-button.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-dropdown.tsx
|
||||
#: apps/remix/app/components/general/teams/team-email-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-button.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
|
||||
@@ -4080,6 +4100,8 @@ msgstr "Divulgazione della firma elettronica"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
@@ -4110,10 +4132,6 @@ msgstr "Email già confermata"
|
||||
msgid "Email already exists"
|
||||
msgstr "Email già esistente"
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
|
||||
msgid "Email cannot already exist in the template"
|
||||
msgstr "L'email non può già esistere nel modello"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Email Confirmed!"
|
||||
msgstr "Email confermato!"
|
||||
@@ -4278,6 +4296,10 @@ msgstr "Abilita ordine di firma"
|
||||
msgid "Enable SSO portal"
|
||||
msgstr "Abilita portale SSO"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Enable team API tokens to delegate document ownership to another team member."
|
||||
msgstr "Abilita i token API del team per delegare la proprietà del documento a un altro membro del team."
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
@@ -4295,6 +4317,10 @@ msgstr "Abilitare l'account consente all'utente di utilizzare nuovamente l'accou
|
||||
msgid "Enclosed Document"
|
||||
msgstr "Documento Allegato"
|
||||
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Documenti allegati"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Inserisci un nome per la tua nuova cartella. Le cartelle ti aiutano a organizzare i tuoi elementi."
|
||||
@@ -4370,6 +4396,7 @@ msgid "Envelope Duplicated"
|
||||
msgstr "Busta Duplicata"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Envelope ID"
|
||||
msgstr "ID Busta"
|
||||
|
||||
@@ -5067,6 +5094,7 @@ msgid "I am required to receive a copy of this document"
|
||||
msgstr "Sono tenuto a ricevere una copia di questo documento"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "I am the owner of this document"
|
||||
msgstr "Sono il proprietario di questo documento"
|
||||
|
||||
@@ -5162,6 +5190,7 @@ msgstr "Ereditare metodo di autenticazione"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
msgid "Inherit from organisation"
|
||||
@@ -5293,6 +5322,8 @@ msgstr "Fattura"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "IP Address"
|
||||
msgstr "Indirizzo IP"
|
||||
|
||||
@@ -5405,6 +5436,7 @@ msgid "Last updated"
|
||||
msgstr "Ultimo aggiornamento"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Last Updated"
|
||||
msgstr "Ultimo aggiornamento"
|
||||
|
||||
@@ -5554,6 +5586,7 @@ msgid "Looking for signature fields"
|
||||
msgstr "Ricerca dei campi firma in corso"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-groups-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-teams-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
msgid "Manage"
|
||||
@@ -5614,6 +5647,10 @@ msgstr "Gestisci account collegati"
|
||||
msgid "Manage organisation"
|
||||
msgstr "Gestisci l'organizzazione"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Manage Organisation"
|
||||
msgstr "Gestisci organizzazione"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx
|
||||
msgid "Manage organisations"
|
||||
msgstr "Gestisci le organizzazioni"
|
||||
@@ -5861,6 +5898,7 @@ msgid "My Folder"
|
||||
msgstr "La Mia Cartella"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
@@ -5960,6 +5998,7 @@ msgstr "Nome prossimo destinatario"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
@@ -6386,6 +6425,7 @@ msgstr "Sovrascrivi impostazioni organizzazione"
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Owner"
|
||||
msgstr "Proprietario"
|
||||
|
||||
@@ -6436,6 +6476,7 @@ msgid "Passkey name"
|
||||
msgstr "Nome della passkey"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Passkey Re-Authentication"
|
||||
msgstr "Ri-autenticazione con chiave di accesso"
|
||||
|
||||
@@ -6462,6 +6503,7 @@ msgid "Password"
|
||||
msgstr "\"Password\""
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Password Re-Authentication"
|
||||
msgstr "Riautenticazione della password"
|
||||
|
||||
@@ -6932,6 +6974,7 @@ msgid "Ready"
|
||||
msgstr "Pronto"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Reason"
|
||||
msgstr "Motivo"
|
||||
|
||||
@@ -7018,6 +7061,7 @@ msgstr "Destinatario aggiornato"
|
||||
#: apps/remix/app/components/general/template/template-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Recipients"
|
||||
msgstr "Destinatari"
|
||||
|
||||
@@ -7095,6 +7139,7 @@ msgstr "Rifiuta Documento"
|
||||
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Rejected"
|
||||
msgstr "Rifiutato"
|
||||
|
||||
@@ -7332,6 +7377,11 @@ msgstr "Riprova"
|
||||
msgid "Return"
|
||||
msgstr "Ritorna"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx
|
||||
msgid "Return Home"
|
||||
msgstr "Torna alla home"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/o.$orgUrl.signin.tsx
|
||||
msgid "Return to Documenso sign in page here"
|
||||
msgstr "Torna alla pagina di accesso a Documenso qui"
|
||||
@@ -7761,6 +7811,7 @@ msgstr "Invio..."
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Sent"
|
||||
msgstr "Inviato"
|
||||
|
||||
@@ -7991,6 +8042,7 @@ msgstr "Firma le tue iniziali nel campo"
|
||||
#: apps/remix/app/components/general/envelope-signing/envelope-signer-form.tsx
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/types.ts
|
||||
@@ -7999,6 +8051,7 @@ msgid "Signature"
|
||||
msgstr "Firma"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signature ID"
|
||||
msgstr "ID Firma"
|
||||
|
||||
@@ -8024,6 +8077,7 @@ msgstr "Firme raccolte"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/ui/components/document/document-read-only-fields.tsx
|
||||
#: packages/ui/components/document/envelope-recipient-field-tooltip.tsx
|
||||
msgid "Signed"
|
||||
@@ -8049,6 +8103,7 @@ msgid "Signer"
|
||||
msgstr "Firmante"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signer Events"
|
||||
msgstr "Eventi del Firmatario"
|
||||
|
||||
@@ -8063,10 +8118,12 @@ msgid "Signing"
|
||||
msgstr "Firma in corso"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing Certificate"
|
||||
msgstr "Certificato di Firma"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing certificate provided by"
|
||||
msgstr "Certificato di firma fornito da"
|
||||
|
||||
@@ -8274,6 +8331,7 @@ msgstr "Statistiche"
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Status"
|
||||
msgstr "Stato"
|
||||
|
||||
@@ -8835,6 +8893,12 @@ msgstr "Il proprietario del documento è stato informato di questo rifiuto. Non
|
||||
msgid "The document owner has been notified of your decision. They may contact you with further instructions if necessary."
|
||||
msgstr "Il proprietario del documento è stato informato della tua decisione. Potrebbe contattarti per ulteriori istruzioni, se necessario."
|
||||
|
||||
#. placeholder {0}: data.delegatedOwnerName || data.delegatedOwnerEmail
|
||||
#. placeholder {1}: data.teamName
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "The document ownership was delegated to {0} on behalf of {1}"
|
||||
msgstr "La proprietà del documento è stata delegata a {0} per conto di {1}"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "The document was created but could not be sent to recipients."
|
||||
msgstr "Il documento è stato creato ma non è stato possibile inviarlo ai destinatari."
|
||||
@@ -9410,6 +9474,7 @@ msgstr "Fuso orario"
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Time Zone"
|
||||
@@ -9601,6 +9666,7 @@ msgstr "I codici di recupero dell'autenticazione a due fattori sono utilizzati p
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Authentication"
|
||||
msgstr "Autenticazione a due fattori"
|
||||
|
||||
@@ -9617,6 +9683,7 @@ msgid "Two-factor authentication has been disabled for your account. You will no
|
||||
msgstr "L'autenticazione a due fattori è stata disattivata per il tuo account. Non sarà più necessario inserire un codice dalla tua app di autenticazione quando accedi."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Re-Authentication"
|
||||
msgstr "Ri-autenticazione a due fattori"
|
||||
|
||||
@@ -9750,6 +9817,10 @@ msgstr "Annulla"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Unknown"
|
||||
msgstr "Sconosciuto"
|
||||
|
||||
@@ -10052,10 +10123,12 @@ msgstr "Usa la tua chiave di accesso per l'autenticazione"
|
||||
|
||||
#: apps/remix/app/components/tables/document-logs-table.tsx
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User"
|
||||
msgstr "Utente"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User Agent"
|
||||
msgstr "User Agent"
|
||||
|
||||
@@ -10299,6 +10372,7 @@ msgstr "Visualizza i record DNS per questo dominio email"
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-list.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Viewed"
|
||||
msgstr "Visualizzato"
|
||||
|
||||
@@ -10842,6 +10916,7 @@ msgstr "Annuale"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Yes"
|
||||
msgstr "Sì"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ja\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-12-15 02:39\n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Japanese\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
@@ -244,6 +244,7 @@ msgid "{0} Teams"
|
||||
msgstr "{0} のチーム"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{browserInfo} on {os}"
|
||||
msgstr "{browserInfo}({os})"
|
||||
|
||||
@@ -475,6 +476,7 @@ msgid "{teamName} has invited you to {action} {documentName}"
|
||||
msgstr "{teamName} から {action} {documentName} の依頼が届いています"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{userAgent}"
|
||||
msgstr "{userAgent}"
|
||||
|
||||
@@ -955,6 +957,7 @@ msgid "Account"
|
||||
msgstr "アカウント"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Authentication"
|
||||
msgstr "アカウント認証"
|
||||
|
||||
@@ -992,6 +995,7 @@ msgid "Account Linking Request"
|
||||
msgstr "アカウントリンクリクエスト"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Re-Authentication"
|
||||
msgstr "アカウント再認証"
|
||||
|
||||
@@ -1939,6 +1943,7 @@ msgid "Attempts sealing the document again, useful for after a code change has o
|
||||
msgstr "文書の再封止を試行します。コード変更後に文書の不具合を解消する際などに役立ちます。"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Audit Log"
|
||||
msgstr "監査ログ"
|
||||
|
||||
@@ -1947,6 +1952,7 @@ msgid "Audit Logs"
|
||||
msgstr "監査ログ"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Authentication Level"
|
||||
msgstr "認証レベル"
|
||||
|
||||
@@ -2967,6 +2973,7 @@ msgstr "作成日時"
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Created At"
|
||||
msgstr "作成日時"
|
||||
|
||||
@@ -3109,6 +3116,10 @@ msgstr "既定のタイムゾーン"
|
||||
msgid "Default Value"
|
||||
msgstr "既定値"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Delegate Document Ownership"
|
||||
msgstr "文書の所有権を委譲する"
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx
|
||||
msgid "delete"
|
||||
msgstr "delete"
|
||||
@@ -3255,6 +3266,7 @@ msgstr "アカウントと、その完了済み文書を含むすべての内容
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Deleted"
|
||||
msgstr "削除済み"
|
||||
|
||||
@@ -3268,6 +3280,7 @@ msgstr "送信先"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Details"
|
||||
msgstr "詳細"
|
||||
|
||||
@@ -3324,6 +3337,7 @@ msgstr "開発者モード"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Device"
|
||||
msgstr "デバイス"
|
||||
|
||||
@@ -3672,6 +3686,11 @@ msgctxt "Audit log format"
|
||||
msgid "Document opened"
|
||||
msgstr "ドキュメントが開かれました"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgctxt "Audit log format"
|
||||
msgid "Document ownership delegated"
|
||||
msgstr "文書の所有権が委譲されました"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
msgid "Document pending"
|
||||
msgstr "文書は保留中です"
|
||||
@@ -4013,6 +4032,7 @@ msgstr "例: 100"
|
||||
#: apps/remix/app/components/general/document/document-page-view-button.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-dropdown.tsx
|
||||
#: apps/remix/app/components/general/teams/team-email-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-button.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
|
||||
@@ -4080,6 +4100,8 @@ msgstr "電子署名に関する開示"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
@@ -4110,10 +4132,6 @@ msgstr "メールはすでに確認済みです"
|
||||
msgid "Email already exists"
|
||||
msgstr "メールはすでに存在します"
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
|
||||
msgid "Email cannot already exist in the template"
|
||||
msgstr "このテンプレート内ですでに使用されているメールアドレスは指定できません"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Email Confirmed!"
|
||||
msgstr "メールアドレスが確認されました!"
|
||||
@@ -4278,6 +4296,10 @@ msgstr "署名順序を有効にする"
|
||||
msgid "Enable SSO portal"
|
||||
msgstr "SSO ポータルを有効にする"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Enable team API tokens to delegate document ownership to another team member."
|
||||
msgstr "チーム API トークンを有効にして、別のチームメンバーに文書の所有権を委譲できるようにします。"
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
@@ -4295,6 +4317,10 @@ msgstr "アカウントを有効にすると、そのユーザーは再びアカ
|
||||
msgid "Enclosed Document"
|
||||
msgstr "同封文書"
|
||||
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "同封された文書"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "新しいフォルダ名を入力してください。フォルダを使うとアイテムを整理できます。"
|
||||
@@ -4370,6 +4396,7 @@ msgid "Envelope Duplicated"
|
||||
msgstr "封筒を複製しました"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Envelope ID"
|
||||
msgstr "封筒ID"
|
||||
|
||||
@@ -5067,6 +5094,7 @@ msgid "I am required to receive a copy of this document"
|
||||
msgstr "私はこのドキュメントのコピー受信が必須です"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "I am the owner of this document"
|
||||
msgstr "私はこの文書の所有者です"
|
||||
|
||||
@@ -5162,6 +5190,7 @@ msgstr "認証方法を継承"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
msgid "Inherit from organisation"
|
||||
@@ -5293,6 +5322,8 @@ msgstr "請求書"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "IP Address"
|
||||
msgstr "IP アドレス"
|
||||
|
||||
@@ -5405,6 +5436,7 @@ msgid "Last updated"
|
||||
msgstr "最終更新日時"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Last Updated"
|
||||
msgstr "最終更新"
|
||||
|
||||
@@ -5554,6 +5586,7 @@ msgid "Looking for signature fields"
|
||||
msgstr "署名フィールドを探しています"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-groups-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-teams-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
msgid "Manage"
|
||||
@@ -5614,6 +5647,10 @@ msgstr "リンク済みアカウントを管理"
|
||||
msgid "Manage organisation"
|
||||
msgstr "組織を管理"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Manage Organisation"
|
||||
msgstr "組織を管理する"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx
|
||||
msgid "Manage organisations"
|
||||
msgstr "組織を管理"
|
||||
@@ -5861,6 +5898,7 @@ msgid "My Folder"
|
||||
msgstr "マイフォルダ"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
@@ -5960,6 +5998,7 @@ msgstr "次の受信者の名前"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "No"
|
||||
msgstr "いいえ"
|
||||
|
||||
@@ -6386,6 +6425,7 @@ msgstr "組織設定を上書き"
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Owner"
|
||||
msgstr "所有者"
|
||||
|
||||
@@ -6436,6 +6476,7 @@ msgid "Passkey name"
|
||||
msgstr "パスキー名"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Passkey Re-Authentication"
|
||||
msgstr "パスキーによる再認証"
|
||||
|
||||
@@ -6462,6 +6503,7 @@ msgid "Password"
|
||||
msgstr "パスワード"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Password Re-Authentication"
|
||||
msgstr "パスワード再認証"
|
||||
|
||||
@@ -6932,6 +6974,7 @@ msgid "Ready"
|
||||
msgstr "準備完了"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Reason"
|
||||
msgstr "理由"
|
||||
|
||||
@@ -7018,6 +7061,7 @@ msgstr "受信者を更新しました"
|
||||
#: apps/remix/app/components/general/template/template-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Recipients"
|
||||
msgstr "受信者"
|
||||
|
||||
@@ -7095,6 +7139,7 @@ msgstr "ドキュメントを却下"
|
||||
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Rejected"
|
||||
msgstr "却下済み"
|
||||
|
||||
@@ -7332,6 +7377,11 @@ msgstr "再試行"
|
||||
msgid "Return"
|
||||
msgstr "戻る"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx
|
||||
msgid "Return Home"
|
||||
msgstr "ホームに戻る"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/o.$orgUrl.signin.tsx
|
||||
msgid "Return to Documenso sign in page here"
|
||||
msgstr "Documenso のサインインページへ戻る"
|
||||
@@ -7761,6 +7811,7 @@ msgstr "送信中..."
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Sent"
|
||||
msgstr "送信日時"
|
||||
|
||||
@@ -7991,6 +8042,7 @@ msgstr "イニシャルをフィールドに入力してください"
|
||||
#: apps/remix/app/components/general/envelope-signing/envelope-signer-form.tsx
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/types.ts
|
||||
@@ -7999,6 +8051,7 @@ msgid "Signature"
|
||||
msgstr "署名"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signature ID"
|
||||
msgstr "署名 ID"
|
||||
|
||||
@@ -8024,6 +8077,7 @@ msgstr "収集された署名数"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/ui/components/document/document-read-only-fields.tsx
|
||||
#: packages/ui/components/document/envelope-recipient-field-tooltip.tsx
|
||||
msgid "Signed"
|
||||
@@ -8049,6 +8103,7 @@ msgid "Signer"
|
||||
msgstr "署名者"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signer Events"
|
||||
msgstr "署名者のイベント"
|
||||
|
||||
@@ -8063,10 +8118,12 @@ msgid "Signing"
|
||||
msgstr "署名中"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing Certificate"
|
||||
msgstr "署名証明書"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing certificate provided by"
|
||||
msgstr "署名証明書の提供元"
|
||||
|
||||
@@ -8274,6 +8331,7 @@ msgstr "統計"
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Status"
|
||||
msgstr "ステータス"
|
||||
|
||||
@@ -8835,6 +8893,12 @@ msgstr "この却下内容について、ドキュメントの所有者に通知
|
||||
msgid "The document owner has been notified of your decision. They may contact you with further instructions if necessary."
|
||||
msgstr "お客様の決定について、ドキュメントの所有者に通知しました。必要に応じて、追加の手順について連絡がある場合があります。"
|
||||
|
||||
#. placeholder {0}: data.delegatedOwnerName || data.delegatedOwnerEmail
|
||||
#. placeholder {1}: data.teamName
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "The document ownership was delegated to {0} on behalf of {1}"
|
||||
msgstr "文書の所有権は、{1} を代表して {0} に委譲されました"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "The document was created but could not be sent to recipients."
|
||||
msgstr "文書は作成されましたが、受信者に送信できませんでした。"
|
||||
@@ -9410,6 +9474,7 @@ msgstr "タイムゾーン"
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Time Zone"
|
||||
@@ -9601,6 +9666,7 @@ msgstr "二要素認証のリカバリーコードは、認証アプリにアク
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Authentication"
|
||||
msgstr "二要素認証"
|
||||
|
||||
@@ -9617,6 +9683,7 @@ msgid "Two-factor authentication has been disabled for your account. You will no
|
||||
msgstr "アカウントの二要素認証を無効にしました。今後、サインイン時に認証アプリのコードを入力する必要はありません。"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Re-Authentication"
|
||||
msgstr "二要素認証による再認証"
|
||||
|
||||
@@ -9750,6 +9817,10 @@ msgstr "元に戻す"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Unknown"
|
||||
msgstr "不明"
|
||||
|
||||
@@ -10052,10 +10123,12 @@ msgstr "パスキーで認証する"
|
||||
|
||||
#: apps/remix/app/components/tables/document-logs-table.tsx
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User"
|
||||
msgstr "ユーザー"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User Agent"
|
||||
msgstr "ユーザーエージェント"
|
||||
|
||||
@@ -10299,6 +10372,7 @@ msgstr "このメールドメインの DNS レコードを表示"
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-list.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Viewed"
|
||||
msgstr "閲覧済み"
|
||||
|
||||
@@ -10842,6 +10916,7 @@ msgstr "年額"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Yes"
|
||||
msgstr "はい"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ko\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-12-15 02:39\n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Korean\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
@@ -244,6 +244,7 @@ msgid "{0} Teams"
|
||||
msgstr "{0} 팀"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{browserInfo} on {os}"
|
||||
msgstr "{browserInfo} / {os}"
|
||||
|
||||
@@ -475,6 +476,7 @@ msgid "{teamName} has invited you to {action} {documentName}"
|
||||
msgstr "{teamName}이(가) 귀하께 {action} {documentName} 문서를 요청했습니다."
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{userAgent}"
|
||||
msgstr "{userAgent}"
|
||||
|
||||
@@ -955,6 +957,7 @@ msgid "Account"
|
||||
msgstr "계정"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Authentication"
|
||||
msgstr "계정 인증"
|
||||
|
||||
@@ -992,6 +995,7 @@ msgid "Account Linking Request"
|
||||
msgstr "계정 연결 요청"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Re-Authentication"
|
||||
msgstr "계정 재인증"
|
||||
|
||||
@@ -1939,6 +1943,7 @@ msgid "Attempts sealing the document again, useful for after a code change has o
|
||||
msgstr "코드 변경으로 잘못된 문서를 수정한 후, 문서를 다시 봉인할 때 유용합니다."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Audit Log"
|
||||
msgstr "감사 로그"
|
||||
|
||||
@@ -1947,6 +1952,7 @@ msgid "Audit Logs"
|
||||
msgstr "감사 로그"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Authentication Level"
|
||||
msgstr "인증 수준"
|
||||
|
||||
@@ -2967,6 +2973,7 @@ msgstr "생성일"
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Created At"
|
||||
msgstr "생성 일시"
|
||||
|
||||
@@ -3109,6 +3116,10 @@ msgstr "기본 시간대"
|
||||
msgid "Default Value"
|
||||
msgstr "기본값"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Delegate Document Ownership"
|
||||
msgstr "문서 소유권 위임"
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx
|
||||
msgid "delete"
|
||||
msgstr "delete"
|
||||
@@ -3255,6 +3266,7 @@ msgstr "계정 및 완료된 문서를 포함한 모든 콘텐츠가 삭제됩
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Deleted"
|
||||
msgstr "삭제됨"
|
||||
|
||||
@@ -3268,6 +3280,7 @@ msgstr "대상"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Details"
|
||||
msgstr "세부 정보"
|
||||
|
||||
@@ -3324,6 +3337,7 @@ msgstr "개발자 모드"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Device"
|
||||
msgstr "디바이스"
|
||||
|
||||
@@ -3672,6 +3686,11 @@ msgctxt "Audit log format"
|
||||
msgid "Document opened"
|
||||
msgstr "문서가 열렸습니다."
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgctxt "Audit log format"
|
||||
msgid "Document ownership delegated"
|
||||
msgstr "문서 소유권이 위임됨"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
msgid "Document pending"
|
||||
msgstr "문서 보류 중"
|
||||
@@ -4013,6 +4032,7 @@ msgstr "예: 100"
|
||||
#: apps/remix/app/components/general/document/document-page-view-button.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-dropdown.tsx
|
||||
#: apps/remix/app/components/general/teams/team-email-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-button.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
|
||||
@@ -4080,6 +4100,8 @@ msgstr "전자 서명 고지"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
@@ -4110,10 +4132,6 @@ msgstr "이미 확인된 이메일입니다."
|
||||
msgid "Email already exists"
|
||||
msgstr "이메일이 이미 존재합니다."
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
|
||||
msgid "Email cannot already exist in the template"
|
||||
msgstr "이 이메일은 템플릿에 이미 존재할 수 없습니다"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Email Confirmed!"
|
||||
msgstr "이메일이 확인되었습니다!"
|
||||
@@ -4278,6 +4296,10 @@ msgstr "서명 순서 활성화"
|
||||
msgid "Enable SSO portal"
|
||||
msgstr "SSO 포털 활성화"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Enable team API tokens to delegate document ownership to another team member."
|
||||
msgstr "팀 API 토큰을 활성화하여 문서 소유권을 다른 팀 구성원에게 위임하세요."
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
@@ -4295,6 +4317,10 @@ msgstr "계정을 활성화하면 사용자가 다시 계정을 사용할 수
|
||||
msgid "Enclosed Document"
|
||||
msgstr "동봉 문서"
|
||||
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "동봉된 문서들"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "새 폴더 이름을 입력하세요. 폴더는 항목을 정리하는 데 도움이 됩니다."
|
||||
@@ -4370,6 +4396,7 @@ msgid "Envelope Duplicated"
|
||||
msgstr "봉투가 복제되었습니다"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Envelope ID"
|
||||
msgstr "봉투 ID"
|
||||
|
||||
@@ -5067,6 +5094,7 @@ msgid "I am required to receive a copy of this document"
|
||||
msgstr "저는 이 문서의 사본을 받아야 합니다."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "I am the owner of this document"
|
||||
msgstr "이 문서의 소유자입니다"
|
||||
|
||||
@@ -5162,6 +5190,7 @@ msgstr "인증 방식 상속"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
msgid "Inherit from organisation"
|
||||
@@ -5293,6 +5322,8 @@ msgstr "청구서"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "IP Address"
|
||||
msgstr "IP 주소"
|
||||
|
||||
@@ -5405,6 +5436,7 @@ msgid "Last updated"
|
||||
msgstr "마지막 업데이트"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Last Updated"
|
||||
msgstr "마지막 업데이트"
|
||||
|
||||
@@ -5554,6 +5586,7 @@ msgid "Looking for signature fields"
|
||||
msgstr "서명 필드를 찾는 중"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-groups-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-teams-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
msgid "Manage"
|
||||
@@ -5614,6 +5647,10 @@ msgstr "연결된 계정 관리"
|
||||
msgid "Manage organisation"
|
||||
msgstr "조직 관리"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Manage Organisation"
|
||||
msgstr "조직 관리"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx
|
||||
msgid "Manage organisations"
|
||||
msgstr "조직 관리"
|
||||
@@ -5861,6 +5898,7 @@ msgid "My Folder"
|
||||
msgstr "내 폴더"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "N/A"
|
||||
msgstr "해당 없음"
|
||||
|
||||
@@ -5960,6 +5998,7 @@ msgstr "다음 수신자 이름"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "No"
|
||||
msgstr "아니요"
|
||||
|
||||
@@ -6386,6 +6425,7 @@ msgstr "조직 설정 재정의"
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Owner"
|
||||
msgstr "소유자"
|
||||
|
||||
@@ -6436,6 +6476,7 @@ msgid "Passkey name"
|
||||
msgstr "패스키 이름"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Passkey Re-Authentication"
|
||||
msgstr "패스키 재인증"
|
||||
|
||||
@@ -6462,6 +6503,7 @@ msgid "Password"
|
||||
msgstr "비밀번호"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Password Re-Authentication"
|
||||
msgstr "비밀번호 재인증"
|
||||
|
||||
@@ -6932,6 +6974,7 @@ msgid "Ready"
|
||||
msgstr "준비됨"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Reason"
|
||||
msgstr "이유"
|
||||
|
||||
@@ -7018,6 +7061,7 @@ msgstr "수신자가 업데이트되었습니다"
|
||||
#: apps/remix/app/components/general/template/template-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Recipients"
|
||||
msgstr "수신자"
|
||||
|
||||
@@ -7095,6 +7139,7 @@ msgstr "문서 거부"
|
||||
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Rejected"
|
||||
msgstr "거부됨"
|
||||
|
||||
@@ -7332,6 +7377,11 @@ msgstr "재시도"
|
||||
msgid "Return"
|
||||
msgstr "돌아가기"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx
|
||||
msgid "Return Home"
|
||||
msgstr "홈으로 돌아가기"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/o.$orgUrl.signin.tsx
|
||||
msgid "Return to Documenso sign in page here"
|
||||
msgstr "여기를 눌러 Documenso 로그인 페이지로 돌아가기"
|
||||
@@ -7761,6 +7811,7 @@ msgstr "전송 중..."
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Sent"
|
||||
msgstr "발송됨"
|
||||
|
||||
@@ -7991,6 +8042,7 @@ msgstr "필드에 이니셜을 입력하세요"
|
||||
#: apps/remix/app/components/general/envelope-signing/envelope-signer-form.tsx
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/types.ts
|
||||
@@ -7999,6 +8051,7 @@ msgid "Signature"
|
||||
msgstr "서명"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signature ID"
|
||||
msgstr "서명 ID"
|
||||
|
||||
@@ -8024,6 +8077,7 @@ msgstr "수집된 서명 수"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/ui/components/document/document-read-only-fields.tsx
|
||||
#: packages/ui/components/document/envelope-recipient-field-tooltip.tsx
|
||||
msgid "Signed"
|
||||
@@ -8049,6 +8103,7 @@ msgid "Signer"
|
||||
msgstr "서명자"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signer Events"
|
||||
msgstr "서명자 이벤트"
|
||||
|
||||
@@ -8063,10 +8118,12 @@ msgid "Signing"
|
||||
msgstr "서명 중"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing Certificate"
|
||||
msgstr "서명 인증서"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing certificate provided by"
|
||||
msgstr "서명 인증서 제공자"
|
||||
|
||||
@@ -8274,6 +8331,7 @@ msgstr "통계"
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Status"
|
||||
msgstr "상태"
|
||||
|
||||
@@ -8835,6 +8893,12 @@ msgstr "문서 소유자에게 이 거부 사실이 통보되었습니다. 현
|
||||
msgid "The document owner has been notified of your decision. They may contact you with further instructions if necessary."
|
||||
msgstr "문서 소유자에게 귀하의 결정이 이미 전달되었습니다. 필요할 경우 추가 안내를 위해 연락을 드릴 수 있습니다."
|
||||
|
||||
#. placeholder {0}: data.delegatedOwnerName || data.delegatedOwnerEmail
|
||||
#. placeholder {1}: data.teamName
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "The document ownership was delegated to {0} on behalf of {1}"
|
||||
msgstr "문서 소유권이 {1}을(를) 대신해 {0}에게 위임되었습니다"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "The document was created but could not be sent to recipients."
|
||||
msgstr "문서는 생성되었지만 수신자에게 발송되지 않았습니다."
|
||||
@@ -9410,6 +9474,7 @@ msgstr "시간대"
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Time Zone"
|
||||
@@ -9601,6 +9666,7 @@ msgstr "2단계 인증 복구 코드는 인증 앱에 대한 접근 권한을
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Authentication"
|
||||
msgstr "2단계 인증"
|
||||
|
||||
@@ -9617,6 +9683,7 @@ msgid "Two-factor authentication has been disabled for your account. You will no
|
||||
msgstr "계정의 2단계 인증이 비활성화되었습니다. 앞으로 로그인할 때 인증 앱의 코드를 입력하지 않아도 됩니다."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Re-Authentication"
|
||||
msgstr "2단계 재인증"
|
||||
|
||||
@@ -9750,6 +9817,10 @@ msgstr "실행 취소"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Unknown"
|
||||
msgstr "알 수 없음"
|
||||
|
||||
@@ -10052,10 +10123,12 @@ msgstr "패스키로 인증하세요."
|
||||
|
||||
#: apps/remix/app/components/tables/document-logs-table.tsx
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User"
|
||||
msgstr "사용자"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User Agent"
|
||||
msgstr "User Agent"
|
||||
|
||||
@@ -10299,6 +10372,7 @@ msgstr "이 이메일 도메인에 대한 DNS 레코드를 확인합니다."
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-list.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Viewed"
|
||||
msgstr "열람됨"
|
||||
|
||||
@@ -10842,6 +10916,7 @@ msgstr "연간"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Yes"
|
||||
msgstr "예"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: nl\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-12-15 02:39\n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@@ -244,6 +244,7 @@ msgid "{0} Teams"
|
||||
msgstr "{0} teams"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{browserInfo} on {os}"
|
||||
msgstr "{browserInfo} op {os}"
|
||||
|
||||
@@ -475,6 +476,7 @@ msgid "{teamName} has invited you to {action} {documentName}"
|
||||
msgstr "{teamName} heeft je uitgenodigd om {action} {documentName}"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{userAgent}"
|
||||
msgstr "{userAgent}"
|
||||
|
||||
@@ -955,6 +957,7 @@ msgid "Account"
|
||||
msgstr "Account"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Authentication"
|
||||
msgstr "Accountauthenticatie"
|
||||
|
||||
@@ -992,6 +995,7 @@ msgid "Account Linking Request"
|
||||
msgstr "Verzoek tot koppelen account"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Re-Authentication"
|
||||
msgstr "Account‑herauthenticatie"
|
||||
|
||||
@@ -1939,6 +1943,7 @@ msgid "Attempts sealing the document again, useful for after a code change has o
|
||||
msgstr "Probeert het document opnieuw te verzegelen, handig nadat een codewijziging is gedaan om een foutief document op te lossen."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Audit Log"
|
||||
msgstr "Auditlog"
|
||||
|
||||
@@ -1947,6 +1952,7 @@ msgid "Audit Logs"
|
||||
msgstr "Auditlogs"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Authentication Level"
|
||||
msgstr "Authenticatieniveau"
|
||||
|
||||
@@ -2967,6 +2973,7 @@ msgstr "Aangemaakt"
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Created At"
|
||||
msgstr "Aangemaakt op"
|
||||
|
||||
@@ -3109,6 +3116,10 @@ msgstr "Standaardtijdzone"
|
||||
msgid "Default Value"
|
||||
msgstr "Standaardwaarde"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Delegate Document Ownership"
|
||||
msgstr "Documenteigendom delegeren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx
|
||||
msgid "delete"
|
||||
msgstr "verwijderen"
|
||||
@@ -3255,6 +3266,7 @@ msgstr "Verwijder je account en alle inhoud, inclusief voltooide documenten. Dez
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Deleted"
|
||||
msgstr "Verwijderd"
|
||||
|
||||
@@ -3268,6 +3280,7 @@ msgstr "Bestemming"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Details"
|
||||
msgstr "Details"
|
||||
|
||||
@@ -3324,6 +3337,7 @@ msgstr "Ontwikkelaarsmodus"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Device"
|
||||
msgstr "Apparaat"
|
||||
|
||||
@@ -3672,6 +3686,11 @@ msgctxt "Audit log format"
|
||||
msgid "Document opened"
|
||||
msgstr "Document geopend"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgctxt "Audit log format"
|
||||
msgid "Document ownership delegated"
|
||||
msgstr "Documenteigendom gedelegeerd"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
msgid "Document pending"
|
||||
msgstr "Document in behandeling"
|
||||
@@ -4013,6 +4032,7 @@ msgstr "Bijv. 100"
|
||||
#: apps/remix/app/components/general/document/document-page-view-button.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-dropdown.tsx
|
||||
#: apps/remix/app/components/general/teams/team-email-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-button.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
|
||||
@@ -4080,6 +4100,8 @@ msgstr "Kennisgeving elektronische handtekening"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
@@ -4110,10 +4132,6 @@ msgstr "E-mailadres al bevestigd"
|
||||
msgid "Email already exists"
|
||||
msgstr "E-mailadres bestaat al"
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
|
||||
msgid "Email cannot already exist in the template"
|
||||
msgstr "E‑mail mag nog niet in de sjabloon bestaan"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Email Confirmed!"
|
||||
msgstr "E‑mail bevestigd!"
|
||||
@@ -4278,6 +4296,10 @@ msgstr "Ondertekeningsvolgorde inschakelen"
|
||||
msgid "Enable SSO portal"
|
||||
msgstr "SSO-portaal inschakelen"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Enable team API tokens to delegate document ownership to another team member."
|
||||
msgstr "Schakel team-API-tokens in om documenteigendom aan een ander teamlid te delegeren."
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
@@ -4295,6 +4317,10 @@ msgstr "Als je het account inschakelt, kan de gebruiker het account weer gebruik
|
||||
msgid "Enclosed Document"
|
||||
msgstr "Bijgevoegd document"
|
||||
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Bijgevoegde documenten"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Voer een naam in voor je nieuwe map. Mappen helpen je je items te organiseren."
|
||||
@@ -4370,6 +4396,7 @@ msgid "Envelope Duplicated"
|
||||
msgstr "Envelope gedupliceerd"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Envelope ID"
|
||||
msgstr "Envelope-ID"
|
||||
|
||||
@@ -5067,6 +5094,7 @@ msgid "I am required to receive a copy of this document"
|
||||
msgstr "Ik moet een kopie van dit document ontvangen"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "I am the owner of this document"
|
||||
msgstr "Ik ben de eigenaar van dit document"
|
||||
|
||||
@@ -5162,6 +5190,7 @@ msgstr "Authenticatiemethode overnemen"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
msgid "Inherit from organisation"
|
||||
@@ -5293,6 +5322,8 @@ msgstr "Factuur"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "IP Address"
|
||||
msgstr "IP‑adres"
|
||||
|
||||
@@ -5405,6 +5436,7 @@ msgid "Last updated"
|
||||
msgstr "Laatst bijgewerkt"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Last Updated"
|
||||
msgstr "Laatst bijgewerkt"
|
||||
|
||||
@@ -5554,6 +5586,7 @@ msgid "Looking for signature fields"
|
||||
msgstr "Zoeken naar handtekeningvelden"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-groups-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-teams-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
msgid "Manage"
|
||||
@@ -5614,6 +5647,10 @@ msgstr "Gekoppelde accounts beheren"
|
||||
msgid "Manage organisation"
|
||||
msgstr "Organisatie beheren"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Manage Organisation"
|
||||
msgstr "Organisatie beheren"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx
|
||||
msgid "Manage organisations"
|
||||
msgstr "Organisaties beheren"
|
||||
@@ -5861,6 +5898,7 @@ msgid "My Folder"
|
||||
msgstr "Mijn map"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "N/A"
|
||||
msgstr "n.v.t."
|
||||
|
||||
@@ -5960,6 +5998,7 @@ msgstr "Naam volgende ontvanger"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "No"
|
||||
msgstr "Nee"
|
||||
|
||||
@@ -6386,6 +6425,7 @@ msgstr "Organisatie-instellingen overschrijven"
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Owner"
|
||||
msgstr "Eigenaar"
|
||||
|
||||
@@ -6436,6 +6476,7 @@ msgid "Passkey name"
|
||||
msgstr "Naam van passkey"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Passkey Re-Authentication"
|
||||
msgstr "Passkey‑herauthenticatie"
|
||||
|
||||
@@ -6462,6 +6503,7 @@ msgid "Password"
|
||||
msgstr "Wachtwoord"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Password Re-Authentication"
|
||||
msgstr "Wachtwoordherauthenticatie"
|
||||
|
||||
@@ -6932,6 +6974,7 @@ msgid "Ready"
|
||||
msgstr "Klaar"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Reason"
|
||||
msgstr "Reden"
|
||||
|
||||
@@ -7018,6 +7061,7 @@ msgstr "Ontvanger bijgewerkt"
|
||||
#: apps/remix/app/components/general/template/template-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Recipients"
|
||||
msgstr "Ontvangers"
|
||||
|
||||
@@ -7095,6 +7139,7 @@ msgstr "Document weigeren"
|
||||
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Rejected"
|
||||
msgstr "Geweigerd"
|
||||
|
||||
@@ -7332,6 +7377,11 @@ msgstr "Opnieuw proberen"
|
||||
msgid "Return"
|
||||
msgstr "Terugkeren"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx
|
||||
msgid "Return Home"
|
||||
msgstr "Terug naar startpagina"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/o.$orgUrl.signin.tsx
|
||||
msgid "Return to Documenso sign in page here"
|
||||
msgstr "Ga hier terug naar de Documenso-aanmeldpagina"
|
||||
@@ -7761,6 +7811,7 @@ msgstr "Verzenden..."
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Sent"
|
||||
msgstr "Verzonden"
|
||||
|
||||
@@ -7991,6 +8042,7 @@ msgstr "Schrijf uw initialen in het veld"
|
||||
#: apps/remix/app/components/general/envelope-signing/envelope-signer-form.tsx
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/types.ts
|
||||
@@ -7999,6 +8051,7 @@ msgid "Signature"
|
||||
msgstr "Handtekening"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signature ID"
|
||||
msgstr "Handtekening‑ID"
|
||||
|
||||
@@ -8024,6 +8077,7 @@ msgstr "Verzamelde handtekeningen"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/ui/components/document/document-read-only-fields.tsx
|
||||
#: packages/ui/components/document/envelope-recipient-field-tooltip.tsx
|
||||
msgid "Signed"
|
||||
@@ -8049,6 +8103,7 @@ msgid "Signer"
|
||||
msgstr "Ondertekenaar"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signer Events"
|
||||
msgstr "Ondertekenaarsgebeurtenissen"
|
||||
|
||||
@@ -8063,10 +8118,12 @@ msgid "Signing"
|
||||
msgstr "Ondertekenen"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing Certificate"
|
||||
msgstr "Ondertekeningscertificaat"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing certificate provided by"
|
||||
msgstr "Ondertekeningscertificaat verstrekt door"
|
||||
|
||||
@@ -8274,6 +8331,7 @@ msgstr "Statistieken"
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
@@ -8835,6 +8893,12 @@ msgstr "De documenteigenaar is op de hoogte gebracht van deze weigering. Er is o
|
||||
msgid "The document owner has been notified of your decision. They may contact you with further instructions if necessary."
|
||||
msgstr "De documenteigenaar is op de hoogte gebracht van je beslissing. Indien nodig kan diegene contact met je opnemen met verdere instructies."
|
||||
|
||||
#. placeholder {0}: data.delegatedOwnerName || data.delegatedOwnerEmail
|
||||
#. placeholder {1}: data.teamName
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "The document ownership was delegated to {0} on behalf of {1}"
|
||||
msgstr "De documenteigendom is namens {1} gedelegeerd aan {0}"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "The document was created but could not be sent to recipients."
|
||||
msgstr "Het document is aangemaakt, maar kon niet naar ontvangers worden verzonden."
|
||||
@@ -9410,6 +9474,7 @@ msgstr "Tijdzone"
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Time Zone"
|
||||
@@ -9601,6 +9666,7 @@ msgstr "Herstelcodes voor twee‑factor‑authenticatie worden gebruikt om toega
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Authentication"
|
||||
msgstr "Twee‑factor‑authenticatie"
|
||||
|
||||
@@ -9617,6 +9683,7 @@ msgid "Two-factor authentication has been disabled for your account. You will no
|
||||
msgstr "Twee‑factor‑authenticatie is uitgeschakeld voor je account. Je hoeft niet langer een code van je authenticator‑app in te voeren bij het inloggen."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Re-Authentication"
|
||||
msgstr "Twee‑factor‑herauthenticatie"
|
||||
|
||||
@@ -9750,6 +9817,10 @@ msgstr "Ongedaan maken"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Unknown"
|
||||
msgstr "Onbekend"
|
||||
|
||||
@@ -10052,10 +10123,12 @@ msgstr "Gebruik je passkey voor authenticatie"
|
||||
|
||||
#: apps/remix/app/components/tables/document-logs-table.tsx
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User"
|
||||
msgstr "Gebruiker"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User Agent"
|
||||
msgstr "User Agent"
|
||||
|
||||
@@ -10299,6 +10372,7 @@ msgstr "Bekijk de DNS-records voor dit e-maildomein"
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-list.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Viewed"
|
||||
msgstr "Bekeken"
|
||||
|
||||
@@ -10842,6 +10916,7 @@ msgstr "Jaarlijks"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Yes"
|
||||
msgstr "Ja"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: pl\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-12-15 02:39\n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Polish\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||
@@ -244,6 +244,7 @@ msgid "{0} Teams"
|
||||
msgstr "Zespół {0}"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{browserInfo} on {os}"
|
||||
msgstr "{browserInfo} na {os}"
|
||||
|
||||
@@ -475,6 +476,7 @@ msgid "{teamName} has invited you to {action} {documentName}"
|
||||
msgstr "Sprawdź i {action} dokument „{documentName}” utworzony przez zespół {teamName}"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "{userAgent}"
|
||||
msgstr "{userAgent}"
|
||||
|
||||
@@ -955,6 +957,7 @@ msgid "Account"
|
||||
msgstr "Konto"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Authentication"
|
||||
msgstr "Konto"
|
||||
|
||||
@@ -992,6 +995,7 @@ msgid "Account Linking Request"
|
||||
msgstr "Prośba o połączenie konta"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Account Re-Authentication"
|
||||
msgstr "Konto"
|
||||
|
||||
@@ -1939,6 +1943,7 @@ msgid "Attempts sealing the document again, useful for after a code change has o
|
||||
msgstr "Ponowna próba zapieczętowania dokumentu jest przydatna po zmianie kodu, aby usunąć błędy dokumentu."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Audit Log"
|
||||
msgstr "Dziennik logów"
|
||||
|
||||
@@ -1947,6 +1952,7 @@ msgid "Audit Logs"
|
||||
msgstr "Dziennik logów"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Authentication Level"
|
||||
msgstr "Poziom uwierzytelniania"
|
||||
|
||||
@@ -2967,6 +2973,7 @@ msgstr "Utworzono"
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Created At"
|
||||
msgstr "Utworzono"
|
||||
|
||||
@@ -3109,6 +3116,10 @@ msgstr "Domyślna strefa czasowa"
|
||||
msgid "Default Value"
|
||||
msgstr "Domyślna wartość"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Delegate Document Ownership"
|
||||
msgstr "Zmień właściciela dokumentu"
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx
|
||||
msgid "delete"
|
||||
msgstr "usuń"
|
||||
@@ -3255,6 +3266,7 @@ msgstr "Usuń swoje konto i wszystkie jego dane, w tym zakończone dokumenty. Ta
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Deleted"
|
||||
msgstr "Usunięto"
|
||||
|
||||
@@ -3268,6 +3280,7 @@ msgstr "Adres docelowy"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Details"
|
||||
msgstr "Szczegóły"
|
||||
|
||||
@@ -3324,6 +3337,7 @@ msgstr "Tryb programisty"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Device"
|
||||
msgstr "Urządzenie"
|
||||
|
||||
@@ -3672,6 +3686,11 @@ msgctxt "Audit log format"
|
||||
msgid "Document opened"
|
||||
msgstr "Otwarto dokument"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgctxt "Audit log format"
|
||||
msgid "Document ownership delegated"
|
||||
msgstr "Zmieniono właściciela dokumentu"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
msgid "Document pending"
|
||||
msgstr "Dokument oczekujący"
|
||||
@@ -4013,6 +4032,7 @@ msgstr "Np. 100"
|
||||
#: apps/remix/app/components/general/document/document-page-view-button.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-dropdown.tsx
|
||||
#: apps/remix/app/components/general/teams/team-email-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-button.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
|
||||
@@ -4080,6 +4100,8 @@ msgstr "Informacje o podpisie elektronicznym"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
@@ -4110,10 +4132,6 @@ msgstr "Adres e-mail został już potwierdzony"
|
||||
msgid "Email already exists"
|
||||
msgstr "Adres e-mail już istnieje"
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
|
||||
msgid "Email cannot already exist in the template"
|
||||
msgstr "Adres e-mail nie może już istnieć w szablonie"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Email Confirmed!"
|
||||
msgstr "Adres e-mail został potwierdzony!"
|
||||
@@ -4278,6 +4296,10 @@ msgstr "Włącz kolejność podpisywania"
|
||||
msgid "Enable SSO portal"
|
||||
msgstr "Włącz logowanie SSO"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Enable team API tokens to delegate document ownership to another team member."
|
||||
msgstr "Włącz tokeny API zespołu, aby zmienić właściciela dokumentu na innego użytkownika zespołu."
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
@@ -4295,6 +4317,10 @@ msgstr "Włączenie konta spowoduje, że użytkownik będzie mógł ponownie kor
|
||||
msgid "Enclosed Document"
|
||||
msgstr "Załączony dokument"
|
||||
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Załączone dokumenty"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Wpisz nazwę nowego folderu. Foldery pomagają uporządkować elementy."
|
||||
@@ -4370,6 +4396,7 @@ msgid "Envelope Duplicated"
|
||||
msgstr "Zduplikowano kopertę"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Envelope ID"
|
||||
msgstr "Identyfikator koperty"
|
||||
|
||||
@@ -5067,6 +5094,7 @@ msgid "I am required to receive a copy of this document"
|
||||
msgstr "Muszę otrzymać kopię dokumentu"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "I am the owner of this document"
|
||||
msgstr "Jestem właścicielem tego dokumentu"
|
||||
|
||||
@@ -5162,6 +5190,7 @@ msgstr "Odziedzicz metodę uwierzytelniania"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/email-preferences-form.tsx
|
||||
msgid "Inherit from organisation"
|
||||
@@ -5293,6 +5322,8 @@ msgstr "Faktura"
|
||||
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "IP Address"
|
||||
msgstr "Adres IP"
|
||||
|
||||
@@ -5405,6 +5436,7 @@ msgid "Last updated"
|
||||
msgstr "Zaktualizowano"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Last Updated"
|
||||
msgstr "Zaktualizowano"
|
||||
|
||||
@@ -5554,6 +5586,7 @@ msgid "Looking for signature fields"
|
||||
msgstr "Wyszukiwanie pól podpisu"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-groups-table.tsx
|
||||
#: apps/remix/app/components/tables/organisation-teams-table.tsx
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
msgid "Manage"
|
||||
@@ -5614,6 +5647,10 @@ msgstr "Zarządzaj połączonymi kontami"
|
||||
msgid "Manage organisation"
|
||||
msgstr "Zarządzaj organizacją"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Manage Organisation"
|
||||
msgstr "Zarządzaj organizacją"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx
|
||||
msgid "Manage organisations"
|
||||
msgstr "Zarządzaj organizacjami"
|
||||
@@ -5861,6 +5898,7 @@ msgid "My Folder"
|
||||
msgstr "Mój folder"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "N/A"
|
||||
msgstr "Nie dotyczy"
|
||||
|
||||
@@ -5960,6 +5998,7 @@ msgstr "Nazwa następnego odbiorcy"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "No"
|
||||
msgstr "Nie"
|
||||
|
||||
@@ -6386,6 +6425,7 @@ msgstr "Nadpisz ustawienia organizacji"
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Owner"
|
||||
msgstr "Właściciel"
|
||||
|
||||
@@ -6436,6 +6476,7 @@ msgid "Passkey name"
|
||||
msgstr "Nazwa klucza dostępu"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Passkey Re-Authentication"
|
||||
msgstr "Klucz dostępu"
|
||||
|
||||
@@ -6462,6 +6503,7 @@ msgid "Password"
|
||||
msgstr "Hasło"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Password Re-Authentication"
|
||||
msgstr "Hasło"
|
||||
|
||||
@@ -6932,6 +6974,7 @@ msgid "Ready"
|
||||
msgstr "Gotowy"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Reason"
|
||||
msgstr "Rola"
|
||||
|
||||
@@ -7018,6 +7061,7 @@ msgstr "Odbiorca został zaktualizowany"
|
||||
#: apps/remix/app/components/general/template/template-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Recipients"
|
||||
msgstr "Odbiorcy"
|
||||
|
||||
@@ -7095,6 +7139,7 @@ msgstr "Odrzuć dokument"
|
||||
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Rejected"
|
||||
msgstr "Odrzucono"
|
||||
|
||||
@@ -7195,7 +7240,7 @@ msgstr "Odpowiedz na adres"
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Reply To Email <0>(Optional)</0>"
|
||||
msgstr "Odpowiedź na e-mail <0>(opcjonalnie)</0>"
|
||||
msgstr "Odpowiedz na wiadomość <0>(opcjonalnie)</0>"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
msgid "Request"
|
||||
@@ -7332,6 +7377,11 @@ msgstr "Spróbuj ponownie"
|
||||
msgid "Return"
|
||||
msgstr "Wróć"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx
|
||||
msgid "Return Home"
|
||||
msgstr "Powrót do strony głównej"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/o.$orgUrl.signin.tsx
|
||||
msgid "Return to Documenso sign in page here"
|
||||
msgstr "Powrót do strony logowania Documenso"
|
||||
@@ -7761,6 +7811,7 @@ msgstr "Wysyłanie..."
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Sent"
|
||||
msgstr "Wysłano"
|
||||
|
||||
@@ -7817,7 +7868,7 @@ msgstr "Udostępnij link"
|
||||
|
||||
#: packages/ui/components/document/document-share-button.tsx
|
||||
msgid "Share your signing experience!"
|
||||
msgstr "Podziel się swoimi wrażeniami związanymi z podpisywaniem!"
|
||||
msgstr "Podziel się wrażeniami z podpisywania!"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.public-profile.tsx
|
||||
msgid "Show"
|
||||
@@ -7991,6 +8042,7 @@ msgstr "Podpisz inicjały w polu"
|
||||
#: apps/remix/app/components/general/envelope-signing/envelope-signer-form.tsx
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/document-flow/types.ts
|
||||
@@ -7999,6 +8051,7 @@ msgid "Signature"
|
||||
msgstr "Podpis"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signature ID"
|
||||
msgstr "Identyfikator podpisu"
|
||||
|
||||
@@ -8024,6 +8077,7 @@ msgstr "Liczba podpisów"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/ui/components/document/document-read-only-fields.tsx
|
||||
#: packages/ui/components/document/envelope-recipient-field-tooltip.tsx
|
||||
msgid "Signed"
|
||||
@@ -8049,6 +8103,7 @@ msgid "Signer"
|
||||
msgstr "Podpisujący"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signer Events"
|
||||
msgstr "Zdarzenia podpisujących"
|
||||
|
||||
@@ -8063,10 +8118,12 @@ msgid "Signing"
|
||||
msgstr "Podpisuje"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing Certificate"
|
||||
msgstr "Certyfikat podpisu"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Signing certificate provided by"
|
||||
msgstr "Certyfikat podpisu został dostarczony przez"
|
||||
|
||||
@@ -8274,6 +8331,7 @@ msgstr "Statystyki"
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
@@ -8835,6 +8893,12 @@ msgstr "Właściciel został poinformowany o odrzuceniu dokumentu. Może się z
|
||||
msgid "The document owner has been notified of your decision. They may contact you with further instructions if necessary."
|
||||
msgstr "Właściciel dokumentu został poinformowany o Twojej decyzji. Może się z Tobą skontaktować, jeśli będzie to konieczne."
|
||||
|
||||
#. placeholder {0}: data.delegatedOwnerName || data.delegatedOwnerEmail
|
||||
#. placeholder {1}: data.teamName
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "The document ownership was delegated to {0} on behalf of {1}"
|
||||
msgstr "Zmieniono właściciela dokumentu na {0} z zespołu {1}"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "The document was created but could not be sent to recipients."
|
||||
msgstr "Dokument został utworzony, ale nie mógł zostać wysłany do odbiorców."
|
||||
@@ -9410,6 +9474,7 @@ msgstr "Strefa czasowa"
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Time Zone"
|
||||
@@ -9601,6 +9666,7 @@ msgstr "Kody odzyskiwania są używane do odzyskania dostępu do konta, w przypa
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Authentication"
|
||||
msgstr "Weryfikacja dwuetapowa"
|
||||
|
||||
@@ -9617,6 +9683,7 @@ msgid "Two-factor authentication has been disabled for your account. You will no
|
||||
msgstr "Weryfikacja dwuetapowa konta została wyłączona. Wpisywanie kodu z aplikacji uwierzytelniającej podczas logowania nie będzie już wymagane."
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/generate-certificate-pdf.ts
|
||||
msgid "Two-Factor Re-Authentication"
|
||||
msgstr "Weryfikacja dwuetapowa"
|
||||
|
||||
@@ -9750,6 +9817,10 @@ msgstr "Cofnij"
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Unknown"
|
||||
msgstr "Nieznany"
|
||||
|
||||
@@ -10052,10 +10123,12 @@ msgstr "Użyj klucza dostępu do uwierzytelniania"
|
||||
|
||||
#: apps/remix/app/components/tables/document-logs-table.tsx
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User"
|
||||
msgstr "Użytkownik"
|
||||
|
||||
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "User Agent"
|
||||
msgstr "User agent"
|
||||
|
||||
@@ -10299,6 +10372,7 @@ msgstr "Wyświetl rekordy DNS dla tej domeny"
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-list.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
#: packages/lib/server-only/pdf/render-certificate.ts
|
||||
msgid "Viewed"
|
||||
msgstr "Wyświetlono"
|
||||
|
||||
@@ -10842,6 +10916,7 @@ msgstr "Rocznie"
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Yes"
|
||||
msgstr "Tak"
|
||||
|
||||
@@ -10955,12 +11030,12 @@ msgstr "Aktualizujesz adres <0>{0}</0>"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-member-update-dialog.tsx
|
||||
msgid "You are currently updating <0>{memberName}</0>."
|
||||
msgstr "Aktualizujesz adres <0>{memberName}</0>."
|
||||
msgstr "Aktualizujesz użytkownika <0>{memberName}</0>."
|
||||
|
||||
#: apps/remix/app/components/dialogs/admin-organisation-member-update-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx
|
||||
msgid "You are currently updating <0>{organisationMemberName}</0>."
|
||||
msgstr "Aktualnie aktualizujesz <0>{organisationMemberName}</0>."
|
||||
msgstr "Aktualizujesz użytkownika <0>{organisationMemberName}</0>."
|
||||
|
||||
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
|
||||
msgid "You are currently updating the <0>{passkeyName}</0> passkey."
|
||||
|
||||