Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e20561e91 | |||
| a2ec5f0fa1 | |||
| de8d13a4c1 | |||
| 495d61a11d | |||
| 90fdba8000 | |||
| aa1cada79b | |||
| 790b385849 | |||
| baa2c51123 | |||
| 1e585e06e6 | |||
| 5624484631 | |||
| 810e00da03 | |||
| eeeee2fa0e | |||
| c50a31a503 | |||
| 7360709795 | |||
| df678d7d69 | |||
| 6739242554 | |||
| a5e5eecf8b | |||
| b0248c20eb | |||
| f129968968 | |||
| c5c87e3fd1 | |||
| 24a74c7b57 | |||
| f0a5a7e816 | |||
| 8462cd13fd | |||
| 576846de32 | |||
| 06071ea035 |
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: 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 |
@@ -17,6 +17,7 @@ import { DocumentSigningDisclosure } from '../general/document-signing/document-
|
||||
|
||||
export type SignFieldSignatureDialogProps = {
|
||||
initialSignature?: string;
|
||||
fullName?: string;
|
||||
typedSignatureEnabled?: boolean;
|
||||
uploadSignatureEnabled?: boolean;
|
||||
drawSignatureEnabled?: boolean;
|
||||
@@ -28,6 +29,7 @@ export const SignFieldSignatureDialog = createCallable<
|
||||
>(
|
||||
({
|
||||
call,
|
||||
fullName,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
@@ -46,6 +48,7 @@ export const SignFieldSignatureDialog = createCallable<
|
||||
</DialogHeader>
|
||||
|
||||
<SignaturePad
|
||||
fullName={fullName}
|
||||
value={localSignature ?? ''}
|
||||
onChange={({ value }) => setLocalSignature(value)}
|
||||
typedSignatureEnabled={typedSignatureEnabled}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -438,6 +438,7 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
className="mt-2"
|
||||
disabled={isThrottled || isSubmitting}
|
||||
disableAnimation
|
||||
fullName={fullName}
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={metadata?.typedSignatureEnabled}
|
||||
|
||||
@@ -455,6 +455,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
className="mt-2"
|
||||
disabled={isThrottled || isSubmitting}
|
||||
disableAnimation
|
||||
fullName={fullName}
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={metadata?.typedSignatureEnabled}
|
||||
|
||||
@@ -319,6 +319,7 @@ export const MultiSignDocumentSigningView = ({
|
||||
className="mt-2"
|
||||
disabled={isSubmitting}
|
||||
disableAnimation
|
||||
fullName={fullName}
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -110,7 +110,7 @@ export const ProfileForm = ({ className }: ProfileFormProps) => {
|
||||
<Label htmlFor="email" className="text-muted-foreground">
|
||||
<Trans>Email</Trans>
|
||||
</Label>
|
||||
<Input id="email" type="email" className="bg-muted mt-2" value={user.email} disabled />
|
||||
<Input id="email" type="email" className="mt-2 bg-muted" value={user.email} disabled />
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
@@ -124,6 +124,7 @@ export const ProfileForm = ({ className }: ProfileFormProps) => {
|
||||
<FormControl>
|
||||
<SignaturePadDialog
|
||||
disabled={isSubmitting}
|
||||
fullName={user.name ?? ''}
|
||||
value={value}
|
||||
onChange={(v) => onChange(v ?? '')}
|
||||
/>
|
||||
|
||||
@@ -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" />}
|
||||
|
||||
@@ -417,6 +417,7 @@ export const DirectTemplateSigningForm = ({
|
||||
<SignaturePadDialog
|
||||
className="mt-2"
|
||||
disabled={isSubmitting}
|
||||
fullName={fullName}
|
||||
value={signature ?? ''}
|
||||
onChange={(value) => setSignature(value)}
|
||||
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
|
||||
@@ -433,7 +434,7 @@ export const DirectTemplateSigningForm = ({
|
||||
|
||||
<div className="mt-4 flex gap-x-4">
|
||||
<Button
|
||||
className="dark:bg-muted dark:hover:bg-muted/80 w-full bg-black/5 hover:bg-black/10"
|
||||
className="w-full bg-black/5 hover:bg-black/10 dark:bg-muted dark:hover:bg-muted/80"
|
||||
size="lg"
|
||||
variant="secondary"
|
||||
disabled={isSubmitting}
|
||||
|
||||
@@ -280,6 +280,7 @@ export const DocumentSigningForm = ({
|
||||
<SignaturePadDialog
|
||||
className="mt-2"
|
||||
disabled={isSubmitting}
|
||||
fullName={fullName}
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
|
||||
|
||||
@@ -56,8 +56,11 @@ export const DocumentSigningSignatureField = ({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [fontSize, setFontSize] = useState(2);
|
||||
|
||||
const { signature: providedSignature, setSignature: setProvidedSignature } =
|
||||
useRequiredDocumentSigningContext();
|
||||
const {
|
||||
fullName,
|
||||
signature: providedSignature,
|
||||
setSignature: setProvidedSignature,
|
||||
} = useRequiredDocumentSigningContext();
|
||||
|
||||
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
||||
|
||||
@@ -236,13 +239,13 @@ export const DocumentSigningSignatureField = ({
|
||||
type="Signature"
|
||||
>
|
||||
{isLoading && (
|
||||
<div className="bg-background absolute inset-0 flex items-center justify-center rounded-md">
|
||||
<Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" />
|
||||
<div className="absolute inset-0 flex items-center justify-center rounded-md bg-background">
|
||||
<Loader className="h-5 w-5 animate-spin text-primary md:h-8 md:w-8" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{state === 'empty' && (
|
||||
<p className="group-hover:text-primary font-signature text-muted-foreground group-hover:text-recipient-green text-[clamp(0.575rem,25cqw,1.2rem)] text-xl duration-200">
|
||||
<p className="font-signature text-[clamp(0.575rem,25cqw,1.2rem)] text-xl text-muted-foreground duration-200 group-hover:text-primary group-hover:text-recipient-green">
|
||||
<Trans>Signature</Trans>
|
||||
</p>
|
||||
)}
|
||||
@@ -259,7 +262,7 @@ export const DocumentSigningSignatureField = ({
|
||||
<div ref={containerRef} className="flex h-full w-full items-center justify-center p-2">
|
||||
<p
|
||||
ref={signatureRef}
|
||||
className="font-signature text-muted-foreground w-full overflow-hidden break-all text-center leading-tight duration-200"
|
||||
className="w-full overflow-hidden break-all text-center font-signature leading-tight text-muted-foreground duration-200"
|
||||
style={{ fontSize: `${fontSize}rem` }}
|
||||
>
|
||||
{signature?.typedSignature}
|
||||
@@ -272,12 +275,13 @@ export const DocumentSigningSignatureField = ({
|
||||
<DialogTitle>
|
||||
<Trans>
|
||||
Sign as {recipient.name}{' '}
|
||||
<div className="text-muted-foreground h-5">({recipient.email})</div>
|
||||
<div className="h-5 text-muted-foreground">({recipient.email})</div>
|
||||
</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<SignaturePad
|
||||
className="mt-2"
|
||||
fullName={fullName}
|
||||
value={localSignature ?? ''}
|
||||
onChange={({ value }) => setLocalSignature(value)}
|
||||
typedSignatureEnabled={typedSignatureEnabled}
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function EnvelopeSignerForm() {
|
||||
|
||||
if (recipient.role === RecipientRole.ASSISTANT) {
|
||||
return (
|
||||
<fieldset className="embed--DocumentWidgetForm dark:bg-background border-border rounded-2xl sm:border sm:p-3">
|
||||
<fieldset className="embed--DocumentWidgetForm rounded-2xl border-border sm:border sm:p-3 dark:bg-background">
|
||||
<RadioGroup
|
||||
className="gap-0 space-y-2 shadow-none sm:space-y-3"
|
||||
value={selectedAssistantRecipient?.id?.toString()}
|
||||
@@ -54,7 +54,7 @@ export default function EnvelopeSignerForm() {
|
||||
.map((r) => (
|
||||
<div
|
||||
key={r.id}
|
||||
className="bg-widget border-border relative flex flex-col gap-4 rounded-lg border p-4"
|
||||
className="relative flex flex-col gap-4 rounded-lg border border-border bg-widget p-4"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -69,15 +69,15 @@ export default function EnvelopeSignerForm() {
|
||||
{r.name}
|
||||
|
||||
{r.id === recipient.id && (
|
||||
<span className="text-muted-foreground ml-2">
|
||||
<span className="ml-2 text-muted-foreground">
|
||||
<Trans>(You)</Trans>
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<p className="text-muted-foreground text-xs">{r.email}</p>
|
||||
<p className="text-xs text-muted-foreground">{r.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-muted-foreground text-xs leading-[inherit]">
|
||||
<div className="text-xs leading-[inherit] text-muted-foreground">
|
||||
<Plural
|
||||
value={assistantFields.filter((field) => field.recipientId === r.id).length}
|
||||
one="# field"
|
||||
@@ -103,7 +103,7 @@ export default function EnvelopeSignerForm() {
|
||||
<Input
|
||||
type="text"
|
||||
id="full-name"
|
||||
className="bg-background mt-2"
|
||||
className="mt-2 bg-background"
|
||||
value={fullName}
|
||||
disabled={isNameLocked}
|
||||
onChange={(e) => !isNameLocked && setFullName(e.target.value.trimStart())}
|
||||
@@ -119,6 +119,7 @@ export default function EnvelopeSignerForm() {
|
||||
<SignaturePadDialog
|
||||
className="mt-2"
|
||||
disabled={isSubmitting}
|
||||
fullName={fullName}
|
||||
value={signature ?? ''}
|
||||
onChange={(v) => setSignature(v ?? '')}
|
||||
typedSignatureEnabled={envelope.documentMeta.typedSignatureEnabled}
|
||||
|
||||
@@ -374,6 +374,7 @@ export default function EnvelopeSignerPageRenderer() {
|
||||
.with({ type: FieldType.SIGNATURE }, (field) => {
|
||||
handleSignatureFieldClick({
|
||||
field,
|
||||
fullName,
|
||||
signature,
|
||||
typedSignatureEnabled: envelope.documentMeta.typedSignatureEnabled,
|
||||
uploadSignatureEnabled: envelope.documentMeta.uploadSignatureEnabled,
|
||||
|
||||
@@ -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(_, row.original, userId).description}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: _(msg`IP Address`),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -120,7 +120,9 @@ export default function OrganisationSettingsTeamsPage() {
|
||||
</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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -185,6 +185,9 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
|
||||
(log) =>
|
||||
log.type === DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT && log.data.recipientId === recipientId,
|
||||
),
|
||||
[DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT]: auditLogs[
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT
|
||||
].filter((log) => log.type === DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT),
|
||||
[DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED]: auditLogs[
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED
|
||||
].filter(
|
||||
@@ -245,11 +248,11 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
|
||||
<TableCell truncate={false} className="w-[min-content] max-w-[220px] align-top">
|
||||
<div className="hyphens-auto break-words font-medium">{recipient.name}</div>
|
||||
<div className="break-all">{recipient.email}</div>
|
||||
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
|
||||
<p className="mt-2 text-sm text-muted-foreground print:text-xs">
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
|
||||
</p>
|
||||
|
||||
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
|
||||
<p className="mt-2 text-sm text-muted-foreground print:text-xs">
|
||||
<span className="font-medium">{_(msg`Authentication Level`)}:</span>{' '}
|
||||
<span className="block">{getAuthenticationLevel(recipient.id)}</span>
|
||||
</p>
|
||||
@@ -273,13 +276,13 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
|
||||
)}
|
||||
|
||||
{signature.signature?.typedSignature && (
|
||||
<p className="font-signature text-center text-sm">
|
||||
<p className="text-center font-signature text-sm">
|
||||
{signature.signature?.typedSignature}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
|
||||
<p className="mt-2 text-sm text-muted-foreground print:text-xs">
|
||||
<span className="font-medium">{_(msg`Signature ID`)}:</span>{' '}
|
||||
<span className="block font-mono uppercase">
|
||||
{signature.secondaryId}
|
||||
@@ -290,14 +293,14 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
|
||||
<p className="text-muted-foreground">N/A</p>
|
||||
)}
|
||||
|
||||
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
|
||||
<p className="mt-2 text-sm text-muted-foreground print:text-xs">
|
||||
<span className="font-medium">{_(msg`IP Address`)}:</span>{' '}
|
||||
<span className="inline-block">
|
||||
{logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.ipAddress ?? _(msg`Unknown`)}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p className="text-muted-foreground mt-1 text-sm print:text-xs">
|
||||
<p className="mt-1 text-sm text-muted-foreground print:text-xs">
|
||||
<span className="font-medium">{_(msg`Device`)}:</span>{' '}
|
||||
<span className="inline-block">
|
||||
{getDevice(logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.userAgent)}
|
||||
@@ -307,18 +310,22 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
|
||||
|
||||
<TableCell truncate={false} className="w-[min-content] align-top">
|
||||
<div className="space-y-1">
|
||||
<p className="text-muted-foreground text-sm print:text-xs">
|
||||
<p className="text-sm text-muted-foreground print:text-xs">
|
||||
<span className="font-medium">{_(msg`Sent`)}:</span>{' '}
|
||||
<span className="inline-block">
|
||||
{logs.EMAIL_SENT[0]
|
||||
? DateTime.fromJSDate(logs.EMAIL_SENT[0].createdAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
|
||||
: _(msg`Unknown`)}
|
||||
: logs.DOCUMENT_SENT[0]
|
||||
? DateTime.fromJSDate(logs.DOCUMENT_SENT[0].createdAt)
|
||||
.setLocale(APP_I18N_OPTIONS.defaultLocale)
|
||||
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
|
||||
: _(msg`Unknown`)}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p className="text-muted-foreground text-sm print:text-xs">
|
||||
<p className="text-sm text-muted-foreground print:text-xs">
|
||||
<span className="font-medium">{_(msg`Viewed`)}:</span>{' '}
|
||||
<span className="inline-block">
|
||||
{logs.DOCUMENT_OPENED[0]
|
||||
@@ -330,7 +337,7 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
|
||||
</p>
|
||||
|
||||
{logs.DOCUMENT_RECIPIENT_REJECTED[0] ? (
|
||||
<p className="text-muted-foreground text-sm print:text-xs">
|
||||
<p className="text-sm text-muted-foreground print:text-xs">
|
||||
<span className="font-medium">{_(msg`Rejected`)}:</span>{' '}
|
||||
<span className="inline-block">
|
||||
{logs.DOCUMENT_RECIPIENT_REJECTED[0]
|
||||
@@ -341,7 +348,7 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
|
||||
</span>
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-muted-foreground text-sm print:text-xs">
|
||||
<p className="text-sm text-muted-foreground print:text-xs">
|
||||
<span className="font-medium">{_(msg`Signed`)}:</span>{' '}
|
||||
<span className="inline-block">
|
||||
{logs.DOCUMENT_RECIPIENT_COMPLETED[0]
|
||||
@@ -355,7 +362,7 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
|
||||
</p>
|
||||
)}
|
||||
|
||||
<p className="text-muted-foreground text-sm print:text-xs">
|
||||
<p className="text-sm text-muted-foreground print:text-xs">
|
||||
<span className="font-medium">{_(msg`Reason`)}:</span>{' '}
|
||||
<span className="inline-block">
|
||||
{recipient.signingStatus === SigningStatus.REJECTED
|
||||
|
||||
@@ -115,7 +115,9 @@ export default function RejectedSigningPage({ loaderData }: Route.ComponentProps
|
||||
|
||||
{user && (
|
||||
<Button className="mt-6" asChild>
|
||||
<Link to={`/`}>Return Home</Link>
|
||||
<Link to={`/`}>
|
||||
<Trans>Return Home</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { SignFieldSignatureDialog } from '~/components/dialogs/sign-field-signat
|
||||
|
||||
type HandleSignatureFieldClickOptions = {
|
||||
field: TFieldSignature;
|
||||
fullName?: string;
|
||||
signature: string | null;
|
||||
typedSignatureEnabled?: boolean;
|
||||
uploadSignatureEnabled?: boolean;
|
||||
@@ -17,8 +18,14 @@ type HandleSignatureFieldClickOptions = {
|
||||
export const handleSignatureFieldClick = async (
|
||||
options: HandleSignatureFieldClickOptions,
|
||||
): Promise<Extract<TSignEnvelopeFieldValue, { type: typeof FieldType.SIGNATURE }> | null> => {
|
||||
const { field, signature, typedSignatureEnabled, uploadSignatureEnabled, drawSignatureEnabled } =
|
||||
options;
|
||||
const {
|
||||
field,
|
||||
fullName,
|
||||
signature,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
} = options;
|
||||
|
||||
if (field.type !== FieldType.SIGNATURE) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
@@ -37,6 +44,7 @@ export const handleSignatureFieldClick = async (
|
||||
|
||||
if (!signatureToInsert) {
|
||||
signatureToInsert = await SignFieldSignatureDialog.call({
|
||||
fullName,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
|
||||
@@ -107,5 +107,5 @@
|
||||
"vite-plugin-babel-macros": "^1.0.6",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"version": "2.2.6"
|
||||
"version": "2.3.2"
|
||||
}
|
||||
|
||||
@@ -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.6",
|
||||
"version": "2.3.2",
|
||||
"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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
@@ -44,10 +47,12 @@ export type PdfToImagesOptions = {
|
||||
export const pdfToImages = async (pdfBytes: Uint8Array, options: PdfToImagesOptions = {}) => {
|
||||
const { scale = 2 } = options;
|
||||
|
||||
const pdf = await pdfjsLib.getDocument({
|
||||
const task = await pdfjsLib.getDocument({
|
||||
data: pdfBytes,
|
||||
CanvasFactory: SkiaCanvasFactory,
|
||||
}).promise;
|
||||
});
|
||||
|
||||
const pdf = await task.promise;
|
||||
|
||||
const images = await pMap(
|
||||
Array.from({ length: pdf.numPages }),
|
||||
@@ -58,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({
|
||||
@@ -68,18 +75,23 @@ export const pdfToImages = async (pdfBytes: Uint8Array, options: PdfToImagesOpti
|
||||
viewport,
|
||||
}).promise;
|
||||
|
||||
return {
|
||||
const result = {
|
||||
pageNumber,
|
||||
image: await canvas.toBuffer('jpeg'),
|
||||
width: Math.floor(viewport.width),
|
||||
height: Math.floor(viewport.height),
|
||||
mimeType: 'image/jpeg',
|
||||
};
|
||||
|
||||
void page.cleanup();
|
||||
|
||||
return result;
|
||||
},
|
||||
{ concurrency: 10 },
|
||||
);
|
||||
|
||||
void pdf.destroy();
|
||||
void pdf.destroy().catch((e) => console.error(e));
|
||||
void task.destroy().catch((e) => console.error(e));
|
||||
|
||||
return images;
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ export const getDocumentCertificateAuditLogs = async ({
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED,
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
|
||||
DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
||||
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT,
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -37,6 +38,9 @@ export const getDocumentCertificateAuditLogs = async ({
|
||||
[DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED]: auditLogs.filter(
|
||||
(log) => log.type === DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
|
||||
),
|
||||
[DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT]: auditLogs.filter(
|
||||
(log) => log.type === DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT,
|
||||
),
|
||||
[DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED]: auditLogs.filter(
|
||||
(log) => log.type === DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED,
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -185,7 +185,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
documentAuth: directTemplateEnvelope.authOptions,
|
||||
});
|
||||
|
||||
const directRecipientName = user?.name || initialDirectRecipientName;
|
||||
let directRecipientName = user?.name || initialDirectRecipientName;
|
||||
|
||||
// Ensure typesafety when we add more options.
|
||||
const isAccessAuthValid = match(derivedRecipientAccessAuth.at(0))
|
||||
@@ -238,7 +238,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
}
|
||||
|
||||
if (templateField.type === FieldType.NAME && directRecipientName === undefined) {
|
||||
directRecipientName === signedFieldValue?.value;
|
||||
directRecipientName = signedFieldValue?.value;
|
||||
}
|
||||
|
||||
const derivedRecipientActionAuth = await validateFieldAuth({
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-11-27 05:32\n"
|
||||
"PO-Revision-Date: 2025-12-15 02:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@@ -68,7 +68,7 @@ msgstr "{0, plural, one {(1 Zeichen über dem Limit)} other {(# Zeichen über de
|
||||
#. placeholder {2}: table.getFilteredSelectedRowModel().rows.length
|
||||
#: packages/ui/primitives/data-table-pagination.tsx
|
||||
msgid "{0, plural, one {{1} of # row selected.} other {{2} of # rows selected.}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {{1} von # Zeile ausgewählt.} other {{2} von # Zeilen ausgewählt.}}"
|
||||
|
||||
#. placeholder {0}: Math.abs(remaningLength)
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
@@ -104,7 +104,7 @@ msgstr "{0, plural, one {# Ordner} other {# Ordner}}"
|
||||
#. placeholder {0}: detectedRecipients.length
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
|
||||
msgid "{0, plural, one {# recipient have been added from AI detection.} other {# recipients have been added from AI detection.}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {# Empfänger wurde durch die KI-Erkennung hinzugefügt.} other {# Empfänger wurden durch die KI-Erkennung hinzugefügt.}}"
|
||||
|
||||
#. placeholder {0}: template.recipients.length
|
||||
#: apps/remix/app/routes/_recipient+/d.$token+/_index.tsx
|
||||
@@ -163,7 +163,7 @@ msgstr "{0, plural, one {1 Empfänger} other {# Empfänger}}"
|
||||
#. placeholder {4}: progress.totalPages
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "{0, plural, one {Page {1} of {2} - # field found} other {Page {3} of {4} - # fields found}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Seite {1} von {2} - # Feld gefunden} other {Seite {3} von {4} - # Felder gefunden}}"
|
||||
|
||||
#. placeholder {0}: progress.recipientsDetected
|
||||
#. placeholder {1}: progress.pagesProcessed
|
||||
@@ -172,12 +172,12 @@ msgstr ""
|
||||
#. placeholder {4}: progress.totalPages
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "{0, plural, one {Page {1} of {2} - # recipient found} other {Page {3} of {4} - # recipients found}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Seite {1} von {2} - # Empfänger gefunden} other {Seite {3} von {4} - # Empfänger gefunden}}"
|
||||
|
||||
#. placeholder {0}: detectedRecipients.length
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
|
||||
msgid "{0, plural, one {Recipient added} other {Recipients added}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Empfänger hinzugefügt} other {Empfänger hinzugefügt}}"
|
||||
|
||||
#. placeholder {0}: pendingRecipients.length
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
@@ -187,12 +187,12 @@ msgstr "{0, plural, one {Warte auf 1 Empfänger} other {Warte auf # Empfänger}}
|
||||
#. placeholder {0}: detectedFields.length
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "{0, plural, one {We found # field in your document.} other {We found # fields in your document.}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Wir haben # Feld in Ihrem Dokument gefunden.} other {Wir haben # Felder in Ihrem Dokument gefunden.}}"
|
||||
|
||||
#. placeholder {0}: detectedRecipients.length
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "{0, plural, one {We found # recipient in your document.} other {We found # recipients in your document.}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Wir haben # Empfänger in Ihrem Dokument gefunden.} other {Wir haben # Empfänger in Ihrem Dokument gefunden.}}"
|
||||
|
||||
#. placeholder {0}: _(FRIENDLY_FIELD_TYPE[fieldType as FieldType])
|
||||
#. placeholder {0}: route.label
|
||||
@@ -312,7 +312,7 @@ msgstr "{MAXIMUM_PASSKEYS, plural, one {Sie können nicht mehr als # Zugangsschl
|
||||
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
|
||||
#: apps/remix/app/components/general/envelope/envelope-upload-button.tsx
|
||||
msgid "{maximumEnvelopeItemCount, plural, one {You cannot upload more than # item per envelope.} other {You cannot upload more than # items per envelope.}}"
|
||||
msgstr ""
|
||||
msgstr "{maximumEnvelopeItemCount, plural, one {Sie können nicht mehr als # Element pro Umschlag hochladen.} other {Sie können nicht mehr als # Elemente pro Umschlag hochladen.}}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} added a field"
|
||||
@@ -547,39 +547,39 @@ msgstr "<0>{organisationName}</0> hat angefragt, Ihr bestehendes Documenso-Konto
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> has invited you to approve this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> hat Sie eingeladen, dieses Dokument zu genehmigen"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> has invited you to assist this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> hat Sie eingeladen, bei diesem Dokument mitzuwirken"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> has invited you to sign this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> hat Sie eingeladen, dieses Dokument zu unterschreiben"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> has invited you to view this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> hat Sie eingeladen, dieses Dokument anzusehen"
|
||||
|
||||
#. placeholder {0}: document.team?.name
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> on behalf of \"{0}\" has invited you to approve this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> im Namen von \"{0}\" hat Sie eingeladen, dieses Dokument zu genehmigen"
|
||||
|
||||
#. placeholder {0}: document.team?.name
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> on behalf of \"{0}\" has invited you to assist this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> im Namen von \"{0}\" hat Sie eingeladen, bei diesem Dokument mitzuwirken"
|
||||
|
||||
#. placeholder {0}: document.team?.name
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> on behalf of \"{0}\" has invited you to sign this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> im Namen von \"{0}\" hat Sie eingeladen, dieses Dokument zu unterschreiben"
|
||||
|
||||
#. placeholder {0}: document.team?.name
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> on behalf of \"{0}\" has invited you to view this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> im Namen von \"{0}\" hat Sie eingeladen, dieses Dokument anzusehen"
|
||||
|
||||
#: packages/email/templates/confirm-team-email.tsx
|
||||
msgid "<0>{teamName}</0> has requested to use your email address for their team on Documenso."
|
||||
@@ -591,7 +591,7 @@ msgstr "<0>Konto verwalten:</0> Ändern Sie Ihre Kontoeinstellungen, Berechtigun
|
||||
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "<0>Admins only</0> - Only admins can access and view the document"
|
||||
msgstr ""
|
||||
msgstr "<0>Nur Admins</0> – Nur Admins können auf das Dokument zugreifen und es ansehen"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "<0>Data access:</0> Access all data associated with your account"
|
||||
@@ -612,7 +612,7 @@ msgstr "<0>Ereignisse:</0> Alle"
|
||||
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "<0>Everyone</0> - Everyone can access and view the document"
|
||||
msgstr ""
|
||||
msgstr "<0>Jede Person</0> – Jede Person kann auf das Dokument zugreifen und es ansehen"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "<0>Full account access:</0> View all your profile information, settings, and activity"
|
||||
@@ -624,7 +624,7 @@ msgstr "<0>Authentifizierungsmethode erben</0> - Verwenden Sie die in den \"Allg
|
||||
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "<0>Managers and above</0> - Only managers and above can access and view the document"
|
||||
msgstr ""
|
||||
msgstr "<0>Manager und höher</0> – Nur Manager und Personen darüber können auf das Dokument zugreifen und es ansehen."
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx
|
||||
msgid "<0>No restrictions</0> - No authentication required"
|
||||
@@ -981,11 +981,11 @@ msgstr "Konto aktiviert"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "Account link declined"
|
||||
msgstr ""
|
||||
msgstr "Kontoverknüpfung abgelehnt"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "Account linked successfully"
|
||||
msgstr ""
|
||||
msgstr "Konto erfolgreich verknüpft"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "Account Linking Request"
|
||||
@@ -1047,7 +1047,7 @@ msgstr "Aktiv"
|
||||
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
|
||||
msgctxt "Subscription status"
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
msgstr "Aktiv"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
@@ -1144,7 +1144,7 @@ msgstr "E-Mail-Domain hinzufügen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Add fields"
|
||||
msgstr ""
|
||||
msgstr "Felder hinzufügen"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-edit-form.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
|
||||
@@ -1212,7 +1212,7 @@ msgstr "Platzhalter hinzufügen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Add recipients"
|
||||
msgstr ""
|
||||
msgstr "Empfänger hinzufügen"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
|
||||
msgid "Add Recipients"
|
||||
@@ -1316,11 +1316,11 @@ msgstr "Nach der Übermittlung wird ein Dokument automatisch generiert und zu Ih
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "AI Features"
|
||||
msgstr ""
|
||||
msgstr "KI‑Funktionen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
msgid "AI features are disabled for your team. Please ask your team owner or organisation owner to enable them."
|
||||
msgstr ""
|
||||
msgstr "KI-Funktionen sind für Ihr Team deaktiviert. Bitte bitten Sie den Teambesitzer oder den Organisationsbesitzer, sie zu aktivieren."
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
@@ -1418,7 +1418,7 @@ msgstr "Erlaubt die Authentifizierung mit biometrischen Daten, Passwort-Managern
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Almost done"
|
||||
msgstr ""
|
||||
msgstr "Fast fertig"
|
||||
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
msgid "Already have an account? <0>Sign in instead</0>"
|
||||
@@ -1512,7 +1512,7 @@ msgstr "Beim automatischen Signieren des Dokuments ist ein Fehler aufgetreten, e
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx
|
||||
msgid "An error occurred while completing the document. Please try again."
|
||||
msgstr ""
|
||||
msgstr "Beim Abschließen des Dokuments ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "An error occurred while creating document from template."
|
||||
@@ -1560,7 +1560,7 @@ msgstr "Ein Fehler ist aufgetreten, während die Vorlage verschoben wurde."
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-reject-dialog.tsx
|
||||
msgid "An error occurred while rejecting the document. Please try again."
|
||||
msgstr ""
|
||||
msgstr "Beim Ablehnen des Dokuments ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut."
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-checkbox-field.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-date-field.tsx
|
||||
@@ -1726,11 +1726,11 @@ msgstr "Ein unbekannter Fehler ist beim Verschieben des Ordners aufgetreten."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Analyzing page layout"
|
||||
msgstr ""
|
||||
msgstr "Seitenlayout wird analysiert"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Analyzing pages"
|
||||
msgstr ""
|
||||
msgstr "Seiten werden analysiert"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/inbox.tsx
|
||||
msgid "Any documents that you have been invited to will appear here"
|
||||
@@ -2301,7 +2301,7 @@ msgstr "Kontrollkästchen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-content.tsx
|
||||
msgid "Checkbox option"
|
||||
msgstr ""
|
||||
msgstr "Checkbox-Option"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
|
||||
msgid "Checkbox Settings"
|
||||
@@ -2624,7 +2624,7 @@ msgstr "Inhalt"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Context"
|
||||
msgstr ""
|
||||
msgstr "Kontext"
|
||||
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx
|
||||
@@ -2707,13 +2707,13 @@ msgstr "Kopiert"
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx
|
||||
msgid "Copied field"
|
||||
msgstr ""
|
||||
msgstr "Kopiertes Feld"
|
||||
|
||||
#: apps/remix/app/components/embed/authoring/configure-fields-view.tsx
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx
|
||||
msgid "Copied field to clipboard"
|
||||
msgstr ""
|
||||
msgstr "Feld in die Zwischenablage kopiert"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-records-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-records-dialog.tsx
|
||||
@@ -3060,7 +3060,7 @@ msgstr "Datumseinstellungen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "David is the Employee, Lucas is the Manager"
|
||||
msgstr ""
|
||||
msgstr "David ist der Mitarbeiter, Lucas ist der Manager"
|
||||
|
||||
#: apps/remix/app/components/general/organisations/organisation-invitations.tsx
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
@@ -3109,6 +3109,10 @@ msgstr "Standard-Zeitzone"
|
||||
msgid "Default Value"
|
||||
msgstr "Standardwert"
|
||||
|
||||
#: 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 "löschen"
|
||||
@@ -3274,48 +3278,48 @@ msgstr "Einzelheiten"
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Detect"
|
||||
msgstr ""
|
||||
msgstr "Erkennen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Detect fields"
|
||||
msgstr ""
|
||||
msgstr "Felder erkennen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Detect recipients"
|
||||
msgstr ""
|
||||
msgstr "Empfänger erkennen"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
|
||||
msgid "Detect recipients with AI"
|
||||
msgstr ""
|
||||
msgstr "Empfänger mit KI erkennen"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
|
||||
msgid "Detect with AI"
|
||||
msgstr ""
|
||||
msgstr "Mit KI erkennen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Detected fields"
|
||||
msgstr ""
|
||||
msgstr "Erkannte Felder"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Detected recipients"
|
||||
msgstr ""
|
||||
msgstr "Erkannte Empfänger"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Detecting fields"
|
||||
msgstr ""
|
||||
msgstr "Felder werden erkannt"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Detecting recipients"
|
||||
msgstr ""
|
||||
msgstr "Empfänger werden erkannt"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Detecting signature areas"
|
||||
msgstr ""
|
||||
msgstr "Signaturbereiche werden erkannt"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Detection failed"
|
||||
msgstr ""
|
||||
msgstr "Erkennung fehlgeschlagen"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
|
||||
msgid "Developer Mode"
|
||||
@@ -3329,7 +3333,7 @@ msgstr "Gerät"
|
||||
|
||||
#: packages/email/templates/reset-password.tsx
|
||||
msgid "Didn't request a password change? We are here to help you secure your account, just <0>contact us</0>."
|
||||
msgstr ""
|
||||
msgstr "Du hast keine Passwortänderung angefordert? Wir helfen dir, dein Konto zu sichern, kontaktiere uns einfach <0>hier</0>."
|
||||
|
||||
#: apps/remix/app/components/general/template/template-direct-link-badge.tsx
|
||||
#: apps/remix/app/components/tables/templates-table.tsx
|
||||
@@ -3672,6 +3676,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 ""
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
msgid "Document pending"
|
||||
msgstr "Dokument ausstehend"
|
||||
@@ -3873,7 +3882,7 @@ msgstr "Haben Sie kein Konto? <0>Registrieren</0>"
|
||||
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
|
||||
msgid "Don't transfer (Delete all documents)"
|
||||
msgstr ""
|
||||
msgstr "Nicht übertragen (alle Dokumente löschen)"
|
||||
|
||||
#: apps/remix/app/components/forms/2fa/enable-authenticator-app-dialog.tsx
|
||||
#: apps/remix/app/components/forms/2fa/view-recovery-codes-dialog.tsx
|
||||
@@ -4013,6 +4022,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
|
||||
@@ -4231,16 +4241,16 @@ msgstr "Konto Aktivieren"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
|
||||
msgid "Enable AI detection"
|
||||
msgstr ""
|
||||
msgstr "KI-Erkennung aktivieren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
msgid "Enable AI features"
|
||||
msgstr ""
|
||||
msgstr "KI-Funktionen aktivieren"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Enable AI-powered features such as automatic recipient detection. When enabled, document content will be sent to AI providers. We only use providers that do not retain data for training and prefer European regions where available."
|
||||
msgstr ""
|
||||
msgstr "Aktivieren Sie KI‑gestützte Funktionen wie die automatische Empfängererkennung. Wenn diese Option aktiviert ist, werden die Dokumentinhalte an KI‑Anbieter gesendet. Wir verwenden nur Anbieter, die Daten nicht zu Trainingszwecken speichern, und bevorzugen – sofern verfügbar – Standorte in Europa."
|
||||
|
||||
#: apps/remix/app/components/forms/2fa/enable-authenticator-app-dialog.tsx
|
||||
msgid "Enable Authenticator App"
|
||||
@@ -4278,6 +4288,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 ""
|
||||
|
||||
#: 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
|
||||
@@ -4476,11 +4490,11 @@ msgstr "Fehler"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "Error declining account link"
|
||||
msgstr ""
|
||||
msgstr "Fehler beim Ablehnen der Kontoverknüpfung"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "Error linking account"
|
||||
msgstr ""
|
||||
msgstr "Fehler beim Verknüpfen des Kontos"
|
||||
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-upload.tsx
|
||||
msgid "Error uploading file"
|
||||
@@ -4538,7 +4552,7 @@ msgstr "Externe ID"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Extracting contact details"
|
||||
msgstr ""
|
||||
msgstr "Kontaktdaten werden extrahiert"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
@@ -4547,7 +4561,7 @@ msgstr "Fehlgeschlagen"
|
||||
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
msgid "Failed to complete the document. Please try again."
|
||||
msgstr ""
|
||||
msgstr "Das Dokument konnte nicht abgeschlossen werden. Bitte versuchen Sie es erneut."
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Failed to create folder"
|
||||
@@ -4619,7 +4633,7 @@ msgstr "Webhook konnte nicht aktualisiert werden"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx
|
||||
msgid "Failed to upload CSV. Please check the file format and try again."
|
||||
msgstr ""
|
||||
msgstr "CSV konnte nicht hochgeladen werden. Bitte überprüfen Sie das Dateiformat und versuchen Sie es erneut."
|
||||
|
||||
#: packages/email/templates/bulk-send-complete.tsx
|
||||
msgid "Failed: {failedCount}"
|
||||
@@ -4779,13 +4793,13 @@ msgstr "Hast du dein Passwort vergessen?"
|
||||
#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx
|
||||
msgctxt "Plan price"
|
||||
msgid "Free"
|
||||
msgstr ""
|
||||
msgstr "Kostenlos"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
|
||||
msgctxt "Subscription status"
|
||||
msgid "Free"
|
||||
msgstr ""
|
||||
msgstr "Kostenlos"
|
||||
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/types.ts
|
||||
@@ -4878,15 +4892,15 @@ msgstr "Zum Dokument gehen"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx
|
||||
msgid "Go to first page"
|
||||
msgstr ""
|
||||
msgstr "Zur ersten Seite gehen"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx
|
||||
msgid "Go to last page"
|
||||
msgstr ""
|
||||
msgstr "Zur letzten Seite gehen"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx
|
||||
msgid "Go to next page"
|
||||
msgstr ""
|
||||
msgstr "Zur nächsten Seite gehen"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
msgid "Go to owner"
|
||||
@@ -4894,7 +4908,7 @@ msgstr "Zum Eigentümer gehen"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx
|
||||
msgid "Go to previous page"
|
||||
msgstr ""
|
||||
msgstr "Zur vorherigen Seite gehen"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Go to team"
|
||||
@@ -4953,7 +4967,7 @@ msgstr "Hilfe beim Abschließen des Dokuments für andere Unterzeichner."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Help the AI assign fields to the right recipients."
|
||||
msgstr ""
|
||||
msgstr "Helfen Sie der KI, die Felder den richtigen Empfängern zuzuordnen."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email-domains._index.tsx
|
||||
msgid "Here you can add email domains to your organisation."
|
||||
@@ -4977,7 +4991,7 @@ msgstr "Hier können Sie Branding-Präferenzen für Ihre Organisation festlegen.
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
|
||||
msgid "Here you can set branding preferences for your team."
|
||||
msgstr ""
|
||||
msgstr "Hier kannst du die Branding-Einstellungen für dein Team festlegen."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.document.tsx
|
||||
msgid "Here you can set document preferences for your organisation. Teams will inherit these settings by default."
|
||||
@@ -4993,11 +5007,11 @@ msgstr "Hier können Sie Präferenzen und Voreinstellungen für Ihr Team festleg
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
|
||||
msgid "Here you can set your general branding preferences."
|
||||
msgstr ""
|
||||
msgstr "Hier kannst du deine allgemeinen Branding-Einstellungen festlegen."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.document.tsx
|
||||
msgid "Here you can set your general document preferences."
|
||||
msgstr ""
|
||||
msgstr "Hier kannst du deine allgemeinen Dokumenteinstellungen festlegen."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Here's how it works:"
|
||||
@@ -5090,11 +5104,11 @@ msgstr "ID in die Zwischenablage kopiert"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Identifying input fields"
|
||||
msgstr ""
|
||||
msgstr "Eingabefelder werden identifiziert"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Identifying recipients"
|
||||
msgstr ""
|
||||
msgstr "Empfänger werden identifiziert"
|
||||
|
||||
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
|
||||
msgid "If there is any issue with your subscription, please contact us at <0>{SUPPORT_EMAIL}</0>."
|
||||
@@ -5123,7 +5137,7 @@ msgstr "Wichtig: Was dies bedeutet"
|
||||
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
|
||||
msgctxt "Subscription status"
|
||||
msgid "Inactive"
|
||||
msgstr ""
|
||||
msgstr "Inaktiv"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
@@ -5162,6 +5176,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"
|
||||
@@ -5547,13 +5562,14 @@ msgstr "Protokolle"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Looking for form fields"
|
||||
msgstr ""
|
||||
msgstr "Formularfelder werden gesucht"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Looking for signature fields"
|
||||
msgstr ""
|
||||
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 +5630,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 ""
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx
|
||||
msgid "Manage organisations"
|
||||
msgstr "Organisationen verwalten"
|
||||
@@ -5706,7 +5726,7 @@ msgstr "Manager und höher"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Mapping fields to recipients"
|
||||
msgstr ""
|
||||
msgstr "Felder werden Empfängern zugeordnet"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
|
||||
msgid "Mark as viewed"
|
||||
@@ -5960,6 +5980,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"
|
||||
|
||||
@@ -5975,11 +5996,11 @@ msgstr "Keine Dokumente gefunden"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "No email detected"
|
||||
msgstr ""
|
||||
msgstr "Keine E‑Mail erkannt"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "No fields were detected in your document."
|
||||
msgstr ""
|
||||
msgstr "In Ihrem Dokument wurden keine Felder erkannt."
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
|
||||
@@ -6027,7 +6048,7 @@ msgstr "Keine Empfänger"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "No recipients were detected in your document."
|
||||
msgstr ""
|
||||
msgstr "In Ihrem Dokument wurden keine Empfänger erkannt."
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-recipient-selector.tsx
|
||||
#: packages/ui/primitives/recipient-selector.tsx
|
||||
@@ -6168,7 +6189,7 @@ msgstr "Sobald Sie den QR-Code gescannt oder den Code manuell eingegeben haben,
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-records-dialog.tsx
|
||||
msgid "Once you update your DNS records, it may take up to 48 hours for it to be propogated. Once the DNS propagation is complete you will need to come back and press the \"Sync\" domains button."
|
||||
msgstr ""
|
||||
msgstr "Nachdem du deine DNS-Einträge aktualisiert hast, kann es bis zu 48 Stunden dauern, bis sie verbreitet sind. Sobald die DNS-Propagation abgeschlossen ist, musst du zurückkehren und die Schaltfläche \"Domains synchronisieren\" drücken."
|
||||
|
||||
#: packages/lib/constants/template.ts
|
||||
msgid "Once your template is set up, share the link anywhere you want. The person who opens the link will be able to enter their information in the direct link recipient field and complete any other fields assigned to them."
|
||||
@@ -6204,7 +6225,7 @@ msgstr "Hoppla! Etwas ist schief gelaufen."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Open menu"
|
||||
msgstr ""
|
||||
msgstr "Menü öffnen"
|
||||
|
||||
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
|
||||
msgid "Opened"
|
||||
@@ -6408,7 +6429,7 @@ msgstr "Seite {0} von {numPages}"
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
msgctxt "Subscription status"
|
||||
msgid "Paid"
|
||||
msgstr ""
|
||||
msgstr "Bezahlt"
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
@@ -6490,7 +6511,7 @@ msgstr "Passwort aktualisiert!"
|
||||
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
|
||||
msgctxt "Subscription status"
|
||||
msgid "Past Due"
|
||||
msgstr ""
|
||||
msgstr "Überfällig"
|
||||
|
||||
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
|
||||
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
|
||||
@@ -6925,7 +6946,7 @@ msgstr "Lesen Sie die vollständige <0>Offenlegung der Unterschrift</0>."
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Reading your document"
|
||||
msgstr ""
|
||||
msgstr "Ihr Dokument wird gelesen"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
msgid "Ready"
|
||||
@@ -6988,7 +7009,7 @@ msgstr "Empfänger"
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-recipient-selector.tsx
|
||||
msgid "Recipient {0}"
|
||||
msgstr ""
|
||||
msgstr "Empfänger {0}"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx
|
||||
@@ -7170,7 +7191,7 @@ msgstr "Organisationsmitglied entfernen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Remove recipient"
|
||||
msgstr ""
|
||||
msgstr "Empfänger entfernen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx
|
||||
msgid "Remove team email"
|
||||
@@ -7195,7 +7216,7 @@ msgstr "Auf E-Mail antworten"
|
||||
#: 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 ""
|
||||
msgstr "Antwort an E-Mail-Adresse <0>(Optional)</0>"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
msgid "Request"
|
||||
@@ -7332,6 +7353,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 ""
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/o.$orgUrl.signin.tsx
|
||||
msgid "Return to Documenso sign in page here"
|
||||
msgstr "Hier zur Documenso-Anmeldeseite zurückkehren"
|
||||
@@ -8120,7 +8146,7 @@ msgstr "Website Einstellungen"
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Skip"
|
||||
msgstr ""
|
||||
msgstr "Überspringen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx
|
||||
msgid "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
|
||||
@@ -8182,11 +8208,11 @@ msgstr "Etwas ist schiefgelaufen beim Versuch, Ihre E-Mail-Adresse für <0>{0}</
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Something went wrong while detecting fields."
|
||||
msgstr ""
|
||||
msgstr "Bei der Felderkennung ist ein Fehler aufgetreten."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Something went wrong while detecting recipients."
|
||||
msgstr ""
|
||||
msgstr "Bei der Empfängererkennung ist ein Fehler aufgetreten."
|
||||
|
||||
#: packages/ui/components/pdf-viewer/pdf-viewer-konva.tsx
|
||||
#: packages/ui/components/pdf-viewer/pdf-viewer-konva.tsx
|
||||
@@ -8808,7 +8834,7 @@ msgstr "Der Anzeigename für diese E-Mail-Adresse"
|
||||
|
||||
#: apps/remix/app/components/dialogs/admin-document-delete-dialog.tsx
|
||||
msgid "The Document has been deleted successfully."
|
||||
msgstr ""
|
||||
msgstr "Das Dokument wurde erfolgreich gelöscht."
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
|
||||
msgid "The document has been moved successfully."
|
||||
@@ -8816,7 +8842,7 @@ msgstr "Das Dokument wurde erfolgreich verschoben."
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-reject-dialog.tsx
|
||||
msgid "The document has been successfully rejected."
|
||||
msgstr ""
|
||||
msgstr "Das Dokument wurde erfolgreich abgelehnt."
|
||||
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-upload.tsx
|
||||
msgid "The document is already saved and cannot be changed."
|
||||
@@ -8835,6 +8861,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 ""
|
||||
|
||||
#: 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."
|
||||
@@ -8870,7 +8902,7 @@ msgstr "Die gesuchte E-Mail-Domäne wurde möglicherweise entfernt, umbenannt od
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
msgid "The email or password provided is incorrect."
|
||||
msgstr ""
|
||||
msgstr "Die angegebene E-Mail-Adresse oder das Passwort ist falsch."
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
@@ -8903,7 +8935,7 @@ msgstr "Die folgenden Fehler sind aufgetreten:"
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
|
||||
msgid "The following recipients require an email address:"
|
||||
msgstr ""
|
||||
msgstr "Für die folgenden Empfänger wird eine E‑Mail‑Adresse benötigt:"
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
|
||||
msgid "The following signers are missing signature fields:"
|
||||
@@ -9069,7 +9101,7 @@ msgstr "Der Token, den Sie zur Zurücksetzung Ihres Passworts verwendet haben, i
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
msgid "The two-factor authentication code provided is incorrect."
|
||||
msgstr ""
|
||||
msgstr "Der angegebene Zwei-Faktor-Authentifizierungscode ist falsch."
|
||||
|
||||
#: apps/remix/app/components/forms/editor/editor-field-signature-form.tsx
|
||||
msgid "The typed signature font size"
|
||||
@@ -9094,7 +9126,7 @@ msgstr "Die Zwei-Faktor-Authentifizierung des Nutzers wurde erfolgreich zurückg
|
||||
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "The visibility of the document to the recipient."
|
||||
msgstr ""
|
||||
msgstr "Die Sichtbarkeit des Dokuments für den Empfänger."
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-delete-dialog.tsx
|
||||
msgid "The webhook has been successfully deleted."
|
||||
@@ -9165,7 +9197,7 @@ msgstr "Diese Aktion ist umkehrbar, jedoch bitte seien Sie vorsichtig, da das Ko
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "This can take a minute or two depending on the size of your document."
|
||||
msgstr ""
|
||||
msgstr "Je nach Größe Ihres Dokuments kann dies ein bis zwei Minuten dauern."
|
||||
|
||||
#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx
|
||||
msgid "This claim is locked and cannot be deleted."
|
||||
@@ -9240,7 +9272,7 @@ msgstr "Dieses Dokument wurde mit einem direkten Link erstellt."
|
||||
|
||||
#: packages/email/template-components/template-footer.tsx
|
||||
msgid "This document was sent using <0>Documenso</0>."
|
||||
msgstr ""
|
||||
msgstr "Dieses Dokument wurde mit <0>Documenso</0> versendet."
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelope-duplicate-dialog.tsx
|
||||
msgid "This document will be duplicated."
|
||||
@@ -9545,7 +9577,7 @@ msgstr "Token-Name"
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Too many requests"
|
||||
msgstr ""
|
||||
msgstr "Zu viele Anfragen"
|
||||
|
||||
#: apps/remix/app/components/forms/editor/editor-field-generic-field-forms.tsx
|
||||
msgid "Top"
|
||||
@@ -9573,7 +9605,7 @@ msgstr "Gesamtanzahl der Benutzer"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
|
||||
msgid "Transfer documents to a different team"
|
||||
msgstr ""
|
||||
msgstr "Dokumente auf ein anderes Team übertragen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
@@ -9585,11 +9617,11 @@ msgstr "Auslöser"
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Try again"
|
||||
msgstr ""
|
||||
msgstr "Erneut versuchen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
msgid "Turn on AI detection to automatically find recipients and fields in your documents. AI providers do not retain your data for training."
|
||||
msgstr ""
|
||||
msgstr "Aktivieren Sie die KI-Erkennung, um automatisch Empfänger und Felder in Ihren Dokumenten zu finden. KI-Anbieter behalten Ihre Daten nicht für Trainingszwecke."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
|
||||
msgid "Two factor authentication"
|
||||
@@ -9739,7 +9771,7 @@ msgstr "Unvollendet"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Undo"
|
||||
msgstr ""
|
||||
msgstr "Rückgängig"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.linked-accounts.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
@@ -9759,7 +9791,7 @@ msgstr "Unbekannter Fehler"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Unknown name"
|
||||
msgstr ""
|
||||
msgstr "Unbekannter Name"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-claims-table.tsx
|
||||
msgid "Unlimited"
|
||||
@@ -10354,7 +10386,7 @@ msgstr "Warten auf deine Reihe"
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
|
||||
msgid "Want to send slick signing links like this one? <0>Check out Documenso</0>."
|
||||
msgstr ""
|
||||
msgstr "Möchtest du schicke Signatur-Links wie diesen versenden? <0>Sieh dir Documenso an</0>."
|
||||
|
||||
#: apps/remix/app/routes/_profile+/_layout.tsx
|
||||
msgid "Want your own public profile?"
|
||||
@@ -10391,7 +10423,7 @@ msgstr "Wir konnten keinen Stripe-Kunden erstellen. Bitte versuchen Sie es erneu
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
msgid "We couldn't enable AI features right now. Please try again."
|
||||
msgstr ""
|
||||
msgstr "Wir konnten die KI-Funktionen gerade nicht aktivieren. Bitte versuchen Sie es erneut."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
|
||||
msgid "We couldn't update the group. Please try again."
|
||||
@@ -10476,7 +10508,7 @@ msgstr "Wir sind auf einen unbekannten Fehler gestoßen, während wir versucht h
|
||||
|
||||
#: apps/remix/app/components/dialogs/admin-document-delete-dialog.tsx
|
||||
msgid "We encountered an unknown error while attempting to delete your document. Please try again later."
|
||||
msgstr ""
|
||||
msgstr "Beim Versuch, Ihr Dokument zu löschen, ist ein unbekannter Fehler aufgetreten. Bitte versuchen Sie es später erneut."
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-inherit-member-disable-dialog.tsx
|
||||
msgid "We encountered an unknown error while attempting to disable access."
|
||||
@@ -10686,11 +10718,11 @@ msgstr "Wir werden uns so schnell wie möglich per E-Mail bei Ihnen melden."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "We'll scan your document to find form fields like signature lines, text inputs, checkboxes, and more. Detected fields will be suggested for you to review."
|
||||
msgstr ""
|
||||
msgstr "Wir scannen Ihr Dokument, um Formularfelder wie Signaturzeilen, Texteingaben, Kontrollkästchen und mehr zu finden. Erkannte Felder werden Ihnen zur Überprüfung vorgeschlagen."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "We'll scan your document to find signature fields and identify who needs to sign. Detected recipients will be suggested for you to review."
|
||||
msgstr ""
|
||||
msgstr "Wir scannen Ihr Dokument, um Signaturfelder zu finden und zu ermitteln, wer unterschreiben muss. Erkannte Empfänger werden Ihnen zur Überprüfung vorgeschlagen."
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/access-auth-2fa-form.tsx
|
||||
msgid "We'll send a 6-digit code to your email"
|
||||
@@ -10842,6 +10874,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"
|
||||
|
||||
@@ -10955,12 +10988,12 @@ msgstr "Sie aktualisieren derzeit <0>{0}</0>"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-member-update-dialog.tsx
|
||||
msgid "You are currently updating <0>{memberName}</0>."
|
||||
msgstr ""
|
||||
msgstr "Du bearbeitest aktuell <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 ""
|
||||
msgstr "Du bearbeitest aktuell <0>{organisationMemberName}</0>."
|
||||
|
||||
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
|
||||
msgid "You are currently updating the <0>{passkeyName}</0> passkey."
|
||||
@@ -10997,11 +11030,11 @@ msgstr "Sie sind nicht berechtigt, die Zwei-Faktor-Authentifizierung für diesen
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "You can add fields manually in the editor."
|
||||
msgstr ""
|
||||
msgstr "Sie können im Editor manuell Felder hinzufügen."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "You can add recipients manually in the editor."
|
||||
msgstr ""
|
||||
msgstr "Sie können im Editor manuell Empfänger hinzufügen."
|
||||
|
||||
#: packages/email/template-components/template-confirmation-email.tsx
|
||||
msgid "You can also copy and paste this link into your browser: {confirmationLink} (link expires in 1 hour)"
|
||||
@@ -11022,15 +11055,15 @@ msgstr "Sie können den Zugriff so aktivieren, dass alle Organisationsmitglieder
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.email.tsx
|
||||
msgid "You can manage your email preferences here."
|
||||
msgstr ""
|
||||
msgstr "Du kannst deine E-Mail-Einstellungen hier verwalten."
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
|
||||
msgid "You can only detect fields in draft envelopes"
|
||||
msgstr ""
|
||||
msgstr "Sie können Felder nur in Entwürfen von Umschlägen erkennen."
|
||||
|
||||
#: packages/email/templates/confirm-team-email.tsx
|
||||
msgid "You can revoke access at any time in your team settings on Documenso <0>here</0>."
|
||||
msgstr ""
|
||||
msgstr "Sie können den Zugriff jederzeit in Ihren Teameinstellungen auf Documenso <0>hier</0> widerrufen."
|
||||
|
||||
#: apps/remix/app/components/forms/public-profile-form.tsx
|
||||
msgid "You can update the profile URL by updating the team URL in the general settings page."
|
||||
@@ -11064,7 +11097,7 @@ msgstr "Sie können keine Gruppe löschen, die eine höhere Rolle hat als Sie."
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelope-item-delete-dialog.tsx
|
||||
msgid "You cannot delete this item because the document has been sent to recipients."
|
||||
msgstr ""
|
||||
msgstr "Du kannst dieses Element nicht löschen, weil das Dokument bereits an Empfänger gesendet wurde."
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-group-update-dialog.tsx
|
||||
msgid "You cannot modify a group which has a higher role than you."
|
||||
@@ -11091,7 +11124,7 @@ msgstr "Sie können derzeit keine Dokumente hochladen."
|
||||
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
|
||||
#: apps/remix/app/components/general/envelope/envelope-upload-button.tsx
|
||||
msgid "You cannot upload encrypted PDFs."
|
||||
msgstr ""
|
||||
msgstr "Du kannst keine verschlüsselten PDFs hochladen."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.billing.tsx
|
||||
msgid "You currently have an inactive <0>{currentProductName}</0> subscription"
|
||||
@@ -11103,7 +11136,7 @@ msgstr "Derzeit haben Sie keinen Zugriff auf Teams in dieser Organisation. Bitte
|
||||
|
||||
#: apps/remix/app/components/forms/token.tsx
|
||||
msgid "You do not have permission to create a token for this team."
|
||||
msgstr ""
|
||||
msgstr "Sie haben keine Berechtigung, ein Token für dieses Team zu erstellen."
|
||||
|
||||
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
|
||||
msgid "You don't manage billing for any organisations."
|
||||
@@ -11173,7 +11206,7 @@ msgstr "Sie haben noch keine Dokumente erstellt oder erhalten. Bitte laden Sie e
|
||||
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
|
||||
#: apps/remix/app/components/general/envelope/envelope-upload-button.tsx
|
||||
msgid "You have reached the limit of the number of files per envelope."
|
||||
msgstr ""
|
||||
msgstr "Sie haben die maximale Anzahl von Dateien pro Umschlag erreicht."
|
||||
|
||||
#. placeholder {0}: quota.directTemplates
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
@@ -11330,12 +11363,12 @@ msgstr "Sie erhalten eine Kopie des unterschriebenen Dokuments per E-Mail, sobal
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
msgid "You're an admin. You can enable AI features for this team right away. Everyone on the team will see AI detection once enabled."
|
||||
msgstr ""
|
||||
msgstr "Sie sind Administrator. Sie können die KI-Funktionen für dieses Team sofort aktivieren. Alle im Team sehen die KI-Erkennung, sobald sie aktiviert ist."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "You've made too many detection requests. Please wait a minute before trying again."
|
||||
msgstr ""
|
||||
msgstr "Sie haben zu viele Erkennungsanfragen gestellt. Bitte warten Sie eine Minute, bevor Sie es erneut versuchen."
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "Your Account"
|
||||
@@ -11392,7 +11425,7 @@ msgstr "Ihre direkten Unterzeichnungsvorlagen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
msgid "Your document content will be sent securely to our AI provider solely for detection and will not be stored or used for training."
|
||||
msgstr ""
|
||||
msgstr "Der Inhalt Ihres Dokuments wird ausschließlich zum Zweck der Erkennung sicher an unseren KI-Anbieter gesendet und weder gespeichert noch für Trainingszwecke verwendet."
|
||||
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-upload.tsx
|
||||
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
|
||||
@@ -11436,7 +11469,7 @@ msgstr "Ihr Dokument wurde erfolgreich hochgeladen. Sie werden zur Vorlagenseite
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Your document is processed securely using AI services that don't retain your data."
|
||||
msgstr ""
|
||||
msgstr "Ihr Dokument wird sicher mit KI‑Diensten verarbeitet, die Ihre Daten nicht speichern."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.document.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.document.tsx
|
||||
|
||||
@@ -3104,6 +3104,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"
|
||||
@@ -3667,6 +3671,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 +4017,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
|
||||
@@ -4273,6 +4283,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
|
||||
@@ -5157,6 +5171,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"
|
||||
@@ -5549,6 +5564,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 +5625,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"
|
||||
@@ -5955,6 +5975,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"
|
||||
|
||||
@@ -7327,6 +7348,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"
|
||||
@@ -8830,6 +8856,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."
|
||||
@@ -10837,6 +10869,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-11-27 05:32\n"
|
||||
"PO-Revision-Date: 2025-12-15 02:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@@ -68,7 +68,7 @@ msgstr "{0, plural, one {(1 carácter excedido)} other {(# caracteres excedidos)
|
||||
#. placeholder {2}: table.getFilteredSelectedRowModel().rows.length
|
||||
#: packages/ui/primitives/data-table-pagination.tsx
|
||||
msgid "{0, plural, one {{1} of # row selected.} other {{2} of # rows selected.}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {{1} de # fila seleccionada.} other {{2} de # filas seleccionadas.}}"
|
||||
|
||||
#. placeholder {0}: Math.abs(remaningLength)
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
@@ -104,7 +104,7 @@ msgstr "{0, plural, one {# carpeta} other {# carpetas}}"
|
||||
#. placeholder {0}: detectedRecipients.length
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
|
||||
msgid "{0, plural, one {# recipient have been added from AI detection.} other {# recipients have been added from AI detection.}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Se ha añadido # destinatario a partir de la detección por IA.} other {Se han añadido # destinatarios a partir de la detección por IA.}}"
|
||||
|
||||
#. placeholder {0}: template.recipients.length
|
||||
#: apps/remix/app/routes/_recipient+/d.$token+/_index.tsx
|
||||
@@ -163,7 +163,7 @@ msgstr "{0, plural, one {1 Destinatario} other {# Destinatarios}}"
|
||||
#. placeholder {4}: progress.totalPages
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "{0, plural, one {Page {1} of {2} - # field found} other {Page {3} of {4} - # fields found}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Página {1} de {2} - se ha encontrado # campo} other {Página {3} de {4} - se han encontrado # campos}}"
|
||||
|
||||
#. placeholder {0}: progress.recipientsDetected
|
||||
#. placeholder {1}: progress.pagesProcessed
|
||||
@@ -172,12 +172,12 @@ msgstr ""
|
||||
#. placeholder {4}: progress.totalPages
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "{0, plural, one {Page {1} of {2} - # recipient found} other {Page {3} of {4} - # recipients found}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Página {1} de {2} - se ha encontrado # destinatario} other {Página {3} de {4} - se han encontrado # destinatarios}}"
|
||||
|
||||
#. placeholder {0}: detectedRecipients.length
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
|
||||
msgid "{0, plural, one {Recipient added} other {Recipients added}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Destinatario añadido} other {Destinatarios añadidos}}"
|
||||
|
||||
#. placeholder {0}: pendingRecipients.length
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
@@ -187,12 +187,12 @@ msgstr "{0, plural, one {Esperando 1 destinatario} other {Esperando # destinatar
|
||||
#. placeholder {0}: detectedFields.length
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "{0, plural, one {We found # field in your document.} other {We found # fields in your document.}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Hemos encontrado # campo en tu documento.} other {Hemos encontrado # campos en tu documento.}}"
|
||||
|
||||
#. placeholder {0}: detectedRecipients.length
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "{0, plural, one {We found # recipient in your document.} other {We found # recipients in your document.}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Hemos encontrado # destinatario en tu documento.} other {Hemos encontrado # destinatarios en tu documento.}}"
|
||||
|
||||
#. placeholder {0}: _(FRIENDLY_FIELD_TYPE[fieldType as FieldType])
|
||||
#. placeholder {0}: route.label
|
||||
@@ -312,7 +312,7 @@ msgstr "{MAXIMUM_PASSKEYS, plural, one {No puedes tener más de # clave de acces
|
||||
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
|
||||
#: apps/remix/app/components/general/envelope/envelope-upload-button.tsx
|
||||
msgid "{maximumEnvelopeItemCount, plural, one {You cannot upload more than # item per envelope.} other {You cannot upload more than # items per envelope.}}"
|
||||
msgstr ""
|
||||
msgstr "{maximumEnvelopeItemCount, plural, one {No puedes subir más de # elemento por sobre.} other {No puedes subir más de # elementos por sobre.}}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} added a field"
|
||||
@@ -547,39 +547,39 @@ msgstr "<0>{organisationName}</0> ha solicitado vincular tu cuenta actual de Doc
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> has invited you to approve this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> te ha invitado a aprobar este documento"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> has invited you to assist this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> te ha invitado a asistir en este documento"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> has invited you to sign this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> te ha invitado a firmar este documento"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> has invited you to view this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> te ha invitado a ver este documento"
|
||||
|
||||
#. placeholder {0}: document.team?.name
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> on behalf of \"{0}\" has invited you to approve this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0>, en nombre de \"{0}\", te ha invitado a aprobar este documento"
|
||||
|
||||
#. placeholder {0}: document.team?.name
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> on behalf of \"{0}\" has invited you to assist this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0>, en nombre de \"{0}\", te ha invitado a asistir en este documento"
|
||||
|
||||
#. placeholder {0}: document.team?.name
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> on behalf of \"{0}\" has invited you to sign this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0>, en nombre de \"{0}\", te ha invitado a firmar este documento"
|
||||
|
||||
#. placeholder {0}: document.team?.name
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> on behalf of \"{0}\" has invited you to view this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0>, en nombre de \"{0}\", te ha invitado a ver este documento"
|
||||
|
||||
#: packages/email/templates/confirm-team-email.tsx
|
||||
msgid "<0>{teamName}</0> has requested to use your email address for their team on Documenso."
|
||||
@@ -591,7 +591,7 @@ msgstr "<0>Administración de cuenta:</0> Modifica la configuración, permisos y
|
||||
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "<0>Admins only</0> - Only admins can access and view the document"
|
||||
msgstr ""
|
||||
msgstr "<0>Solo administradores</0> - Solo los administradores pueden acceder y ver el documento"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "<0>Data access:</0> Access all data associated with your account"
|
||||
@@ -612,7 +612,7 @@ msgstr "<0>Eventos:</0> Todos"
|
||||
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "<0>Everyone</0> - Everyone can access and view the document"
|
||||
msgstr ""
|
||||
msgstr "<0>Todos</0> - Todos pueden acceder y ver el documento"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "<0>Full account access:</0> View all your profile information, settings, and activity"
|
||||
@@ -624,7 +624,7 @@ msgstr "<0>Heredar método de autenticación</0> - Use el método de autenticaci
|
||||
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "<0>Managers and above</0> - Only managers and above can access and view the document"
|
||||
msgstr ""
|
||||
msgstr "<0>Gerentes y superiores</0> - Solo los gerentes y superiores pueden acceder y ver el documento"
|
||||
|
||||
#: packages/ui/components/document/document-global-auth-action-select.tsx
|
||||
msgid "<0>No restrictions</0> - No authentication required"
|
||||
@@ -981,11 +981,11 @@ msgstr "Cuenta habilitada"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "Account link declined"
|
||||
msgstr ""
|
||||
msgstr "Enlace de cuenta rechazado"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "Account linked successfully"
|
||||
msgstr ""
|
||||
msgstr "Cuenta vinculada correctamente"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "Account Linking Request"
|
||||
@@ -1047,7 +1047,7 @@ msgstr "Activo"
|
||||
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
|
||||
msgctxt "Subscription status"
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
msgstr "Activa"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
@@ -1144,7 +1144,7 @@ msgstr "Agregar dominio de correo electrónico"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Add fields"
|
||||
msgstr ""
|
||||
msgstr "Agregar campos"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-edit-form.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
|
||||
@@ -1212,7 +1212,7 @@ msgstr "Agregar Marcadores de posición"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Add recipients"
|
||||
msgstr ""
|
||||
msgstr "Agregar destinatarios"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
|
||||
msgid "Add Recipients"
|
||||
@@ -1316,11 +1316,11 @@ msgstr "Después de la presentación, se generará automáticamente un documento
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "AI Features"
|
||||
msgstr ""
|
||||
msgstr "Funciones de IA"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
msgid "AI features are disabled for your team. Please ask your team owner or organisation owner to enable them."
|
||||
msgstr ""
|
||||
msgstr "Las funciones de IA están desactivadas para tu equipo. Pide a la persona propietaria del equipo u organización que las habilite."
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
@@ -1418,7 +1418,7 @@ msgstr "Permite autenticarse usando biometría, administradores de contraseñas,
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Almost done"
|
||||
msgstr ""
|
||||
msgstr "Casi listo"
|
||||
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
msgid "Already have an account? <0>Sign in instead</0>"
|
||||
@@ -1512,7 +1512,7 @@ msgstr "Se produjo un error al firmar automáticamente el documento, es posible
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx
|
||||
msgid "An error occurred while completing the document. Please try again."
|
||||
msgstr ""
|
||||
msgstr "Se produjo un error al completar el documento. Inténtalo de nuevo."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "An error occurred while creating document from template."
|
||||
@@ -1560,7 +1560,7 @@ msgstr "Ocurrió un error al mover la plantilla."
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-reject-dialog.tsx
|
||||
msgid "An error occurred while rejecting the document. Please try again."
|
||||
msgstr ""
|
||||
msgstr "Se produjo un error al rechazar el documento. Inténtalo de nuevo."
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-checkbox-field.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-date-field.tsx
|
||||
@@ -1726,11 +1726,11 @@ msgstr "Se produjo un error desconocido al mover la carpeta."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Analyzing page layout"
|
||||
msgstr ""
|
||||
msgstr "Analizando el diseño de la página"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Analyzing pages"
|
||||
msgstr ""
|
||||
msgstr "Analizando las páginas"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/inbox.tsx
|
||||
msgid "Any documents that you have been invited to will appear here"
|
||||
@@ -2301,7 +2301,7 @@ msgstr "Casilla de verificación"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-content.tsx
|
||||
msgid "Checkbox option"
|
||||
msgstr ""
|
||||
msgstr "Opción de casilla de verificación"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
|
||||
msgid "Checkbox Settings"
|
||||
@@ -2624,7 +2624,7 @@ msgstr "Contenido"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Context"
|
||||
msgstr ""
|
||||
msgstr "Contexto"
|
||||
|
||||
#: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx
|
||||
@@ -2707,13 +2707,13 @@ msgstr "Copiado"
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx
|
||||
msgid "Copied field"
|
||||
msgstr ""
|
||||
msgstr "Campo copiado"
|
||||
|
||||
#: apps/remix/app/components/embed/authoring/configure-fields-view.tsx
|
||||
#: packages/ui/primitives/document-flow/add-fields.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx
|
||||
msgid "Copied field to clipboard"
|
||||
msgstr ""
|
||||
msgstr "Campo copiado al portapapeles"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-records-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-records-dialog.tsx
|
||||
@@ -3060,7 +3060,7 @@ msgstr "Configuración de Fecha"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "David is the Employee, Lucas is the Manager"
|
||||
msgstr ""
|
||||
msgstr "David es el empleado, Lucas es el gerente"
|
||||
|
||||
#: apps/remix/app/components/general/organisations/organisation-invitations.tsx
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
@@ -3109,6 +3109,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 ""
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx
|
||||
msgid "delete"
|
||||
msgstr "eliminar"
|
||||
@@ -3274,48 +3278,48 @@ msgstr "Detalles"
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Detect"
|
||||
msgstr ""
|
||||
msgstr "Detectar"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Detect fields"
|
||||
msgstr ""
|
||||
msgstr "Detectar campos"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Detect recipients"
|
||||
msgstr ""
|
||||
msgstr "Detectar destinatarios"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
|
||||
msgid "Detect recipients with AI"
|
||||
msgstr ""
|
||||
msgstr "Detectar destinatarios con IA"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
|
||||
msgid "Detect with AI"
|
||||
msgstr ""
|
||||
msgstr "Detectar con IA"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Detected fields"
|
||||
msgstr ""
|
||||
msgstr "Campos detectados"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Detected recipients"
|
||||
msgstr ""
|
||||
msgstr "Destinatarios detectados"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Detecting fields"
|
||||
msgstr ""
|
||||
msgstr "Detectando campos"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Detecting recipients"
|
||||
msgstr ""
|
||||
msgstr "Detectando destinatarios"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Detecting signature areas"
|
||||
msgstr ""
|
||||
msgstr "Detectando áreas de firma"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Detection failed"
|
||||
msgstr ""
|
||||
msgstr "La detección ha fallado"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
|
||||
msgid "Developer Mode"
|
||||
@@ -3329,7 +3333,7 @@ msgstr "Dispositivo"
|
||||
|
||||
#: packages/email/templates/reset-password.tsx
|
||||
msgid "Didn't request a password change? We are here to help you secure your account, just <0>contact us</0>."
|
||||
msgstr ""
|
||||
msgstr "¿No solicitaste un cambio de contraseña? Estamos aquí para ayudarte a asegurar tu cuenta, solo <0>contáctanos</0>."
|
||||
|
||||
#: apps/remix/app/components/general/template/template-direct-link-badge.tsx
|
||||
#: apps/remix/app/components/tables/templates-table.tsx
|
||||
@@ -3672,6 +3676,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 ""
|
||||
|
||||
#: apps/remix/app/components/general/document/document-status.tsx
|
||||
msgid "Document pending"
|
||||
msgstr "Documento pendiente"
|
||||
@@ -3873,7 +3882,7 @@ msgstr "¿No tienes una cuenta? <0>Regístrate</0>"
|
||||
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
|
||||
msgid "Don't transfer (Delete all documents)"
|
||||
msgstr ""
|
||||
msgstr "No transferir (Eliminar todos los documentos)"
|
||||
|
||||
#: apps/remix/app/components/forms/2fa/enable-authenticator-app-dialog.tsx
|
||||
#: apps/remix/app/components/forms/2fa/view-recovery-codes-dialog.tsx
|
||||
@@ -4013,6 +4022,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
|
||||
@@ -4231,16 +4241,16 @@ msgstr "Habilitar Cuenta"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
|
||||
msgid "Enable AI detection"
|
||||
msgstr ""
|
||||
msgstr "Habilitar detección por IA"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
msgid "Enable AI features"
|
||||
msgstr ""
|
||||
msgstr "Habilitar funciones de IA"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Enable AI-powered features such as automatic recipient detection. When enabled, document content will be sent to AI providers. We only use providers that do not retain data for training and prefer European regions where available."
|
||||
msgstr ""
|
||||
msgstr "Activa las funciones impulsadas por IA, como la detección automática de destinatarios. Cuando esté activado, el contenido del documento se enviará a proveedores de IA. Solo usamos proveedores que no conservan datos para entrenamiento y preferimos regiones europeas cuando sea posible."
|
||||
|
||||
#: apps/remix/app/components/forms/2fa/enable-authenticator-app-dialog.tsx
|
||||
msgid "Enable Authenticator App"
|
||||
@@ -4278,6 +4288,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 ""
|
||||
|
||||
#: 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
|
||||
@@ -4476,11 +4490,11 @@ msgstr "Error"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "Error declining account link"
|
||||
msgstr ""
|
||||
msgstr "Error al rechazar el enlace de cuenta"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "Error linking account"
|
||||
msgstr ""
|
||||
msgstr "Error al vincular la cuenta"
|
||||
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-upload.tsx
|
||||
msgid "Error uploading file"
|
||||
@@ -4538,7 +4552,7 @@ msgstr "ID externo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Extracting contact details"
|
||||
msgstr ""
|
||||
msgstr "Extrayendo datos de contacto"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
@@ -4547,7 +4561,7 @@ msgstr "Error"
|
||||
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
msgid "Failed to complete the document. Please try again."
|
||||
msgstr ""
|
||||
msgstr "No se pudo completar el documento. Inténtalo de nuevo."
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Failed to create folder"
|
||||
@@ -4619,7 +4633,7 @@ msgstr "Falló al actualizar el webhook"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx
|
||||
msgid "Failed to upload CSV. Please check the file format and try again."
|
||||
msgstr ""
|
||||
msgstr "Error al subir el CSV. Verifica el formato del archivo e inténtalo de nuevo."
|
||||
|
||||
#: packages/email/templates/bulk-send-complete.tsx
|
||||
msgid "Failed: {failedCount}"
|
||||
@@ -4779,13 +4793,13 @@ msgstr "¿Olvidaste tu contraseña?"
|
||||
#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx
|
||||
msgctxt "Plan price"
|
||||
msgid "Free"
|
||||
msgstr ""
|
||||
msgstr "Gratis"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
|
||||
msgctxt "Subscription status"
|
||||
msgid "Free"
|
||||
msgstr ""
|
||||
msgstr "Gratis"
|
||||
|
||||
#: packages/lib/utils/fields.ts
|
||||
#: packages/ui/primitives/document-flow/types.ts
|
||||
@@ -4878,15 +4892,15 @@ msgstr "Ir al documento"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx
|
||||
msgid "Go to first page"
|
||||
msgstr ""
|
||||
msgstr "Ir a la primera página"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx
|
||||
msgid "Go to last page"
|
||||
msgstr ""
|
||||
msgstr "Ir a la última página"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx
|
||||
msgid "Go to next page"
|
||||
msgstr ""
|
||||
msgstr "Ir a la página siguiente"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
msgid "Go to owner"
|
||||
@@ -4894,7 +4908,7 @@ msgstr "Ir al propietario"
|
||||
|
||||
#: packages/ui/primitives/data-table-pagination.tsx
|
||||
msgid "Go to previous page"
|
||||
msgstr ""
|
||||
msgstr "Ir a la página anterior"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Go to team"
|
||||
@@ -4953,7 +4967,7 @@ msgstr "Ayuda a completar el documento para otros firmantes."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Help the AI assign fields to the right recipients."
|
||||
msgstr ""
|
||||
msgstr "Ayuda a la IA a asignar los campos a los destinatarios correctos."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email-domains._index.tsx
|
||||
msgid "Here you can add email domains to your organisation."
|
||||
@@ -4977,7 +4991,7 @@ msgstr "Aquí puedes configurar las preferencias de marca para tu organización.
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
|
||||
msgid "Here you can set branding preferences for your team."
|
||||
msgstr ""
|
||||
msgstr "Aquí puedes configurar las preferencias de marca de tu equipo."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.document.tsx
|
||||
msgid "Here you can set document preferences for your organisation. Teams will inherit these settings by default."
|
||||
@@ -4993,11 +5007,11 @@ msgstr "Aquí puedes establecer preferencias y valores predeterminados para tu e
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
|
||||
msgid "Here you can set your general branding preferences."
|
||||
msgstr ""
|
||||
msgstr "Aquí puedes configurar tus preferencias generales de marca."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.document.tsx
|
||||
msgid "Here you can set your general document preferences."
|
||||
msgstr ""
|
||||
msgstr "Aquí puedes configurar tus preferencias generales de documentos."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Here's how it works:"
|
||||
@@ -5090,11 +5104,11 @@ msgstr "ID copiado al portapapeles"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Identifying input fields"
|
||||
msgstr ""
|
||||
msgstr "Identificando campos de entrada"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Identifying recipients"
|
||||
msgstr ""
|
||||
msgstr "Identificando destinatarios"
|
||||
|
||||
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
|
||||
msgid "If there is any issue with your subscription, please contact us at <0>{SUPPORT_EMAIL}</0>."
|
||||
@@ -5123,7 +5137,7 @@ msgstr "Importante: Lo que esto significa"
|
||||
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
|
||||
msgctxt "Subscription status"
|
||||
msgid "Inactive"
|
||||
msgstr ""
|
||||
msgstr "Inactiva"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
@@ -5162,6 +5176,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"
|
||||
@@ -5547,13 +5562,14 @@ msgstr "Registros"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Looking for form fields"
|
||||
msgstr ""
|
||||
msgstr "Buscando campos de formulario"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Looking for signature fields"
|
||||
msgstr ""
|
||||
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 +5630,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 ""
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx
|
||||
msgid "Manage organisations"
|
||||
msgstr "Administrar organizaciones"
|
||||
@@ -5706,7 +5726,7 @@ msgstr "Gerentes y superiores"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Mapping fields to recipients"
|
||||
msgstr ""
|
||||
msgstr "Asignando campos a destinatarios"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
|
||||
msgid "Mark as viewed"
|
||||
@@ -5960,6 +5980,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"
|
||||
|
||||
@@ -5975,11 +5996,11 @@ msgstr "No se encontraron documentos"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "No email detected"
|
||||
msgstr ""
|
||||
msgstr "No se detectó ningún correo electrónico"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "No fields were detected in your document."
|
||||
msgstr ""
|
||||
msgstr "No se detectaron campos en tu documento."
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
|
||||
@@ -6027,7 +6048,7 @@ msgstr "Sin destinatarios"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "No recipients were detected in your document."
|
||||
msgstr ""
|
||||
msgstr "No se detectaron destinatarios en tu documento."
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-recipient-selector.tsx
|
||||
#: packages/ui/primitives/recipient-selector.tsx
|
||||
@@ -6168,7 +6189,7 @@ msgstr "Una vez que hayas escaneado el código QR o ingresado el código manualm
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-records-dialog.tsx
|
||||
msgid "Once you update your DNS records, it may take up to 48 hours for it to be propogated. Once the DNS propagation is complete you will need to come back and press the \"Sync\" domains button."
|
||||
msgstr ""
|
||||
msgstr "Una vez que actualices tus registros DNS, la propagación puede tardar hasta 48 horas. Cuando la propagación del DNS se haya completado, deberás volver y presionar el botón \"Sincronizar\" dominios."
|
||||
|
||||
#: packages/lib/constants/template.ts
|
||||
msgid "Once your template is set up, share the link anywhere you want. The person who opens the link will be able to enter their information in the direct link recipient field and complete any other fields assigned to them."
|
||||
@@ -6204,7 +6225,7 @@ msgstr "¡Ups! Algo salió mal."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
|
||||
msgid "Open menu"
|
||||
msgstr ""
|
||||
msgstr "Abrir menú"
|
||||
|
||||
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
|
||||
msgid "Opened"
|
||||
@@ -6408,7 +6429,7 @@ msgstr "Página {0} de {numPages}"
|
||||
#: apps/remix/app/components/tables/admin-organisations-table.tsx
|
||||
msgctxt "Subscription status"
|
||||
msgid "Paid"
|
||||
msgstr ""
|
||||
msgstr "De pago"
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
@@ -6490,7 +6511,7 @@ msgstr "¡Contraseña actualizada!"
|
||||
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
|
||||
msgctxt "Subscription status"
|
||||
msgid "Past Due"
|
||||
msgstr ""
|
||||
msgstr "Vencida"
|
||||
|
||||
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
|
||||
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
|
||||
@@ -6925,7 +6946,7 @@ msgstr "Lea la <0>divulgación de firma</0> completa."
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Reading your document"
|
||||
msgstr ""
|
||||
msgstr "Leyendo tu documento"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-recipients.tsx
|
||||
msgid "Ready"
|
||||
@@ -6988,7 +7009,7 @@ msgstr "Destinatario"
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-recipient-selector.tsx
|
||||
msgid "Recipient {0}"
|
||||
msgstr ""
|
||||
msgstr "Destinatario {0}"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx
|
||||
@@ -7170,7 +7191,7 @@ msgstr "Eliminar miembro de la organización"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Remove recipient"
|
||||
msgstr ""
|
||||
msgstr "Eliminar destinatario"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx
|
||||
msgid "Remove team email"
|
||||
@@ -7195,7 +7216,7 @@ msgstr "Responder al correo"
|
||||
#: 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 ""
|
||||
msgstr "Responder al correo electrónico <0>(Opcional)</0>"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
msgid "Request"
|
||||
@@ -7332,6 +7353,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 ""
|
||||
|
||||
#: 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í"
|
||||
@@ -8120,7 +8146,7 @@ msgstr "Configuraciones del sitio"
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Skip"
|
||||
msgstr ""
|
||||
msgstr "Omitir"
|
||||
|
||||
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx
|
||||
msgid "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
|
||||
@@ -8182,11 +8208,11 @@ msgstr "Algo salió mal al intentar verificar tu dirección de correo electróni
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "Something went wrong while detecting fields."
|
||||
msgstr ""
|
||||
msgstr "Algo salió mal al detectar los campos."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Something went wrong while detecting recipients."
|
||||
msgstr ""
|
||||
msgstr "Algo salió mal al detectar los destinatarios."
|
||||
|
||||
#: packages/ui/components/pdf-viewer/pdf-viewer-konva.tsx
|
||||
#: packages/ui/components/pdf-viewer/pdf-viewer-konva.tsx
|
||||
@@ -8808,7 +8834,7 @@ msgstr "El nombre para mostrar para esta dirección de correo electrónico"
|
||||
|
||||
#: apps/remix/app/components/dialogs/admin-document-delete-dialog.tsx
|
||||
msgid "The Document has been deleted successfully."
|
||||
msgstr ""
|
||||
msgstr "El documento se ha eliminado correctamente."
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
|
||||
msgid "The document has been moved successfully."
|
||||
@@ -8816,7 +8842,7 @@ msgstr "El documento se ha movido exitosamente."
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-reject-dialog.tsx
|
||||
msgid "The document has been successfully rejected."
|
||||
msgstr ""
|
||||
msgstr "El documento se ha rechazado correctamente."
|
||||
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-upload.tsx
|
||||
msgid "The document is already saved and cannot be changed."
|
||||
@@ -8835,6 +8861,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 ""
|
||||
|
||||
#: 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."
|
||||
@@ -8870,7 +8902,7 @@ msgstr "El dominio de correo electrónico que estás buscando puede haber sido e
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
msgid "The email or password provided is incorrect."
|
||||
msgstr ""
|
||||
msgstr "El correo electrónico o la contraseña proporcionados son incorrectos."
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
@@ -8903,7 +8935,7 @@ msgstr "Se produjeron los siguientes errores:"
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
|
||||
msgid "The following recipients require an email address:"
|
||||
msgstr ""
|
||||
msgstr "Los siguientes destinatarios requieren una dirección de correo electrónico:"
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
|
||||
msgid "The following signers are missing signature fields:"
|
||||
@@ -9069,7 +9101,7 @@ msgstr "El token que has utilizado para restablecer tu contraseña ha expirado o
|
||||
|
||||
#: apps/remix/app/components/forms/signin.tsx
|
||||
msgid "The two-factor authentication code provided is incorrect."
|
||||
msgstr ""
|
||||
msgstr "El código de autenticación de dos factores proporcionado es incorrecto."
|
||||
|
||||
#: apps/remix/app/components/forms/editor/editor-field-signature-form.tsx
|
||||
msgid "The typed signature font size"
|
||||
@@ -9094,7 +9126,7 @@ msgstr "La autenticación de dos factores del usuario se ha restablecido con éx
|
||||
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "The visibility of the document to the recipient."
|
||||
msgstr ""
|
||||
msgstr "La visibilidad del documento para el destinatario."
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-delete-dialog.tsx
|
||||
msgid "The webhook has been successfully deleted."
|
||||
@@ -9165,7 +9197,7 @@ msgstr "Esta acción es reversible, pero ten cuidado ya que la cuenta podría ve
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "This can take a minute or two depending on the size of your document."
|
||||
msgstr ""
|
||||
msgstr "Esto puede tardar uno o dos minutos, según el tamaño de tu documento."
|
||||
|
||||
#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx
|
||||
msgid "This claim is locked and cannot be deleted."
|
||||
@@ -9240,7 +9272,7 @@ msgstr "Este documento fue creado usando un enlace directo."
|
||||
|
||||
#: packages/email/template-components/template-footer.tsx
|
||||
msgid "This document was sent using <0>Documenso</0>."
|
||||
msgstr ""
|
||||
msgstr "Este documento fue enviado usando <0>Documenso</0>."
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelope-duplicate-dialog.tsx
|
||||
msgid "This document will be duplicated."
|
||||
@@ -9545,7 +9577,7 @@ msgstr "Nombre del token"
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Too many requests"
|
||||
msgstr ""
|
||||
msgstr "Demasiadas solicitudes"
|
||||
|
||||
#: apps/remix/app/components/forms/editor/editor-field-generic-field-forms.tsx
|
||||
msgid "Top"
|
||||
@@ -9573,7 +9605,7 @@ msgstr "Total de usuarios"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
|
||||
msgid "Transfer documents to a different team"
|
||||
msgstr ""
|
||||
msgstr "Transferir documentos a un equipo diferente"
|
||||
|
||||
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
|
||||
@@ -9585,11 +9617,11 @@ msgstr "Desencadenadores"
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Try again"
|
||||
msgstr ""
|
||||
msgstr "Intentar de nuevo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
msgid "Turn on AI detection to automatically find recipients and fields in your documents. AI providers do not retain your data for training."
|
||||
msgstr ""
|
||||
msgstr "Activa la detección por IA para encontrar automáticamente destinatarios y campos en tus documentos. Los proveedores de IA no conservan tus datos para entrenamiento."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
|
||||
msgid "Two factor authentication"
|
||||
@@ -9739,7 +9771,7 @@ msgstr "Incompleto"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Undo"
|
||||
msgstr ""
|
||||
msgstr "Deshacer"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.linked-accounts.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
@@ -9759,7 +9791,7 @@ msgstr "Error desconocido"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Unknown name"
|
||||
msgstr ""
|
||||
msgstr "Nombre desconocido"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-claims-table.tsx
|
||||
msgid "Unlimited"
|
||||
@@ -10354,7 +10386,7 @@ msgstr "Esperando tu turno"
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
|
||||
msgid "Want to send slick signing links like this one? <0>Check out Documenso</0>."
|
||||
msgstr ""
|
||||
msgstr "¿Quieres enviar enlaces de firma tan elegantes como este? <0>Prueba Documenso</0>."
|
||||
|
||||
#: apps/remix/app/routes/_profile+/_layout.tsx
|
||||
msgid "Want your own public profile?"
|
||||
@@ -10391,7 +10423,7 @@ msgstr "No pudimos crear un cliente de Stripe. Por favor, intente nuevamente."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
msgid "We couldn't enable AI features right now. Please try again."
|
||||
msgstr ""
|
||||
msgstr "No hemos podido habilitar las funciones de IA en este momento. Inténtalo de nuevo."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
|
||||
msgid "We couldn't update the group. Please try again."
|
||||
@@ -10476,7 +10508,7 @@ msgstr "Encontramos un error desconocido al intentar eliminar tu cuenta. Por fav
|
||||
|
||||
#: apps/remix/app/components/dialogs/admin-document-delete-dialog.tsx
|
||||
msgid "We encountered an unknown error while attempting to delete your document. Please try again later."
|
||||
msgstr ""
|
||||
msgstr "Encontramos un error desconocido al intentar eliminar tu documento. Inténtalo de nuevo más tarde."
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-inherit-member-disable-dialog.tsx
|
||||
msgid "We encountered an unknown error while attempting to disable access."
|
||||
@@ -10686,11 +10718,11 @@ msgstr "Nos pondremos en contacto contigo lo antes posible, a través de correo
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "We'll scan your document to find form fields like signature lines, text inputs, checkboxes, and more. Detected fields will be suggested for you to review."
|
||||
msgstr ""
|
||||
msgstr "Escanearemos tu documento para encontrar campos de formulario como líneas de firma, entradas de texto, casillas de verificación y más. Los campos detectados se sugerirán para que los revises."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "We'll scan your document to find signature fields and identify who needs to sign. Detected recipients will be suggested for you to review."
|
||||
msgstr ""
|
||||
msgstr "Escanearemos tu documento para encontrar campos de firma e identificar quién debe firmar. Los destinatarios detectados se sugerirán para que los revises."
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/access-auth-2fa-form.tsx
|
||||
msgid "We'll send a 6-digit code to your email"
|
||||
@@ -10842,6 +10874,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í"
|
||||
|
||||
@@ -10955,12 +10988,12 @@ msgstr "Actualmente estás actualizando <0>{0}</0>"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-member-update-dialog.tsx
|
||||
msgid "You are currently updating <0>{memberName}</0>."
|
||||
msgstr ""
|
||||
msgstr "Actualmente estás actualizando a <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 ""
|
||||
msgstr "Actualmente estás actualizando a <0>{organisationMemberName}</0>."
|
||||
|
||||
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
|
||||
msgid "You are currently updating the <0>{passkeyName}</0> passkey."
|
||||
@@ -10997,11 +11030,11 @@ msgstr "No está autorizado para restablecer la autenticación de dos factores p
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "You can add fields manually in the editor."
|
||||
msgstr ""
|
||||
msgstr "Puedes agregar campos manualmente en el editor."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "You can add recipients manually in the editor."
|
||||
msgstr ""
|
||||
msgstr "Puedes agregar destinatarios manualmente en el editor."
|
||||
|
||||
#: packages/email/template-components/template-confirmation-email.tsx
|
||||
msgid "You can also copy and paste this link into your browser: {confirmationLink} (link expires in 1 hour)"
|
||||
@@ -11022,15 +11055,15 @@ msgstr "Puedes habilitar el acceso para permitir que todos los miembros de la or
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.email.tsx
|
||||
msgid "You can manage your email preferences here."
|
||||
msgstr ""
|
||||
msgstr "Aquí puedes gestionar tus preferencias de correo electrónico."
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
|
||||
msgid "You can only detect fields in draft envelopes"
|
||||
msgstr ""
|
||||
msgstr "Solo puedes detectar campos en sobres en borrador"
|
||||
|
||||
#: packages/email/templates/confirm-team-email.tsx
|
||||
msgid "You can revoke access at any time in your team settings on Documenso <0>here</0>."
|
||||
msgstr ""
|
||||
msgstr "Puedes revocar el acceso en cualquier momento en la configuración de tu equipo en Documenso <0>aquí</0>."
|
||||
|
||||
#: apps/remix/app/components/forms/public-profile-form.tsx
|
||||
msgid "You can update the profile URL by updating the team URL in the general settings page."
|
||||
@@ -11064,7 +11097,7 @@ msgstr "No puedes eliminar un grupo que tiene un rol superior al tuyo."
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelope-item-delete-dialog.tsx
|
||||
msgid "You cannot delete this item because the document has been sent to recipients."
|
||||
msgstr ""
|
||||
msgstr "No puedes eliminar este elemento porque el documento ha sido enviado a los destinatarios."
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-group-update-dialog.tsx
|
||||
msgid "You cannot modify a group which has a higher role than you."
|
||||
@@ -11091,7 +11124,7 @@ msgstr "No puede cargar documentos en este momento."
|
||||
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
|
||||
#: apps/remix/app/components/general/envelope/envelope-upload-button.tsx
|
||||
msgid "You cannot upload encrypted PDFs."
|
||||
msgstr ""
|
||||
msgstr "No puedes subir archivos PDF cifrados."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.billing.tsx
|
||||
msgid "You currently have an inactive <0>{currentProductName}</0> subscription"
|
||||
@@ -11103,7 +11136,7 @@ msgstr "Actualmente no tienes acceso a ningún equipo dentro de esta organizaci
|
||||
|
||||
#: apps/remix/app/components/forms/token.tsx
|
||||
msgid "You do not have permission to create a token for this team."
|
||||
msgstr ""
|
||||
msgstr "No tienes permiso para crear un token para este equipo."
|
||||
|
||||
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
|
||||
msgid "You don't manage billing for any organisations."
|
||||
@@ -11173,7 +11206,7 @@ msgstr "Aún no has creado ni recibido documentos. Para crear un documento, por
|
||||
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
|
||||
#: apps/remix/app/components/general/envelope/envelope-upload-button.tsx
|
||||
msgid "You have reached the limit of the number of files per envelope."
|
||||
msgstr ""
|
||||
msgstr "Has alcanzado el límite de archivos por sobre."
|
||||
|
||||
#. placeholder {0}: quota.directTemplates
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
@@ -11330,12 +11363,12 @@ msgstr "Recibirás una copia por correo electrónico del documento firmado cuand
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
msgid "You're an admin. You can enable AI features for this team right away. Everyone on the team will see AI detection once enabled."
|
||||
msgstr ""
|
||||
msgstr "Eres administrador. Puedes habilitar las funciones de IA para este equipo de inmediato. Todos los miembros del equipo verán la detección por IA una vez que esté habilitada."
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "You've made too many detection requests. Please wait a minute before trying again."
|
||||
msgstr ""
|
||||
msgstr "Has realizado demasiadas solicitudes de detección. Espera un minuto antes de intentarlo de nuevo."
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "Your Account"
|
||||
@@ -11392,7 +11425,7 @@ msgstr "Tus {0} plantillas de firma directa"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
|
||||
msgid "Your document content will be sent securely to our AI provider solely for detection and will not be stored or used for training."
|
||||
msgstr ""
|
||||
msgstr "El contenido de tu documento se enviará de forma segura a nuestro proveedor de IA únicamente para la detección y no se almacenará ni se utilizará para entrenamiento."
|
||||
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-upload.tsx
|
||||
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
|
||||
@@ -11436,7 +11469,7 @@ msgstr "Tu documento ha sido subido con éxito. Serás redirigido a la página d
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "Your document is processed securely using AI services that don't retain your data."
|
||||
msgstr ""
|
||||
msgstr "Tu documento se procesa de forma segura mediante servicios de IA que no conservan tus datos."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.document.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.document.tsx
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: fr\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-11-27 05:32\n"
|
||||
"PO-Revision-Date: 2025-12-15 02:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
@@ -68,7 +68,7 @@ msgstr "{0, plural, one {(1 caractère de trop)} other {(# caractères de trop)}
|
||||
#. placeholder {2}: table.getFilteredSelectedRowModel().rows.length
|
||||
#: packages/ui/primitives/data-table-pagination.tsx
|
||||
msgid "{0, plural, one {{1} of # row selected.} other {{2} of # rows selected.}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {{1} sur # ligne sélectionnée.} other {{2} sur # lignes sélectionnées.}}"
|
||||
|
||||
#. placeholder {0}: Math.abs(remaningLength)
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
@@ -104,7 +104,7 @@ msgstr "{0, plural, one {# dossier} other {# dossiers}}"
|
||||
#. placeholder {0}: detectedRecipients.length
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
|
||||
msgid "{0, plural, one {# recipient have been added from AI detection.} other {# recipients have been added from AI detection.}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {# destinataire a été ajouté grâce à la détection par IA.} other {# destinataires ont été ajoutés grâce à la détection par IA.}}"
|
||||
|
||||
#. placeholder {0}: template.recipients.length
|
||||
#: apps/remix/app/routes/_recipient+/d.$token+/_index.tsx
|
||||
@@ -163,7 +163,7 @@ msgstr "{0, plural, one {1 Destinataire} other {# Destinataires}}"
|
||||
#. placeholder {4}: progress.totalPages
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "{0, plural, one {Page {1} of {2} - # field found} other {Page {3} of {4} - # fields found}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Page {1} sur {2} - # champ trouvé} other {Page {3} sur {4} - # champs trouvés}}"
|
||||
|
||||
#. placeholder {0}: progress.recipientsDetected
|
||||
#. placeholder {1}: progress.pagesProcessed
|
||||
@@ -172,12 +172,12 @@ msgstr ""
|
||||
#. placeholder {4}: progress.totalPages
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "{0, plural, one {Page {1} of {2} - # recipient found} other {Page {3} of {4} - # recipients found}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Page {1} sur {2} - # destinataire trouvé} other {Page {3} sur {4} - # destinataires trouvés}}"
|
||||
|
||||
#. placeholder {0}: detectedRecipients.length
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
|
||||
msgid "{0, plural, one {Recipient added} other {Recipients added}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Destinataire ajouté} other {Destinataires ajoutés}}"
|
||||
|
||||
#. placeholder {0}: pendingRecipients.length
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
@@ -187,12 +187,12 @@ msgstr "{0, plural, one {En attente d'1 destinataire} other {En attente de # des
|
||||
#. placeholder {0}: detectedFields.length
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
msgid "{0, plural, one {We found # field in your document.} other {We found # fields in your document.}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Nous avons trouvé # champ dans votre document.} other {Nous avons trouvé # champs dans votre document.}}"
|
||||
|
||||
#. placeholder {0}: detectedRecipients.length
|
||||
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
|
||||
msgid "{0, plural, one {We found # recipient in your document.} other {We found # recipients in your document.}}"
|
||||
msgstr ""
|
||||
msgstr "{0, plural, one {Nous avons trouvé # destinataire dans votre document.} other {Nous avons trouvé # destinataires dans votre document.}}"
|
||||
|
||||
#. placeholder {0}: _(FRIENDLY_FIELD_TYPE[fieldType as FieldType])
|
||||
#. placeholder {0}: route.label
|
||||
@@ -312,7 +312,7 @@ msgstr "{MAXIMUM_PASSKEYS, plural, one {Vous ne pouvez pas avoir plus de # clé
|
||||
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
|
||||
#: apps/remix/app/components/general/envelope/envelope-upload-button.tsx
|
||||
msgid "{maximumEnvelopeItemCount, plural, one {You cannot upload more than # item per envelope.} other {You cannot upload more than # items per envelope.}}"
|
||||
msgstr ""
|
||||
msgstr "{maximumEnvelopeItemCount, plural, one {Vous ne pouvez pas téléverser plus d’un élément par enveloppe.} other {Vous ne pouvez pas téléverser plus de # éléments par enveloppe.}}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} added a field"
|
||||
@@ -547,39 +547,39 @@ msgstr "<0>{organisationName}</0> a demandé à lier votre compte Documenso actu
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> has invited you to approve this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> vous a invité à approuver ce document"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> has invited you to assist this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> vous a invité à assister ce document"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> has invited you to sign this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> vous a invité à signer ce document"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> has invited you to view this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0> vous a invité à voir ce document"
|
||||
|
||||
#. placeholder {0}: document.team?.name
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> on behalf of \"{0}\" has invited you to approve this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0>, au nom de \"{0}\", vous a invité à approuver ce document"
|
||||
|
||||
#. placeholder {0}: document.team?.name
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> on behalf of \"{0}\" has invited you to assist this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0>, au nom de \"{0}\", vous a invité à assister ce document"
|
||||
|
||||
#. placeholder {0}: document.team?.name
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> on behalf of \"{0}\" has invited you to sign this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0>, au nom de \"{0}\", vous a invité à signer ce document"
|
||||
|
||||
#. placeholder {0}: document.team?.name
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-page-view-v1.tsx
|
||||
msgid "<0>{senderName} {senderEmail}</0> on behalf of \"{0}\" has invited you to view this document"
|
||||
msgstr ""
|
||||
msgstr "<0>{senderName} {senderEmail}</0>, au nom de \"{0}\", vous a invité à voir ce document"
|
||||
|
||||
#: packages/email/templates/confirm-team-email.tsx
|
||||
msgid "<0>{teamName}</0> has requested to use your email address for their team on Documenso."
|
||||
@@ -591,7 +591,7 @@ msgstr "<0>Gestion du compte :</0> Modifiez les paramètres, permissions, et pr
|
||||
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "<0>Admins only</0> - Only admins can access and view the document"
|
||||
msgstr ""
|
||||
msgstr "<0>Administrateurs uniquement</0> - Seuls les administrateurs peuvent accéder au document et le voir"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.sso.confirmation.$token.tsx
|
||||
msgid "<0>Data access:</0> Access all data associated with your account"
|
||||
@@ -612,7 +612,7 @@ msgstr "<0>Événements | ||||