Compare commits

..

33 Commits

Author SHA1 Message Date
AntoLC
2384023d14 ✏️(project) automatic typo correction
Fix typos in the project.
2025-05-08 21:13:18 +00:00
Anthony LC
6be87ed477 🔖(patch) release 3.2.1
Fixed:
- 🐛(frontend) fix list copy paste
2025-05-07 10:27:39 +02:00
Anthony LC
c96182b3e3 🐛(frontend) fix list copy paste
When we copy paste a list, the pasted
list is not formatted correctly.
By pinning prosemirror-model to 1.25.0,
we avoid this issue.
We added "prosemirror-model" to the
ignored dependencies of Renovate to
avoid to have a bump until the patch
on the Blocknote.js side.
2025-05-07 10:25:48 +02:00
Anthony LC
e79d1d618a ⬆️(dependencies) update js dependencies 2025-05-06 11:51:24 +02:00
renovate[bot]
2691cdd4a2 ⬆️(dependencies) update python dependencies (#934) 2025-05-06 09:35:31 +00:00
Riël Notermans
05a1390bdc 📝(doc) Update env.md add AI_FEATURE_ENABLED
This is false by default.
Without this env setting on true AI will not be available in the
docs application.
The setting was missing in the env options.
2025-05-06 10:54:18 +02:00
Anthony LC
dfe8ae14fe 🐛(docker-compose) unbind the y-provider service with frontend
We cannot add new js dependency locally when we bind the
frontend with the y-provider service. It results in
"EPERM: operation not permitted" when the `node_modules`
has to be updated.
Better to remove the binding, we can add the binding
locally during development on the y-provider.
2025-05-06 10:35:59 +02:00
Anthony LC
74165f6890 🔖(minor) release 3.2.0
Added:
- 🚸(backend) make document search on title
  accent-insensitive
- 🚩 add homepage feature flag
- (settings) Allow configuring PKCE for the SSO
- 🌐(i18n) activate chinese and spanish languages
- 🔧(backend) allow overwriting the data directory
- (backend) add  `django-lasuite` dependency
  (breaking change)
- (frontend) advanced table features

Changed:
- ️(frontend) reduce unblocking time for config
- ♻️(frontend) bind UI with ability access
- ♻️(frontend) use built-in Quote block

Fixed:
- 🐛(nginx) fix 404 when accessing a doc
- 🔒️(drf) disable browsable HTML API renderer
- 🔒(frontend) enhance file download security
- 🐛(backend) race condition create doc
- 🐛(frontend) fix breaklines in custom blocks
2025-05-06 09:33:42 +02:00
Anthony LC
349cbf8eb3 🌐(i18n) update translated strings
Update translated files with new translations
2025-05-06 09:33:42 +02:00
Anthony LC
12ef1a2450 🚩(backend) default enable FRONTEND_HOMEPAGE_FEATURE_ENABLED
We decided to enable the FRONTEND_HOMEPAGE_FEATURE_ENABLED
feature flag by default.
It will not be a breaking change like that.
2025-05-05 11:54:26 +02:00
Anthony LC
9b2f7966f6 🌐(i18n) update translated strings
Update translated files with new translations
2025-05-05 11:17:58 +02:00
Anthony LC
5ad30b404d 🌐(i18n) add PO of new languages
New languages were added to Crowdin.
We import the new translations from Crowdin
to version them in the repository.
2025-05-02 16:25:50 +02:00
Anthony LC
12524f35b7 🌐(i18n) remove chinese language
We're going to make languages ​​configurable
per instance, but until we manage that, we're going
to remove Chinese from the default language list.

- Remove the chinese language from the default language
list.
- Change Spanish to Español
2025-05-02 16:25:50 +02:00
Anthony LC
f8a40cf8cc (frontend) add advanced table features
We added advanced table features to the
table editor, including:
- split / merge cells
- cell background color
- cell text color
- header

We adapted the export and brought some improvements
compare to the previous version.

The export PDF supports colspan (merge horizontally),
but does not support the rowspan (merge vertically)
for now.
2025-04-30 17:22:21 +02:00
Anthony LC
c32fdb67ac (frontend) add @blocknote/code-block
To reduce the bundles size, the highlight syntax
library is not included in blocknote core anymore.
We need to add a separate dependency in order
to have the code block syntax highlight feature.
2025-04-30 17:22:21 +02:00
Anthony LC
7f2a21cdc9 🔥(frontend) remove Quote custom block
Last Blocknote upgrade included a Quote block,
better to use their built-in one.
2025-04-30 17:22:21 +02:00
Anthony LC
4ad917906c ⬆️(dependencies) update js dependencies 2025-04-30 17:22:21 +02:00
Anthony LC
9ca79688c9 ♻️(frontend) bind ui with ability access
Some actions were not available in the frontend
but allowed in the backend, this commit binds the frontend
ui with the ability access coming from the backend.
2025-04-30 17:02:13 +02:00
Manuel Raynaud
7f0eb9117e 🔒️(drf) disable browsable HTML API renderer (#919)
The `BrowsableAPIRenderer` generates a form to test POST/PUT/... actions
and fill the FK fields with unfiltered data. This issue has been spoted
on visio and fixed suitenumerique/meet#508
2025-04-30 16:23:26 +02:00
Quentin BEY
2557c6bc77 (backend) add django-lasuite dependency
Use the OIDC backend from the new library and add settings to setup OIDC
token storage required for later calls to OIDC Resource Servers.
2025-04-29 13:15:43 +02:00
Manuel Raynaud
df173c3ce6 🔧(helmfile) personalize keycloak configuration
The keycloak configuration used in dev environment is too generic and we
can have a conflict with other project that are using the same ingress
domain. Also the namespace was missing in the keycloak extra ConfigMap
leading to creating it in the default namespace.
2025-04-28 21:41:02 +02:00
Anthony LC
b58c991c81 🐛(nginx) fix 404 when accessing a doc
We improve the nginx way to access to a specific
doc.
We stop to wait for a initial attempt that
give a 404. If we see a UUID in the url we will
redirect to the doc/[id] page. Next will then
manage the 404.
2025-04-28 21:41:02 +02:00
Martin Weinelt
96f6aeea60 🔧(backend) Allow overwriting the data directory (#893)
## Purpose

Deployments that don't rely on Docker should be given the option to use
a different data directory.

## Proposal

Allow customization of the `DATA_DIR` through an environment variable of
the same name.

If the environment variable is not set the behaviour remains the same as
before.

Signed-off-by: Martin Weinelt <hexa@darmstadt.ccc.de>
2025-04-28 15:41:28 +00:00
Nathan Panchout
9465f1a6ec 🔒(frontend) enhance file download security (#889)
## Purpose

Added a safety check for URLs in the FileDownloadButton component. Now,
before opening a URL, it verifies if the URL is safe using the isSafeUrl
function.
This prevents potentially unsafe URLs from being opened in a new tab.
2025-04-28 12:50:14 +00:00
virgile-dev
98f11ff8ac 🌐(i18n) add spanish and chinese (#884)
All the spanish and chinese translations are complete on crowdin. We
activate it in django settings and download all translations from
crowdin

Signed-off-by: virgile-deville <virgile.deville@beta.gouv.fr>
2025-04-28 12:36:34 +00:00
renovate[bot]
b29daa2d77 ⬆️(dependencies) update python dependencies (#847)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [boto3](https://redirect.github.com/boto/boto3) | `==1.37.24` ->
`==1.37.33` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/boto3/1.37.33?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/boto3/1.37.33?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/boto3/1.37.24/1.37.33?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/boto3/1.37.24/1.37.33?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [celery](https://docs.celeryq.dev/)
([source](https://redirect.github.com/celery/celery),
[changelog](https://docs.celeryq.dev/en/stable/changelog.html)) |
`==5.5.0` -> `==5.5.1` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/celery/5.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/celery/5.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/celery/5.5.0/5.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/celery/5.5.0/5.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [django](https://redirect.github.com/django/django)
([changelog](https://docs.djangoproject.com/en/stable/releases/)) |
`==5.1.8` -> `==5.2` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/django/5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/django/5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/django/5.1.8/5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/django/5.1.8/5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[django-extensions](https://redirect.github.com/django-extensions/django-extensions)
([changelog](https://redirect.github.com/django-extensions/django-extensions/blob/main/CHANGELOG.md))
| `==3.2.3` -> `==4.1` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/django-extensions/4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/django-extensions/4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/django-extensions/3.2.3/4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/django-extensions/3.2.3/4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[django-storages](https://redirect.github.com/jschneier/django-storages)
([changelog](https://redirect.github.com/jschneier/django-storages/blob/master/CHANGELOG.rst))
| `==1.14.5` -> `==1.14.6` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/django-storages/1.14.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/django-storages/1.14.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/django-storages/1.14.5/1.14.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/django-storages/1.14.5/1.14.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[drf-spectacular-sidecar](https://redirect.github.com/tfranzel/drf-spectacular-sidecar)
| `==2025.3.1` -> `==2025.4.1` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/drf-spectacular-sidecar/2025.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/drf-spectacular-sidecar/2025.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/drf-spectacular-sidecar/2025.3.1/2025.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/drf-spectacular-sidecar/2025.3.1/2025.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [ipython](https://redirect.github.com/ipython/ipython) | `==9.0.2` ->
`==9.1.0` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/ipython/9.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/ipython/9.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/ipython/9.0.2/9.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/ipython/9.0.2/9.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [lxml](https://lxml.de/)
([source](https://redirect.github.com/lxml/lxml),
[changelog](https://git.launchpad.net/lxml/plain/CHANGES.txt)) |
`==5.3.1` -> `==5.3.2` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/lxml/5.3.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/lxml/5.3.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/lxml/5.3.1/5.3.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/lxml/5.3.1/5.3.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [markdown](https://redirect.github.com/Python-Markdown/markdown)
([changelog](https://python-markdown.github.io/changelog/)) | `==3.7` ->
`==3.8` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/markdown/3.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/markdown/3.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/markdown/3.7/3.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/markdown/3.7/3.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [openai](https://redirect.github.com/openai/openai-python) |
`==1.70.0` -> `==1.73.0` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/openai/1.73.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/openai/1.73.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/openai/1.70.0/1.73.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/openai/1.70.0/1.73.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [pycrdt](https://redirect.github.com/jupyter-server/pycrdt) |
`==0.12.10` -> `==0.12.12` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/pycrdt/0.12.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/pycrdt/0.12.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/pycrdt/0.12.10/0.12.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/pycrdt/0.12.10/0.12.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [pytest-cov](https://redirect.github.com/pytest-dev/pytest-cov)
([changelog](https://pytest-cov.readthedocs.io/en/latest/changelog.html))
| `==6.0.0` -> `==6.1.1` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/pytest-cov/6.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/pytest-cov/6.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/pytest-cov/6.0.0/6.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/pytest-cov/6.0.0/6.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [pytest-django](https://redirect.github.com/pytest-dev/pytest-django)
([changelog](https://pytest-django.readthedocs.io/en/latest/changelog.html))
| `==4.10.0` -> `==4.11.1` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/pytest-django/4.11.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/pytest-django/4.11.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/pytest-django/4.10.0/4.11.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/pytest-django/4.10.0/4.11.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [ruff](https://docs.astral.sh/ruff)
([source](https://redirect.github.com/astral-sh/ruff),
[changelog](https://redirect.github.com/astral-sh/ruff/blob/main/CHANGELOG.md))
| `==0.11.2` -> `==0.11.5` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/ruff/0.11.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/ruff/0.11.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/ruff/0.11.2/0.11.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/ruff/0.11.2/0.11.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [sentry-sdk](https://redirect.github.com/getsentry/sentry-python)
([changelog](https://redirect.github.com/getsentry/sentry-python/blob/master/CHANGELOG.md))
| `==2.25.0` -> `==2.25.1` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/sentry-sdk/2.25.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/sentry-sdk/2.25.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/sentry-sdk/2.25.0/2.25.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/sentry-sdk/2.25.0/2.25.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>boto/boto3 (boto3)</summary>

###
[`v1.37.33`](https://redirect.github.com/boto/boto3/blob/HEAD/CHANGELOG.rst#13733)

[Compare
Source](https://redirect.github.com/boto/boto3/compare/1.37.32...1.37.33)

\=======

- api-change:`connect-contact-lens`: \[`botocore`] Making sentiment
optional for ListRealtimeContactAnalysisSegments Response depending on
conversational analytics configuration
- api-change:`datazone`: \[`botocore`] Raise hard limit of authorized
principals per SubscriptionTarget from 10 to 20.
- api-change:`detective`: \[`botocore`] Add support for Detective
DualStack endpoints
- api-change:`dynamodb`: \[`botocore`] Doc only update for API
descriptions.
- api-change:`marketplace-entitlement`: \[`botocore`] Add support for
Marketplace Entitlement Service dual-stack endpoints for CN and GOV
regions
- api-change:`meteringmarketplace`: \[`botocore`] Add support for
Marketplace Metering Service dual-stack endpoints for CN regions
- api-change:`pcs`: \[`botocore`] Changed the minimum length of
clusterIdentifier, computeNodeGroupIdentifier, and queueIdentifier to 3.
- api-change:`verifiedpermissions`: \[`botocore`] Adds deletion
protection support to policy stores. Deletion protection is disabled by
default, can be enabled via the CreatePolicyStore or UpdatePolicyStore
APIs, and is visible in GetPolicyStore.
- bugfix:`download_fileobj`: Fileobj provided in append mode will no
longer allow concurrent writes to preserve data integrity.

###
[`v1.37.32`](https://redirect.github.com/boto/boto3/blob/HEAD/CHANGELOG.rst#13732)

[Compare
Source](https://redirect.github.com/boto/boto3/compare/1.37.31...1.37.32)

\=======

- api-change:`application-autoscaling`: \[`botocore`] Application Auto
Scaling now supports horizontal scaling for Elasticache Memcached
self-designed clusters using target tracking scaling policies and
scheduled scaling.
- api-change:`elasticache`: \[`botocore`] AWS ElastiCache SDK now
supports using MemcachedUpgradeConfig parameter with ModifyCacheCluster
API to enable updating Memcached cache node types. Please refer to
updated AWS ElastiCache public documentation for detailed information on
API usage and implementation.
- api-change:`m2`: \[`botocore`] Introduce three new APIs:
CreateDataSetExportTask, GetDataSetExportTask and
ListDataSetExportHistory. Add support for batch restart for Blu Age
applications.
- api-change:`medialive`: \[`botocore`] AWS Elemental MediaLive /
Features : Add support for CMAF Ingest CaptionLanguageMappings,
TimedMetadataId3 settings, and Link InputResolution.
- api-change:`qbusiness`: \[`botocore`] Adds functionality to
enable/disable a new Q Business Hallucination Reduction feature. If
enabled, Q Business will detect and attempt to remove Hallucinations
from certain Chat requests.
- api-change:`quicksight`: \[`botocore`] Add support to analysis and
sheet level highlighting in QuickSight.

###
[`v1.37.31`](https://redirect.github.com/boto/boto3/blob/HEAD/CHANGELOG.rst#13731)

[Compare
Source](https://redirect.github.com/boto/boto3/compare/1.37.30...1.37.31)

\=======

- api-change:`controlcatalog`: \[`botocore`] The GetControl API now
surfaces a control's Severity, CreateTime, and Identifier for a
control's Implementation. The ListControls API now surfaces a control's
Behavior, Severity, CreateTime, and Identifier for a control's
Implementation.
- api-change:`dynamodb`: \[`botocore`] Documentation update for
secondary indexes and Create_Table.
- api-change:`glue`: \[`botocore`] The TableOptimizer APIs in AWS Glue
now return the DpuHours field in each TableOptimizerRun, providing
clients visibility to the DPU-hours used for billing in managed Apache
Iceberg table compaction optimization.
- api-change:`groundstation`: \[`botocore`] Support tagging Agents and
adjust input field validations
- api-change:`transfer`: \[`botocore`] This launch includes 2
enhancements to SFTP connectors user-experience: 1) Customers can
self-serve concurrent connections setting for their connectors, and 2)
Customers can discover the public host key of remote servers using their
SFTP connectors.

###
[`v1.37.30`](https://redirect.github.com/boto/boto3/blob/HEAD/CHANGELOG.rst#13730)

[Compare
Source](https://redirect.github.com/boto/boto3/compare/1.37.29...1.37.30)

\=======

- api-change:`bedrock-runtime`: \[`botocore`] This release introduces
our latest bedrock runtime API, InvokeModelWithBidirectionalStream. The
API supports both input and output streams and is supported by only
HTTP2.0.
- api-change:`ce`: \[`botocore`] This release supports Pagination traits
on Cost Anomaly Detection APIs.
- api-change:`cost-optimization-hub`: \[`botocore`] This release adds
resource type "MemoryDbReservedInstances" and resource type
"DynamoDbReservedCapacity" to the GetRecommendation,
ListRecommendations, and ListRecommendationSummaries APIs to support new
MemoryDB and DynamoDB RI recommendations.
- api-change:`iotfleetwise`: \[`botocore`] This release adds the option
to update the strategy of state templates already associated to a
vehicle, without the need to remove and re-add them.
- api-change:`securityhub`: \[`botocore`] Documentation updates for AWS
Security Hub.
- api-change:`storagegateway`: \[`botocore`] Added new
ActiveDirectoryStatus value, ListCacheReports paginator, and support for
longer pagination tokens.
- api-change:`taxsettings`: \[`botocore`] Uzbekistan Launch on
TaxSettings Page

###
[`v1.37.29`](https://redirect.github.com/boto/boto3/blob/HEAD/CHANGELOG.rst#13729)

[Compare
Source](https://redirect.github.com/boto/boto3/compare/1.37.28...1.37.29)

\=======

- api-change:`bedrock`: \[`botocore`] New options for how to handle
harmful content detected by Amazon Bedrock Guardrails.
- api-change:`bedrock-runtime`: \[`botocore`] New options for how to
handle harmful content detected by Amazon Bedrock Guardrails.
- api-change:`codebuild`: \[`botocore`] AWS CodeBuild now offers an
enhanced debugging experience.
- api-change:`glue`: \[`botocore`] Add input validations for multiple
Glue APIs
- api-change:`medialive`: \[`botocore`] AWS Elemental MediaLive now
supports SDI inputs to MediaLive Anywhere Channels in workflows that use
AWS SDKs.
- api-change:`personalize`: \[`botocore`] Add support for eventsConfig
for CreateSolution, UpdateSolution, DescribeSolution,
DescribeSolutionVersion. Add support for GetSolutionMetrics to return
weighted NDCG metrics when eventsConfig is enabled for the solution.
- api-change:`transfer`: \[`botocore`] This launch enables customers to
manage contents of their remote directories, by deleting old files or
moving files to archive folders in remote servers once they have been
retrieved. Customers will be able to automate the process using
event-driven architecture.

###
[`v1.37.28`](https://redirect.github.com/boto/boto3/blob/HEAD/CHANGELOG.rst#13728)

[Compare
Source](https://redirect.github.com/boto/boto3/compare/1.37.27...1.37.28)

\=======

- api-change:`ds-data`: \[`botocore`] Doc only update - fixed broken
links.
-   api-change:`ec2`: \[`botocore`] Doc-only updates for Amazon EC2
- api-change:`events`: \[`botocore`] Amazon EventBridge adds support for
customer-managed keys on Archives and validations for two fields:
eventSourceArn and kmsKeyIdentifier.
- api-change:`s3control`: \[`botocore`] Updated max size of Prefixes
parameter of Scope data type.

###
[`v1.37.27`](https://redirect.github.com/boto/boto3/blob/HEAD/CHANGELOG.rst#13727)

[Compare
Source](https://redirect.github.com/boto/boto3/compare/1.37.26...1.37.27)

\=======

- api-change:`bedrock-agent`: \[`botocore`] Added optional
"customMetadataField" for Amazon Aurora knowledge bases, allowing
single-column metadata. Also added optional "textIndexName" for MongoDB
Atlas knowledge bases, enabling hybrid search support.
- api-change:`chime-sdk-voice`: \[`botocore`] Added FOC date as an
attribute of PhoneNumberOrder, added AccessDeniedException as a possible
return type of ValidateE911Address
- api-change:`mailmanager`: \[`botocore`] Add support for Dual_Stack and
PrivateLink types of IngressPoint. For configuration requests, SES Mail
Manager will now accept both IPv4/IPv6 dual-stack endpoints and AWS
PrivateLink VPC endpoints for email receiving.
- api-change:`opensearch`: \[`botocore`] Improve descriptions for
various API commands and data types.
- api-change:`route53`: \[`botocore`] Added us-gov-east-1 and
us-gov-west-1 as valid Latency Based Routing regions for
change-resource-record-sets.
- api-change:`sagemaker`: \[`botocore`] Adds support for i3en, m7i, r7i
instance types for SageMaker Hyperpod
- api-change:`sesv2`: \[`botocore`] This release enables customers to
provide attachments in the SESv2 SendEmail and SendBulkEmail APIs.
- api-change:`transcribe`: \[`botocore`] This Feature Adds Support for
the "zh-HK" Locale for Batch Operations
- enhancement:Eventstream: \[`botocore`] The event streams maximum
payload size is now required to be 24Mb or less.

###
[`v1.37.26`](https://redirect.github.com/boto/boto3/blob/HEAD/CHANGELOG.rst#13726)

[Compare
Source](https://redirect.github.com/boto/boto3/compare/1.37.25...1.37.26)

\=======

- api-change:`application-signals`: \[`botocore`] Application Signals
now supports creating Service Level Objectives on service dependencies.
Users can now create or update SLOs on discovered service dependencies
to monitor their standard application metrics.
- api-change:`codebuild`: \[`botocore`] This release adds support for
environment type WINDOWS_SERVER\_2022\_CONTAINER in ProjectEnvironment
- api-change:`ecr`: \[`botocore`] Fix for customer issues related to AWS
account ID and size limitation for token.
- api-change:`ecs`: \[`botocore`] This is an Amazon ECS documentation
only update to address various tickets.
- api-change:`lexv2-models`: \[`botocore`] Release feature of
errorlogging for lex bot, customer can config this feature in bot
version to generate log for error exception which helps debug
- api-change:`medialive`: \[`botocore`] Added support for SMPTE 2110
inputs when running a channel in a MediaLive Anywhere cluster. This
feature enables ingestion of SMPTE 2110-compliant video, audio, and
ancillary streams by reading SDP files that AWS Elemental MediaLive can
retrieve from a network source.

###
[`v1.37.25`](https://redirect.github.com/boto/boto3/blob/HEAD/CHANGELOG.rst#13725)

[Compare
Source](https://redirect.github.com/boto/boto3/compare/1.37.24...1.37.25)

\=======

- api-change:`cleanrooms`: \[`botocore`] This release adds support for
updating the analytics engine of a collaboration.
- api-change:`sagemaker`: \[`botocore`] Added tagging support for
SageMaker notebook instance lifecycle configurations

</details>

<details>
<summary>celery/celery (celery)</summary>

###
[`v5.5.1`](https://redirect.github.com/celery/celery/blob/HEAD/Changelog.rst#551)

[Compare
Source](https://redirect.github.com/celery/celery/compare/v5.5.0...v5.5.1)

\=====

:release-date: 2025-04-08
:release-by: Tomer Nosrati

What's Changed

```

- Fixed "AttributeError: list object has no attribute strip" with quorum queues and failover brokers (#&#8203;9657)
- Prepare for release: v5.5.1 (#&#8203;9660)

.. _version-5.5.0:
```

</details>

<details>
<summary>django/django (django)</summary>

###
[`v5.2`](https://redirect.github.com/django/django/compare/5.1.8...5.2)

[Compare
Source](https://redirect.github.com/django/django/compare/5.1.8...5.2)

</details>

<details>
<summary>django-extensions/django-extensions
(django-extensions)</summary>

###
[`v4.1`](https://redirect.github.com/django-extensions/django-extensions/blob/HEAD/CHANGELOG.md#41)

[Compare
Source](https://redirect.github.com/django-extensions/django-extensions/compare/4.0...4.1)

Changes:

- Add: show_permissions command
([#&#8203;1920](https://redirect.github.com/django-extensions/django-extensions/issues/1920))
- Improvement: graph_models, style per app
([#&#8203;1848](https://redirect.github.com/django-extensions/django-extensions/issues/1848))
- Fix: JSONField, bulk_update's
([#&#8203;1924](https://redirect.github.com/django-extensions/django-extensions/issues/1924))

###
[`v4.0`](https://redirect.github.com/django-extensions/django-extensions/blob/HEAD/CHANGELOG.md#40)

[Compare
Source](https://redirect.github.com/django-extensions/django-extensions/compare/3.2.3...4.0)

Changes:

-   Improvement: Support for Python 3.12 and 3.13
-   Improvement: Support for Django 5.x
-   Improvement: Switch from setup.{cfg,py} to pyproject.toml
- Improvement: graph_models, Add option to display field choices in
graph_models
([#&#8203;1854](https://redirect.github.com/django-extensions/django-extensions/issues/1854))
- Improvement: graph_models, Add webp support
([#&#8203;1857](https://redirect.github.com/django-extensions/django-extensions/issues/1857))
- Improvement: graph_models, Support for ordering edges on
pydot/dot/graphviz
([#&#8203;1914](https://redirect.github.com/django-extensions/django-extensions/issues/1914))
- Improvement: mail_debug, Update mail_debug command to use aiosmtpd
([#&#8203;1880](https://redirect.github.com/django-extensions/django-extensions/issues/1880))
- Improvement: shell_plus, Improve error message for missing import
([#&#8203;1898](https://redirect.github.com/django-extensions/django-extensions/issues/1898))
- Improvement: reset_db, Add reset_db support for django_tenants
([#&#8203;1855](https://redirect.github.com/django-extensions/django-extensions/issues/1855))
- Improvement: docs, various improvements
([#&#8203;1852](https://redirect.github.com/django-extensions/django-extensions/issues/1852),
[#&#8203;1888](https://redirect.github.com/django-extensions/django-extensions/issues/1888),
[#&#8203;1882](https://redirect.github.com/django-extensions/django-extensions/issues/1882),
[#&#8203;1901](https://redirect.github.com/django-extensions/django-extensions/issues/1901),
[#&#8203;1912](https://redirect.github.com/django-extensions/django-extensions/issues/1912),
[#&#8203;1913](https://redirect.github.com/django-extensions/django-extensions/issues/1913))
- Improvement: jobs, Handle non-package modules when looking for job
definitions
([#&#8203;1887](https://redirect.github.com/django-extensions/django-extensions/issues/1887))
- Improvement: Add django-prometheus DB backends support
([#&#8203;1800](https://redirect.github.com/django-extensions/django-extensions/issues/1800))
- Improvement: Call post_command when the command raises an unhandled
exception
([#&#8203;1837](https://redirect.github.com/django-extensions/django-extensions/issues/1837))
- Fix: sqldiff, do not consider ('serial', 'integer') nor ('bigserial',
'bigint') as a `field-type-differ`
([#&#8203;1867](https://redirect.github.com/django-extensions/django-extensions/issues/1867))
- Fix: shell_plus, Fix start up order and add history
([#&#8203;1869](https://redirect.github.com/django-extensions/django-extensions/issues/1869))
- Remove pipchecker and associated tests
([#&#8203;1906](https://redirect.github.com/django-extensions/django-extensions/issues/1906))
- Following Django's release numbering style more closely (see
https://docs.djangoproject.com/en/5.2/internals/release-process/ )

</details>

<details>
<summary>jschneier/django-storages (django-storages)</summary>

###
[`v1.14.6`](https://redirect.github.com/jschneier/django-storages/compare/1.14.5...1.14.6)

[Compare
Source](https://redirect.github.com/jschneier/django-storages/compare/1.14.5...1.14.6)

</details>

<details>
<summary>tfranzel/drf-spectacular-sidecar
(drf-spectacular-sidecar)</summary>

###
[`v2025.4.1`](https://redirect.github.com/tfranzel/drf-spectacular-sidecar/compare/2025.3.1...2025.4.1)

[Compare
Source](https://redirect.github.com/tfranzel/drf-spectacular-sidecar/compare/2025.3.1...2025.4.1)

</details>

<details>
<summary>ipython/ipython (ipython)</summary>

###
[`v9.1.0`](https://redirect.github.com/ipython/ipython/compare/9.0.2...9.1.0)

[Compare
Source](https://redirect.github.com/ipython/ipython/compare/9.0.2...9.1.0)

</details>

<details>
<summary>lxml/lxml (lxml)</summary>

###
[`v5.3.2`](https://redirect.github.com/lxml/lxml/blob/HEAD/CHANGES.txt#532-2025-04-05)

[Compare
Source](https://redirect.github.com/lxml/lxml/compare/lxml-5.3.1...lxml-5.3.2)

\==================

This release resolves CVE-2025-24928 as described in
https://gitlab.gnome.org/GNOME/libxml2/-/issues/847

## Bugs fixed

-   Binary wheels use libxml2 2.12.10 and libxslt 1.1.42.

- Binary wheels for Windows use a patched libxml2 2.11.9 and libxslt
1.1.39.

</details>

<details>
<summary>Python-Markdown/markdown (markdown)</summary>

###
[`v3.8`](https://redirect.github.com/Python-Markdown/markdown/releases/tag/3.8)

[Compare
Source](https://redirect.github.com/Python-Markdown/markdown/compare/3.7...3.8)

##### Changed

- DRY fix in `abbr` extension by introducing method `create_element`
([#&#8203;1483](https://redirect.github.com/Python-Markdown/markdown/issues/1483)).
-   Clean up test directory by removing some redundant tests and port
    non-redundant cases to the newer test framework.
- Improved performance of the raw HTML post-processor
([#&#8203;1510](https://redirect.github.com/Python-Markdown/markdown/issues/1510)).

##### Fixed

- Backslash Unescape IDs set via `attr_list` on `toc`
([#&#8203;1493](https://redirect.github.com/Python-Markdown/markdown/issues/1493)).
- Ensure `md_in_html` processes content inside "markdown" blocks as they
are
parsed outside of "markdown" blocks to keep things more consistent for
third-party extensions
([#&#8203;1503](https://redirect.github.com/Python-Markdown/markdown/issues/1503)).
- `md_in_html` handle tags within inline code blocks better
([#&#8203;1075](https://redirect.github.com/Python-Markdown/markdown/issues/1075)).
- `md_in_html` fix handling of one-liner block HTML handling
([#&#8203;1074](https://redirect.github.com/Python-Markdown/markdown/issues/1074)).
- Ensure `<center>` is treated like a block-level element
([#&#8203;1481](https://redirect.github.com/Python-Markdown/markdown/issues/1481)).
- Ensure that `abbr` extension respects `AtomicString` and does not
process
perceived abbreviations in these strings
([#&#8203;1512](https://redirect.github.com/Python-Markdown/markdown/issues/1512)).
- Ensure `smarty` extension correctly renders nested closing quotes
([#&#8203;1514](https://redirect.github.com/Python-Markdown/markdown/issues/1514)).

</details>

<details>
<summary>openai/openai-python (openai)</summary>

###
[`v1.73.0`](https://redirect.github.com/openai/openai-python/blob/HEAD/CHANGELOG.md#1730-2025-04-12)

[Compare
Source](https://redirect.github.com/openai/openai-python/compare/v1.72.0...v1.73.0)

Full Changelog:
[v1.72.0...v1.73.0](https://redirect.github.com/openai/openai-python/compare/v1.72.0...v1.73.0)

##### Features

- **api:** manual updates
([a3253dd](a3253dd798))

##### Bug Fixes

- **perf:** optimize some hot paths
([f79d39f](f79d39fbca))
- **perf:** skip traversing types for NotGiven values
([28d220d](28d220de3b))

##### Chores

- **internal:** expand CI branch coverage
([#&#8203;2295](https://redirect.github.com/openai/openai-python/issues/2295))
([0ae783b](0ae783b991))
- **internal:** reduce CI branch coverage
([2fb7d42](2fb7d425cd))
- slight wording improvement in README
([#&#8203;2291](https://redirect.github.com/openai/openai-python/issues/2291))
([e020759](e0207598d1))
- workaround build errors
([4e10c96](4e10c96a48))

###
[`v1.72.0`](https://redirect.github.com/openai/openai-python/blob/HEAD/CHANGELOG.md#1720-2025-04-08)

[Compare
Source](https://redirect.github.com/openai/openai-python/compare/v1.71.0...v1.72.0)

Full Changelog:
[v1.71.0...v1.72.0](https://redirect.github.com/openai/openai-python/compare/v1.71.0...v1.72.0)

##### Features

- **api:** Add evalapi to sdk
([#&#8203;2287](https://redirect.github.com/openai/openai-python/issues/2287))
([35262fc](35262fcef6))

##### Chores

- **internal:** fix examples
([#&#8203;2288](https://redirect.github.com/openai/openai-python/issues/2288))
([39defd6](39defd61e8))
- **internal:** skip broken test
([#&#8203;2289](https://redirect.github.com/openai/openai-python/issues/2289))
([e2c9bce](e2c9bce1f5))
- **internal:** slight transform perf improvement
([#&#8203;2284](https://redirect.github.com/openai/openai-python/issues/2284))
([746174f](746174fae7))
- **tests:** improve enum examples
([#&#8203;2286](https://redirect.github.com/openai/openai-python/issues/2286))
([c9dd81c](c9dd81ce02))

###
[`v1.71.0`](https://redirect.github.com/openai/openai-python/blob/HEAD/CHANGELOG.md#1710-2025-04-07)

[Compare
Source](https://redirect.github.com/openai/openai-python/compare/v1.70.0...v1.71.0)

Full Changelog:
[v1.70.0...v1.71.0](https://redirect.github.com/openai/openai-python/compare/v1.70.0...v1.71.0)

##### Features

- **api:** manual updates
([bf8b4b6](bf8b4b6990))
- **api:** manual updates
([3e37aa3](3e37aa3e15))
- **api:** manual updates
([dba9b65](dba9b656fa))
- **api:** manual updates
([f0c463b](f0c463b478))

##### Chores

- **deps:** allow websockets v15
([#&#8203;2281](https://redirect.github.com/openai/openai-python/issues/2281))
([19c619e](19c619ea95))
- **internal:** only run examples workflow in main repo
([#&#8203;2282](https://redirect.github.com/openai/openai-python/issues/2282))
([c3e0927](c3e0927d3f))
- **internal:** remove trailing character
([#&#8203;2277](https://redirect.github.com/openai/openai-python/issues/2277))
([5a21a2d](5a21a2d799))
- Remove deprecated/unused remote spec feature
([23f76eb](23f76eb0b9))

</details>

<details>
<summary>jupyter-server/pycrdt (pycrdt)</summary>

###
[`v0.12.12`](https://redirect.github.com/jupyter-server/pycrdt/blob/HEAD/CHANGELOG.md#01212)

[Compare
Source](https://redirect.github.com/jupyter-server/pycrdt/compare/0.12.11...0.12.12)

-   Add doc and shared type `events()` async event iterator.
-   Fix deadlock while getting root type from within transaction.

###
[`v0.12.11`](https://redirect.github.com/jupyter-server/pycrdt/blob/HEAD/CHANGELOG.md#01211)

[Compare
Source](https://redirect.github.com/jupyter-server/pycrdt/compare/0.12.10...0.12.11)

-   Upgrade `pyo3` to v0.24.1.

</details>

<details>
<summary>pytest-dev/pytest-cov (pytest-cov)</summary>

###
[`v6.1.1`](https://redirect.github.com/pytest-dev/pytest-cov/blob/HEAD/CHANGELOG.rst#611-2025-04-05)

[Compare
Source](https://redirect.github.com/pytest-dev/pytest-cov/compare/v6.1.0...v6.1.1)

- Fixed breakage that occurs when `--cov-context` and the `no_cover`
marker are used together.

###
[`v6.1.0`](https://redirect.github.com/pytest-dev/pytest-cov/blob/HEAD/CHANGELOG.rst#610-2025-04-01)

[Compare
Source](https://redirect.github.com/pytest-dev/pytest-cov/compare/v6.0.0...v6.1.0)

- Change terminal output to use full width lines for the coverage
header.
Contributed by Tsvika Shapira in `#&#8203;678
<https://github.com/pytest-dev/pytest-cov/pull/678>`\_.
- Removed unnecessary CovFailUnderWarning. Fixes `#&#8203;675
<https://github.com/pytest-dev/pytest-cov/issues/675>`\_.
- Fixed the term report not using the precision specified via
`--cov-precision`.

</details>

<details>
<summary>pytest-dev/pytest-django (pytest-django)</summary>

###
[`v4.11.1`](https://redirect.github.com/pytest-dev/pytest-django/releases/tag/v4.11.1)

[Compare
Source](https://redirect.github.com/pytest-dev/pytest-django/compare/v4.11.0...v4.11.1)


https://github.com/pytest-dev/pytest-django/blob/main/docs/changelog.rst#v4111-2025-04-03

###
[`v4.11.0`](https://redirect.github.com/pytest-dev/pytest-django/releases/tag/v4.11.0)

[Compare
Source](https://redirect.github.com/pytest-dev/pytest-django/compare/v4.10.0...v4.11.0)


https://github.com/pytest-dev/pytest-django/blob/main/docs/changelog.rst#v4110-2025-04-01

</details>

<details>
<summary>astral-sh/ruff (ruff)</summary>

###
[`v0.11.5`](https://redirect.github.com/astral-sh/ruff/blob/HEAD/CHANGELOG.md#0115)

[Compare
Source](https://redirect.github.com/astral-sh/ruff/compare/0.11.4...0.11.5)

##### Preview features

- \[`airflow`] Add missing `AIR302` attribute check
([#&#8203;17115](https://redirect.github.com/astral-sh/ruff/pull/17115))
- \[`airflow`] Expand module path check to individual symbols (`AIR302`)
([#&#8203;17278](https://redirect.github.com/astral-sh/ruff/pull/17278))
- \[`airflow`] Extract `AIR312` from `AIR302` rules (`AIR302`, `AIR312`)
([#&#8203;17152](https://redirect.github.com/astral-sh/ruff/pull/17152))
- \[`airflow`] Update oudated `AIR301`, `AIR302` rules
([#&#8203;17123](https://redirect.github.com/astral-sh/ruff/pull/17123))
- \[syntax-errors] Async comprehension in sync comprehension
([#&#8203;17177](https://redirect.github.com/astral-sh/ruff/pull/17177))
- \[syntax-errors] Check annotations in annotated assignments
([#&#8203;17283](https://redirect.github.com/astral-sh/ruff/pull/17283))
- \[syntax-errors] Extend annotation checks to `await`
([#&#8203;17282](https://redirect.github.com/astral-sh/ruff/pull/17282))

##### Bug fixes

- \[`flake8-pie`] Avoid false positive for multiple assignment with
`auto()` (`PIE796`)
([#&#8203;17274](https://redirect.github.com/astral-sh/ruff/pull/17274))

##### Rule changes

- \[`ruff`] Fix `RUF100` to detect unused file-level `noqa` directives
with specific codes
([#&#8203;17042](https://redirect.github.com/astral-sh/ruff/issues/17042))
([#&#8203;17061](https://redirect.github.com/astral-sh/ruff/pull/17061))
- \[`flake8-pytest-style`] Avoid false positive for legacy form of
`pytest.raises` (`PT011`)
([#&#8203;17231](https://redirect.github.com/astral-sh/ruff/pull/17231))

##### Documentation

- Fix formatting of "See Style Guide" link
([#&#8203;17272](https://redirect.github.com/astral-sh/ruff/pull/17272))

###
[`v0.11.4`](https://redirect.github.com/astral-sh/ruff/blob/HEAD/CHANGELOG.md#0114)

[Compare
Source](https://redirect.github.com/astral-sh/ruff/compare/0.11.3...0.11.4)

##### Preview features

- \[`ruff`] Implement `invalid-rule-code` as `RUF102`
([#&#8203;17138](https://redirect.github.com/astral-sh/ruff/pull/17138))
- \[syntax-errors] Detect duplicate keys in `match` mapping patterns
([#&#8203;17129](https://redirect.github.com/astral-sh/ruff/pull/17129))
- \[syntax-errors] Detect duplicate attributes in `match` class patterns
([#&#8203;17186](https://redirect.github.com/astral-sh/ruff/pull/17186))
- \[syntax-errors] Detect invalid syntax in annotations
([#&#8203;17101](https://redirect.github.com/astral-sh/ruff/pull/17101))

##### Bug fixes

- \[syntax-errors] Fix multiple assignment error for class fields in
`match` patterns
([#&#8203;17184](https://redirect.github.com/astral-sh/ruff/pull/17184))
- Don't skip visiting non-tuple slice in `typing.Annotated` subscripts
([#&#8203;17201](https://redirect.github.com/astral-sh/ruff/pull/17201))

###
[`v0.11.3`](https://redirect.github.com/astral-sh/ruff/blob/HEAD/CHANGELOG.md#0113)

[Compare
Source](https://redirect.github.com/astral-sh/ruff/compare/0.11.2...0.11.3)

##### Preview features

- \[`airflow`] Add more autofixes for `AIR302`
([#&#8203;16876](https://redirect.github.com/astral-sh/ruff/pull/16876),
[#&#8203;16977](https://redirect.github.com/astral-sh/ruff/pull/16977),
[#&#8203;16976](https://redirect.github.com/astral-sh/ruff/pull/16976),
[#&#8203;16965](https://redirect.github.com/astral-sh/ruff/pull/16965))
- \[`airflow`] Move `AIR301` to `AIR002`
([#&#8203;16978](https://redirect.github.com/astral-sh/ruff/pull/16978))
- \[`airflow`] Move `AIR302` to `AIR301` and `AIR303` to `AIR302`
([#&#8203;17151](https://redirect.github.com/astral-sh/ruff/pull/17151))
- \[`flake8-bandit`] Mark `str` and `list[str]` literals as trusted
input (`S603`)
([#&#8203;17136](https://redirect.github.com/astral-sh/ruff/pull/17136))
- \[`ruff`] Support slices in `RUF005`
([#&#8203;17078](https://redirect.github.com/astral-sh/ruff/pull/17078))
- \[syntax-errors] Start detecting compile-time syntax errors
([#&#8203;16106](https://redirect.github.com/astral-sh/ruff/pull/16106))
- \[syntax-errors] Duplicate type parameter names
([#&#8203;16858](https://redirect.github.com/astral-sh/ruff/pull/16858))
- \[syntax-errors] Irrefutable `case` pattern before final case
([#&#8203;16905](https://redirect.github.com/astral-sh/ruff/pull/16905))
- \[syntax-errors] Multiple assignments in `case` pattern
([#&#8203;16957](https://redirect.github.com/astral-sh/ruff/pull/16957))
- \[syntax-errors] Single starred assignment target
([#&#8203;17024](https://redirect.github.com/astral-sh/ruff/pull/17024))
- \[syntax-errors] Starred expressions in `return`, `yield`, and `for`
([#&#8203;17134](https://redirect.github.com/astral-sh/ruff/pull/17134))
- \[syntax-errors] Store to or delete `__debug__`
([#&#8203;16984](https://redirect.github.com/astral-sh/ruff/pull/16984))

##### Bug fixes

- Error instead of `panic!` when running Ruff from a deleted directory
([#&#8203;16903](https://redirect.github.com/astral-sh/ruff/issues/16903))
([#&#8203;17054](https://redirect.github.com/astral-sh/ruff/pull/17054))
- \[syntax-errors] Fix false positive for parenthesized tuple index
([#&#8203;16948](https://redirect.github.com/astral-sh/ruff/pull/16948))

##### CLI

- Check `pyproject.toml` correctly when it is passed via stdin
([#&#8203;16971](https://redirect.github.com/astral-sh/ruff/pull/16971))

##### Configuration

- \[`flake8-import-conventions`] Add import `numpy.typing as npt` to
default `flake8-import-conventions.aliases`
([#&#8203;17133](https://redirect.github.com/astral-sh/ruff/pull/17133))

##### Documentation

- \[`refurb`] Document why `UserDict`, `UserList`, and `UserString` are
preferred over `dict`, `list`, and `str` (`FURB189`)
([#&#8203;16927](https://redirect.github.com/astral-sh/ruff/pull/16927))

</details>

<details>
<summary>getsentry/sentry-python (sentry-sdk)</summary>

###
[`v2.25.1`](https://redirect.github.com/getsentry/sentry-python/blob/HEAD/CHANGELOG.md#2251)

[Compare
Source](https://redirect.github.com/getsentry/sentry-python/compare/2.25.0...2.25.1)

##### Various fixes & improvements

- fix(logs): Add a class which batches groups of logs together.
([#&#8203;4229](https://redirect.github.com/getsentry/sentry-python/issues/4229))
by [@&#8203;colin-sentry](https://redirect.github.com/colin-sentry)
- fix(logs): Use repr instead of json for message and arguments
([#&#8203;4227](https://redirect.github.com/getsentry/sentry-python/issues/4227))
by [@&#8203;colin-sentry](https://redirect.github.com/colin-sentry)
- fix(logs): Debug output from Sentry logs should always be `debug`
level.
([#&#8203;4224](https://redirect.github.com/getsentry/sentry-python/issues/4224))
by [@&#8203;antonpirker](https://redirect.github.com/antonpirker)
- fix(ai): Do not consume anthropic streaming stop
([#&#8203;4232](https://redirect.github.com/getsentry/sentry-python/issues/4232))
by [@&#8203;colin-sentry](https://redirect.github.com/colin-sentry)
- fix(spotlight): Do not spam sentry_sdk.warnings logger w/ Spotlight
([#&#8203;4219](https://redirect.github.com/getsentry/sentry-python/issues/4219))
by [@&#8203;BYK](https://redirect.github.com/BYK)
- fix(docs): fixed code snippet
([#&#8203;4218](https://redirect.github.com/getsentry/sentry-python/issues/4218))
by [@&#8203;antonpirker](https://redirect.github.com/antonpirker)
- build(deps): bump actions/create-github-app-token from 1.11.7 to
1.12.0
([#&#8203;4214](https://redirect.github.com/getsentry/sentry-python/issues/4214))
by [@&#8203;dependabot](https://redirect.github.com/dependabot)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 7am on monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/suitenumerique/docs).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yMjcuMyIsInVwZGF0ZWRJblZlciI6IjM5LjIzOC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiLCJub0NoYW5nZUxvZyJdfQ==-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Manuel Raynaud <manu@raynaud.io>
2025-04-28 14:05:52 +02:00
Tom Hubrecht
5cdbdbf215 (settings) Allow configuring PKCE for the SSO (#886)
C.f.
https://mozilla-django-oidc.readthedocs.io/en/latest/settings.html#OIDC_USE_PKCE

## Purpose

Add pkce settings

## Proposal
Get the settings from the environment

Signed-off-by: Tom Hubrecht <github@mail.hubrecht.ovh>
2025-04-28 12:54:30 +02:00
Anthony LC
5268699d50 ⬆️(dependencies) update js dependencies 2025-04-23 11:43:50 +02:00
virgile-dev
cdafe6fd33 📝(readme) update xl packages info (#885)
Info message so people fulfill their licencing obligations

Signed-off-by: virgile-deville <virgile.deville@beta.gouv.fr>
2025-04-22 13:57:45 +00:00
Anthony LC
4307b4f433 🐛(backend) race condition create doc
When 2 docs are created almost at the same time,
the second one will fail because the first one.
We get a unicity error on the path key already
used ("impress_document_path_key").
To fix this issue, we will lock the table the
time to create the document, the next query will
wait for the lock to be released.
2025-04-22 11:43:29 +02:00
Anthony LC
3bf33d202a ️(frontend) reduce unblocking time for config
We will serve the config from the cache if available
in waiting for the config to be loaded.
It will remove the loading time for the config except
when the config is not available in the cache.
2025-04-22 11:23:55 +02:00
Anthony LC
101cef7d70 ♻️(frontend) refacto useCunninghamTheme
Refacto useCunninghamTheme, we don't need a function
to have access to the tokens anymore.
2025-04-22 10:38:51 +02:00
Samuel Paccoud - DINUM
419079ac69 🚸(backend) make document search on title accent-insensitive
This should work in both cases:
- search for "vélo" when the document title contains "velo"
- search for "velo" when the document title contains "vélo"
2025-04-17 20:28:14 +02:00
136 changed files with 21016 additions and 17902 deletions

View File

@@ -8,10 +8,37 @@ and this project adheres to
## [Unreleased]
## [3.2.1] - 2025-05-06
## Fixed
- 🐛(frontend) fix list copy paste #943
## [3.2.0] - 2025-05-05
## Added
- 🚸(backend) make document search on title accent-insensitive #874
- 🚩 add homepage feature flag #861
- ✨(settings) Allow configuring PKCE for the SSO #886
- 🌐(i18n) activate chinese and spanish languages #884
- 🔧(backend) allow overwriting the data directory #893
- (backend) add `django-lasuite` dependency #839
- ✨(frontend) advanced table features #908
## Changed
- ⚡️(frontend) reduce unblocking time for config #867
- ♻️(frontend) bind UI with ability access #900
- ♻️(frontend) use built-in Quote block #908
## Fixed
- 🐛(nginx) fix 404 when accessing a doc #866
- 🔒️(drf) disable browsable HTML API renderer #919
- 🔒(frontend) enhance file download security #889
- 🐛(backend) race condition create doc #633
- 🐛(frontend) fix breaklines in custom blocks #908
## [3.1.0] - 2025-04-07
@@ -140,7 +167,6 @@ and this project adheres to
- ♻️(frontend) improve table pdf rendering
- 🐛(email) invitation emails in receivers language
## [2.2.0] - 2025-02-10
## Added
@@ -511,7 +537,7 @@ and this project adheres to
- ⚡️(e2e) unique login between tests (#80)
- ⚡️(CI) improve e2e job (#86)
- ♻️(frontend) improve the error and message info ui (#93)
- ✏️(frontend) change all occurences of pad to doc (#99)
- ✏️(frontend) change all occurrences of pad to doc (#99)
## Fixed
@@ -529,7 +555,9 @@ and this project adheres to
- ✨(frontend) Coming Soon page (#67)
- 🚀 Impress, project to manage your documents easily and collaboratively.
[unreleased]: https://github.com/numerique-gouv/impress/compare/v3.1.0...main
[unreleased]: https://github.com/numerique-gouv/impress/compare/v3.2.1...main
[v3.2.1]: https://github.com/numerique-gouv/impress/releases/v3.2.1
[v3.2.0]: https://github.com/numerique-gouv/impress/releases/v3.2.0
[v3.1.0]: https://github.com/numerique-gouv/impress/releases/v3.1.0
[v3.0.0]: https://github.com/numerique-gouv/impress/releases/v3.0.0
[v2.6.0]: https://github.com/numerique-gouv/impress/releases/v2.6.0

View File

@@ -48,10 +48,6 @@ All commit messages must adhere to the following format:
Implemented login and signup features, and integrated OAuth2 for social login.
```
## Signing commits
Only signed commits are accepted. They can be signed using a SSH or GPG key. Github documentation about signing commits contains all the information you need : https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#about-commit-signature-verification
## Changelog Update
Please add a line to the changelog describing your development. The changelog entry should include a brief summary of the changes, this helps in tracking changes effectively and keeping everyone informed. We usually include the title of the pull request, followed by the pull request ID to finish the log entry. The changelog line should be less than 80 characters in total.

View File

@@ -149,7 +149,7 @@ COPY docker/files/usr/local/etc/gunicorn/impress.py /usr/local/etc/gunicorn/impr
ARG DOCKER_USER
USER ${DOCKER_USER}
# Copy statics
# Copy statistics
COPY --from=link-collector ${IMPRESS_STATIC_ROOT} ${IMPRESS_STATIC_ROOT}
# Copy impress mails

View File

@@ -24,8 +24,6 @@ Welcome to Docs! The open source document editor where your notes can become kno
## Why use Docs ❓
⚠️ **Note that Docs provides docs/pdf exporters by loading [two BlockNote packages](https://github.com/suitenumerique/docs/blob/main/src/frontend/apps/impress/package.json#L22C7-L23C53), which we use under the AGPL-3.0 licence. Until we comply with the terms of this license, we recommend that you don't run Docs as a commercial product, unless you are willing to sponsor [BlockNote](https://github.com/TypeCellOS/BlockNote).**
Docs is a collaborative text editor designed to address common challenges in knowledge building and sharing.
### Write
@@ -39,11 +37,13 @@ Docs is a collaborative text editor designed to address common challenges in kno
* 🤝 Collaborate with your team in real time
* 🔒 Granular access control to ensure your information is secure and only shared with the right people
* 📑 Professional document exports in multiple formats (.odt, .doc, .pdf) with customizable templates
* 📚 Built-in wiki functionality to turn your team's collaborative work into organized knowledge `ETA 02/2025`
* 📚 Built-in wiki functionality to turn your team's collaborative work into organized knowledge `ETA 05/2025`
### Self-host
* 🚀 Easy to install, scalable and secure alternative to Notion, Outline or Confluence
⚠️ For the PDF and Docx export Docs relies on XL packages from BlockNote licenced in AGPL-3.0. Please make sure you fulfill your obligations regarding BlockNote licensing (see https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-pdf-exporter/LICENSE and https://www.blocknotejs.org/about#partner-with-us).
## Getting started 🔧
### Test it
@@ -118,6 +118,7 @@ $ make run-backend
```
**Adding content**
You can create a basic demo site by running:
```shellscript

View File

@@ -4,7 +4,7 @@
Security is very important to us.
If you have any issue regarding security, please disclose the information responsibly submiting [this form](https://vdp.numerique.gouv.fr/p/Send-a-report?lang=en) and not by creating an issue on the repository. You can also email us at docs@numerique.gouv.fr
If you have any issue regarding security, please disclose the information responsibly submitting [this form](https://vdp.numerique.gouv.fr/p/Send-a-report?lang=en) and not by creating an issue on the repository. You can also email us at docs@numerique.gouv.fr
We appreciate your effort to make Docs more secure.

View File

@@ -185,15 +185,11 @@ services:
context: .
dockerfile: ./src/frontend/servers/y-provider/Dockerfile
target: y-provider
command: ["yarn", "workspace", "server-y-provider", "run", "dev"]
working_dir: /app/frontend
restart: unless-stopped
env_file:
- env.d/development/common
ports:
- "4444:4444"
volumes:
- ./src/frontend/:/app/frontend
kc_postgresql:
image: postgres:14.3

View File

@@ -39,7 +39,7 @@ These are the environmental variables you can set for the impress-backend contai
| DJANGO_EMAIL_PORT | port used to connect to email host | |
| DJANGO_EMAIL_USE_TLS | use tls for email host connection | false |
| DJANGO_EMAIL_USE_SSL | use sstl for email host connection | false |
| DJANGO_EMAIL_FROM | email adress used as sender | from@example.com |
| DJANGO_EMAIL_FROM | email address used as sender | from@example.com |
| DJANGO_CORS_ALLOW_ALL_ORIGINS | allow all CORS origins | true |
| DJANGO_CORS_ALLOWED_ORIGINS | list of origins allowed for CORS | [] |
| DJANGO_CORS_ALLOWED_ORIGIN_REGEXES | list of origins allowed for CORS using regulair expressions | [] |
@@ -62,11 +62,11 @@ These are the environmental variables you can set for the impress-backend contai
| OIDC_RP_CLIENT_ID | client id used for OIDC | impress |
| OIDC_RP_CLIENT_SECRET | client secret used for OIDC | |
| OIDC_OP_JWKS_ENDPOINT | JWKS endpoint for OIDC | |
| OIDC_OP_AUTHORIZATION_ENDPOINT | Autorization endpoint for OIDC | |
| OIDC_OP_AUTHORIZATION_ENDPOINT | Authorization endpoint for OIDC | |
| OIDC_OP_TOKEN_ENDPOINT | Token endpoint for OIDC | |
| OIDC_OP_USER_ENDPOINT | User endpoint for OIDC | |
| OIDC_OP_LOGOUT_ENDPOINT | Logout endpoint for OIDC | |
| OIDC_AUTH_REQUEST_EXTRA_PARAMS | OIDC extra auth paramaters | {} |
| OIDC_AUTH_REQUEST_EXTRA_PARAMS | OIDC extra auth parameters | {} |
| OIDC_RP_SCOPES | scopes requested for OIDC | openid email |
| LOGIN_REDIRECT_URL | login redirect url | |
| LOGIN_REDIRECT_URL_FAILURE | login redirect url on failure | |
@@ -76,15 +76,16 @@ These are the environmental variables you can set for the impress-backend contai
| OIDC_REDIRECT_ALLOWED_HOSTS | Allowed hosts for OIDC redirect url | [] |
| OIDC_STORE_ID_TOKEN | Store OIDC token | true |
| OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION | faillback to email for identification | true |
| OIDC_ALLOW_DUPLICATE_EMAILS | Allow dupplicate emails | false |
| OIDC_ALLOW_DUPLICATE_EMAILS | Allow duplicate emails | false |
| USER_OIDC_ESSENTIAL_CLAIMS | essential claims in OIDC token | [] |
| USER_OIDC_FIELDS_TO_FULLNAME | OIDC token claims to create full name | ["first_name", "last_name"] |
| USER_OIDC_FIELD_TO_SHORTNAME | OIDC token claims to create shortname | first_name |
| OIDC_USERINFO_FULLNAME_FIELDS | OIDC token claims to create full name | ["first_name", "last_name"] |
| OIDC_USERINFO_SHORTNAME_FIELD | OIDC token claims to create shortname | first_name |
| ALLOW_LOGOUT_GET_METHOD | Allow get logout method | true |
| AI_API_KEY | AI key to be used for AI Base url | |
| AI_BASE_URL | OpenAI compatible AI base url | |
| AI_MODEL | AI Model to use | |
| AI_ALLOW_REACH_FROM | Users that can use AI must be this level. options are "public", "authenticated", "restricted" | authenticated |
| AI_FEATURE_ENABLED | Enable AI options | false |
| Y_PROVIDER_API_KEY | Y provider API key | |
| Y_PROVIDER_API_BASE_URL | Y Provider url | |
| CONVERSION_API_ENDPOINT | Conversion API endpoint | convert-markdown |

View File

@@ -33,8 +33,8 @@ backend:
OIDC_RP_SIGN_ALGO: RS256
OIDC_RP_SCOPES: "openid email"
OIDC_VERIFY_SSL: False
USER_OIDC_FIELD_TO_SHORTNAME: "given_name"
USER_OIDC_FIELDS_TO_FULLNAME: "given_name,usual_name"
OIDC_USERINFO_SHORTNAME_FIELD: "given_name"
OIDC_USERINFO_FULLNAME_FIELDS: "given_name,usual_name"
OIDC_REDIRECT_ALLOWED_HOSTS: https://impress.127.0.0.1.nip.io
OIDC_AUTH_REQUEST_EXTRA_PARAMS: "{'acr_values': 'eidas1'}"
LOGIN_REDIRECT_URL: https://impress.127.0.0.1.nip.io
@@ -82,13 +82,13 @@ backend:
python manage.py createsuperuser --email admin@example.com --password admin
restartPolicy: Never
# Exra volume to manage our local custom CA and avoid to set ssl_verify: false
# Extra volume to manage our local custom CA and avoid to set ssl_verify: false
extraVolumeMounts:
- name: certs
mountPath: /usr/local/lib/python3.12/site-packages/certifi/cacert.pem
subPath: cacert.pem
# Exra volume to manage our local custom CA and avoid to set ssl_verify: false
# Extra volume to manage our local custom CA and avoid to set ssl_verify: false
extraVolumes:
- name: certs
configMap:

View File

@@ -133,7 +133,7 @@ OIDC_RP_SCOPES: "openid email"
You can find these values in **examples/keycloak.values.yaml**
### Find redis server connexion values
### Find redis server connection values
Docs needs a redis so we start by deploying one:
@@ -146,7 +146,7 @@ keycloak-postgresql-0 1/1 Running 0 26m
redis-master-0 1/1 Running 0 35s
```
### Find postgresql connexion values
### Find postgresql connection values
Docs uses a postgresql database as backend, so if you have a provider, obtain the necessary information to use it. If you don't, you can install a postgresql testing environment as follow:
@@ -173,7 +173,7 @@ POSTGRES_USER: dinum
POSTGRES_PASSWORD: pass
```
### Find s3 bucket connexion values
### Find s3 bucket connection values
Docs uses an s3 bucket to store documents, so if you have a provider obtain the necessary information to use it. If you don't, you can install a local minio testing environment as follow:
@@ -191,7 +191,7 @@ redis-master-0 1/1 Running 0 10m
## Deployment
Now you are ready to deploy Docs without AI. AI requires more dependencies (OpenAI API). To deploy Docs you need to provide all previous informations to the helm chart.
Now you are ready to deploy Docs without AI. AI requires more dependencies (OpenAI API). To deploy Docs you need to provide all previous information to the helm chart.
```
$ helm repo add impress https://suitenumerique.github.io/docs/

View File

@@ -64,6 +64,5 @@ COLLABORATION_WS_URL=ws://localhost:4444/collaboration/ws/
# Frontend
FRONTEND_THEME=default
FRONTEND_HOMEPAGE_FEATURE_ENABLED=True
FRONTEND_FOOTER_FEATURE_ENABLED=True
FRONTEND_URL_JSON_FOOTER=http://frontend:3000/contents/footer-demo.json

View File

@@ -9,6 +9,18 @@
"matchManagers": ["pep621"],
"matchPackageNames": []
},
{
"groupName": "allowed django versions",
"matchManagers": ["pep621"],
"matchPackageNames": ["Django"],
"allowedVersions": "<5.2"
},
{
"groupName": "allowed redis versions",
"matchManagers": ["pep621"],
"matchPackageNames": ["redis"],
"allowedVersions": "<6.0.0"
},
{
"enabled": false,
"groupName": "ignored js dependencies",
@@ -16,6 +28,7 @@
"matchPackageNames": [
"eslint",
"fetch-mock",
"prosemirror-model",
"node",
"node-fetch",
"workbox-webpack-plugin"

View File

@@ -1,5 +1,7 @@
"""API filters for Impress' core application."""
import unicodedata
from django.utils.translation import gettext_lazy as _
import django_filters
@@ -7,13 +9,42 @@ import django_filters
from core import models
class DocumentFilter(django_filters.FilterSet):
def remove_accents(value):
"""Remove accents from a string (vélo -> velo)."""
return "".join(
c
for c in unicodedata.normalize("NFD", value)
if unicodedata.category(c) != "Mn"
)
class AccentInsensitiveCharFilter(django_filters.CharFilter):
"""
Custom filter for filtering documents.
A custom CharFilter that filters on the accent-insensitive value searched.
"""
title = django_filters.CharFilter(
field_name="title", lookup_expr="icontains", label=_("Title")
def filter(self, qs, value):
"""
Apply the filter to the queryset using the unaccented version of the field.
Args:
qs: The queryset to filter.
value: The value to search for in the unaccented field.
Returns:
A filtered queryset.
"""
if value:
value = remove_accents(value)
return super().filter(qs, value)
class DocumentFilter(django_filters.FilterSet):
"""
Custom filter for filtering documents on title (accent and case insensitive).
"""
title = AccentInsensitiveCharFilter(
field_name="title", lookup_expr="unaccent__icontains", label=_("Title")
)
class Meta:

View File

@@ -11,8 +11,8 @@ from django.contrib.postgres.fields import ArrayField
from django.contrib.postgres.search import TrigramSimilarity
from django.core.exceptions import ValidationError
from django.core.files.storage import default_storage
from django.db import connection, transaction
from django.db import models as db
from django.db import transaction
from django.db.models.expressions import RawSQL
from django.db.models.functions import Left, Length
from django.http import Http404, StreamingHttpResponse
@@ -576,7 +576,7 @@ class DocumentViewSet(
queryset, filter_data["is_favorite"]
)
# Apply ordering only now that everyting is filtered and annotated
# Apply ordering only now that everything is filtered and annotated
queryset = filters.OrderingFilter().filter_queryset(
self.request, queryset, self
)
@@ -607,6 +607,14 @@ class DocumentViewSet(
@transaction.atomic
def perform_create(self, serializer):
"""Set the current user as creator and owner of the newly created object."""
# locks the table to ensure safe concurrent access
with connection.cursor() as cursor:
cursor.execute(
f'LOCK TABLE "{models.Document._meta.db_table}" ' # noqa: SLF001
"IN SHARE ROW EXCLUSIVE MODE;"
)
obj = models.Document.add_root(
creator=self.request.user,
**serializer.validated_data,
@@ -666,10 +674,19 @@ class DocumentViewSet(
permission_classes=[],
url_path="create-for-owner",
)
@transaction.atomic
def create_for_owner(self, request):
"""
Create a document on behalf of a specified owner (pre-existing user or invited).
"""
# locks the table to ensure safe concurrent access
with connection.cursor() as cursor:
cursor.execute(
f'LOCK TABLE "{models.Document._meta.db_table}" ' # noqa: SLF001
"IN SHARE ROW EXCLUSIVE MODE;"
)
# Deserialize and validate the data
serializer = serializers.ServerCreateDocumentSerializer(data=request.data)
if not serializer.is_valid():
@@ -775,7 +792,12 @@ class DocumentViewSet(
serializer.is_valid(raise_exception=True)
with transaction.atomic():
child_document = document.add_child(
# "select_for_update" locks the table to ensure safe concurrent access
locked_parent = models.Document.objects.select_for_update().get(
pk=document.pk
)
child_document = locked_parent.add_child(
creator=request.user,
**serializer.validated_data,
)
@@ -836,8 +858,8 @@ class DocumentViewSet(
"""
try:
current_document = self.queryset.only("depth", "path").get(pk=pk)
except models.Document.DoesNotExist as excpt:
raise drf.exceptions.NotFound from excpt
except models.Document.DoesNotExist as except:
raise drf.exceptions.NotFound from except
ancestors = (
(current_document.get_ancestors() | self.queryset.filter(pk=pk))
@@ -867,7 +889,7 @@ class DocumentViewSet(
)
# Compute cache for ancestors links to avoid many queries while computing
# abilties for his documents in the tree!
# abilities for his documents in the tree!
ancestors_links.append(
{"link_reach": ancestor.link_reach, "link_role": ancestor.link_role}
)

View File

@@ -1,130 +1,59 @@
"""Authentication Backends for the Impress core app."""
import logging
import os
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
from django.utils.translation import gettext_lazy as _
import requests
from mozilla_django_oidc.auth import (
OIDCAuthenticationBackend as MozillaOIDCAuthenticationBackend,
from lasuite.oidc_login.backends import (
OIDCAuthenticationBackend as LaSuiteOIDCAuthenticationBackend,
)
from core.models import DuplicateEmailError, User
from core.models import DuplicateEmailError
logger = logging.getLogger(__name__)
# Settings renamed warnings
if os.environ.get("USER_OIDC_FIELDS_TO_FULLNAME"):
logger.warning(
"USER_OIDC_FIELDS_TO_FULLNAME has been renamed to "
"OIDC_USERINFO_FULLNAME_FIELDS please update your settings."
)
class OIDCAuthenticationBackend(MozillaOIDCAuthenticationBackend):
if os.environ.get("USER_OIDC_FIELD_TO_SHORTNAME"):
logger.warning(
"USER_OIDC_FIELD_TO_SHORTNAME has been renamed to "
"OIDC_USERINFO_SHORTNAME_FIELD please update your settings."
)
class OIDCAuthenticationBackend(LaSuiteOIDCAuthenticationBackend):
"""Custom OpenID Connect (OIDC) Authentication Backend.
This class overrides the default OIDC Authentication Backend to accommodate differences
in the User and Identity models, and handles signed and/or encrypted UserInfo response.
"""
def get_userinfo(self, access_token, id_token, payload):
"""Return user details dictionary.
def get_extra_claims(self, user_info):
"""
Return extra claims from user_info.
Parameters:
- access_token (str): The access token.
- id_token (str): The id token (unused).
- payload (dict): The token payload (unused).
Note: The id_token and payload parameters are unused in this implementation,
but were kept to preserve base method signature.
Note: It handles signed and/or encrypted UserInfo Response. It is required by
Agent Connect, which follows the OIDC standard. It forces us to override the
base method, which deal with 'application/json' response.
Args:
user_info (dict): The user information dictionary.
Returns:
- dict: User details dictionary obtained from the OpenID Connect user endpoint.
dict: A dictionary of extra claims.
"""
user_response = requests.get(
self.OIDC_OP_USER_ENDPOINT,
headers={"Authorization": f"Bearer {access_token}"},
verify=self.get_settings("OIDC_VERIFY_SSL", True),
timeout=self.get_settings("OIDC_TIMEOUT", None),
proxies=self.get_settings("OIDC_PROXY", None),
)
user_response.raise_for_status()
try:
userinfo = user_response.json()
except ValueError:
try:
userinfo = self.verify_token(user_response.text)
except Exception as e:
raise SuspiciousOperation(
_("Invalid response format or token verification failed")
) from e
return userinfo
def verify_claims(self, claims):
"""
Verify the presence of essential claims and the "sub" (which is mandatory as defined
by the OIDC specification) to decide if authentication should be allowed.
"""
essential_claims = settings.USER_OIDC_ESSENTIAL_CLAIMS
missing_claims = [claim for claim in essential_claims if claim not in claims]
if missing_claims:
logger.error("Missing essential claims: %s", missing_claims)
return False
return True
def get_or_create_user(self, access_token, id_token, payload):
"""Return a User based on userinfo. Create a new user if no match is found."""
user_info = self.get_userinfo(access_token, id_token, payload)
if not self.verify_claims(user_info):
raise SuspiciousOperation("Claims verification failed.")
sub = user_info["sub"]
email = user_info.get("email")
# Get user's full name from OIDC fields defined in settings
full_name = self.compute_full_name(user_info)
short_name = user_info.get(settings.USER_OIDC_FIELD_TO_SHORTNAME)
claims = {
"email": email,
"full_name": full_name,
"short_name": short_name,
return {
"full_name": self.compute_full_name(user_info),
"short_name": user_info.get(settings.OIDC_USERINFO_SHORTNAME_FIELD),
}
def get_existing_user(self, sub, email):
"""Fetch existing user by sub or email."""
try:
user = User.objects.get_user_by_sub_or_email(sub, email)
return self.UserModel.objects.get_user_by_sub_or_email(sub, email)
except DuplicateEmailError as err:
raise SuspiciousOperation(err.message) from err
if user:
if not user.is_active:
raise SuspiciousOperation(_("User account is disabled"))
self.update_user_if_needed(user, claims)
elif self.get_settings("OIDC_CREATE_USER", True):
user = User.objects.create(sub=sub, password="!", **claims) # noqa: S106
return user
def compute_full_name(self, user_info):
"""Compute user's full name based on OIDC fields in settings."""
name_fields = settings.USER_OIDC_FIELDS_TO_FULLNAME
full_name = " ".join(
user_info[field] for field in name_fields if user_info.get(field)
)
return full_name or None
def update_user_if_needed(self, user, claims):
"""Update user claims if they have changed."""
has_changed = any(
value and value != getattr(user, key) for key, value in claims.items()
)
if has_changed:
updated_claims = {key: value for key, value in claims.items() if value}
self.UserModel.objects.filter(id=user.id).update(**updated_claims)

View File

@@ -1,18 +0,0 @@
"""Authentication URLs for the People core app."""
from django.urls import path
from mozilla_django_oidc.urls import urlpatterns as mozzila_oidc_urls
from .views import OIDCLogoutCallbackView, OIDCLogoutView
urlpatterns = [
# Override the default 'logout/' path from Mozilla Django OIDC with our custom view.
path("logout/", OIDCLogoutView.as_view(), name="oidc_logout_custom"),
path(
"logout-callback/",
OIDCLogoutCallbackView.as_view(),
name="oidc_logout_callback",
),
*mozzila_oidc_urls,
]

View File

@@ -1,137 +0,0 @@
"""Authentication Views for the People core app."""
from urllib.parse import urlencode
from django.contrib import auth
from django.core.exceptions import SuspiciousOperation
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils import crypto
from mozilla_django_oidc.utils import (
absolutify,
)
from mozilla_django_oidc.views import (
OIDCLogoutView as MozillaOIDCOIDCLogoutView,
)
class OIDCLogoutView(MozillaOIDCOIDCLogoutView):
"""Custom logout view for handling OpenID Connect (OIDC) logout flow.
Adds support for handling logout callbacks from the identity provider (OP)
by initiating the logout flow if the user has an active session.
The Django session is retained during the logout process to persist the 'state' OIDC parameter.
This parameter is crucial for maintaining the integrity of the logout flow between this call
and the subsequent callback.
"""
@staticmethod
def persist_state(request, state):
"""Persist the given 'state' parameter in the session's 'oidc_states' dictionary
This method is used to store the OIDC state parameter in the session, according to the
structure expected by Mozilla Django OIDC's 'add_state_and_verifier_and_nonce_to_session'
utility function.
"""
if "oidc_states" not in request.session or not isinstance(
request.session["oidc_states"], dict
):
request.session["oidc_states"] = {}
request.session["oidc_states"][state] = {}
request.session.save()
def construct_oidc_logout_url(self, request):
"""Create the redirect URL for interfacing with the OIDC provider.
Retrieves the necessary parameters from the session and constructs the URL
required to initiate logout with the OpenID Connect provider.
If no ID token is found in the session, the logout flow will not be initiated,
and the method will return the default redirect URL.
The 'state' parameter is generated randomly and persisted in the session to ensure
its integrity during the subsequent callback.
"""
oidc_logout_endpoint = self.get_settings("OIDC_OP_LOGOUT_ENDPOINT")
if not oidc_logout_endpoint:
return self.redirect_url
reverse_url = reverse("oidc_logout_callback")
id_token = request.session.get("oidc_id_token", None)
if not id_token:
return self.redirect_url
query = {
"id_token_hint": id_token,
"state": crypto.get_random_string(self.get_settings("OIDC_STATE_SIZE", 32)),
"post_logout_redirect_uri": absolutify(request, reverse_url),
}
self.persist_state(request, query["state"])
return f"{oidc_logout_endpoint}?{urlencode(query)}"
def post(self, request):
"""Handle user logout.
If the user is not authenticated, redirects to the default logout URL.
Otherwise, constructs the OIDC logout URL and redirects the user to start
the logout process.
If the user is redirected to the default logout URL, ensure her Django session
is terminated.
"""
logout_url = self.redirect_url
if request.user.is_authenticated:
logout_url = self.construct_oidc_logout_url(request)
# If the user is not redirected to the OIDC provider, ensure logout
if logout_url == self.redirect_url:
auth.logout(request)
return HttpResponseRedirect(logout_url)
class OIDCLogoutCallbackView(MozillaOIDCOIDCLogoutView):
"""Custom view for handling the logout callback from the OpenID Connect (OIDC) provider.
Handles the callback after logout from the identity provider (OP).
Verifies the state parameter and performs necessary logout actions.
The Django session is maintained during the logout process to ensure the integrity
of the logout flow initiated in the previous step.
"""
http_method_names = ["get"]
def get(self, request):
"""Handle the logout callback.
If the user is not authenticated, redirects to the default logout URL.
Otherwise, verifies the state parameter and performs necessary logout actions.
"""
if not request.user.is_authenticated:
return HttpResponseRedirect(self.redirect_url)
state = request.GET.get("state")
if state not in request.session.get("oidc_states", {}):
msg = "OIDC callback state not found in session `oidc_states`!"
raise SuspiciousOperation(msg)
del request.session["oidc_states"][state]
request.session.save()
auth.logout(request)
return HttpResponseRedirect(self.redirect_url)

View File

@@ -0,0 +1,10 @@
from django.contrib.postgres.operations import UnaccentExtension
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("core", "0020_remove_is_public_add_field_attachments_and_duplicated_from"),
]
operations = [UnaccentExtension()]

View File

@@ -544,9 +544,9 @@ class Document(MP_Node, BaseModel):
response = default_storage.connection.meta.client.head_object(
Bucket=default_storage.bucket_name, Key=file_key
)
except ClientError as excpt:
except ClientError as except:
# If the error is a 404, the object doesn't exist, so we should create it.
if excpt.response["Error"]["Code"] == "404":
if except.response["Error"]["Code"] == "404":
has_changed = True
else:
raise

View File

@@ -44,7 +44,7 @@ AI_ACTIONS = {
}
AI_TRANSLATE = (
"Keep the same html stucture and formatting. "
"Keep the same html structure and formatting. "
"Translate the content in the html to the specified language {language:s}. "
"Check the translation for accuracy and make any necessary corrections. "
"Do not provide any other information."

View File

@@ -17,7 +17,7 @@ class CollaborationService:
def reset_connections(self, room, user_id=None):
"""
Reset connections of a room in the collaboration server.
Reseting a connection means that the user will be disconnected and will
Resetting a connection means that the user will be disconnected and will
have to reconnect to the collaboration server, with updated rights.
"""
endpoint = "reset-connections"

View File

@@ -2,14 +2,14 @@
import random
import re
from logging import Logger
from unittest import mock
from django.core.exceptions import SuspiciousOperation
from django.test.utils import override_settings
import pytest
import responses
from cryptography.fernet import Fernet
from lasuite.oidc_login.backends import get_oidc_refresh_token
from core import models
from core.authentication.backends import OIDCAuthenticationBackend
@@ -57,7 +57,7 @@ def test_authentication_getter_existing_user_via_email(
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
with django_assert_num_queries(2):
with django_assert_num_queries(3): # user by sub, user by mail, update sub
user = klass.get_or_create_user(
access_token="test-token", id_token=None, payload=None
)
@@ -288,7 +288,7 @@ def test_authentication_getter_new_user_no_email(monkeypatch):
assert user.email is None
assert user.full_name is None
assert user.short_name is None
assert user.password == "!"
assert user.has_usable_password() is False
assert models.User.objects.count() == 1
@@ -315,7 +315,7 @@ def test_authentication_getter_new_user_with_email(monkeypatch):
assert user.email == email
assert user.full_name == "John Doe"
assert user.short_name == "John"
assert user.password == "!"
assert user.has_usable_password() is False
assert models.User.objects.count() == 1
@@ -345,11 +345,15 @@ def test_authentication_get_userinfo_json_response():
@override_settings(OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo")
@responses.activate
def test_authentication_get_userinfo_token_response(monkeypatch):
def test_authentication_get_userinfo_token_response(monkeypatch, settings):
"""Test get_userinfo method with a token response."""
settings.OIDC_RP_SIGN_ALGO = "HS256" # disable JWKS URL call
responses.add(
responses.GET, re.compile(r".*/userinfo"), body="fake.jwt.token", status=200
responses.GET,
re.compile(r".*/userinfo"),
body="fake.jwt.token",
status=200,
content_type="application/jwt",
)
def mock_verify_token(self, token): # pylint: disable=unused-argument
@@ -371,21 +375,25 @@ def test_authentication_get_userinfo_token_response(monkeypatch):
@override_settings(OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo")
@responses.activate
def test_authentication_get_userinfo_invalid_response():
def test_authentication_get_userinfo_invalid_response(settings):
"""
Test get_userinfo method with an invalid JWT response that
causes verify_token to raise an error.
"""
settings.OIDC_RP_SIGN_ALGO = "HS256" # disable JWKS URL call
responses.add(
responses.GET, re.compile(r".*/userinfo"), body="fake.jwt.token", status=200
responses.GET,
re.compile(r".*/userinfo"),
body="fake.jwt.token",
status=200,
content_type="application/jwt",
)
oidc_backend = OIDCAuthenticationBackend()
with pytest.raises(
SuspiciousOperation,
match="Invalid response format or token verification failed",
match="User info response was not valid JWT",
):
oidc_backend.get_userinfo("fake_access_token", None, None)
@@ -450,100 +458,54 @@ def test_authentication_getter_existing_disabled_user_via_email(
assert models.User.objects.count() == 1
# Essential claims
def test_authentication_verify_claims_default(django_assert_num_queries, monkeypatch):
"""The sub claim should be mandatory by default."""
klass = OIDCAuthenticationBackend()
def get_userinfo_mocked(*args):
return {
"test": "123",
}
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
with (
django_assert_num_queries(0),
pytest.raises(
KeyError,
match="sub",
),
):
klass.get_or_create_user(access_token="test-token", id_token=None, payload=None)
assert models.User.objects.exists() is False
@pytest.mark.parametrize(
"essential_claims, missing_claims",
[
(["email", "sub"], ["email"]),
(["Email", "sub"], ["Email"]), # Case sensitivity
],
)
@override_settings(OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo")
@mock.patch.object(Logger, "error")
def test_authentication_verify_claims_essential_missing(
mock_logger,
essential_claims,
missing_claims,
django_assert_num_queries,
monkeypatch,
@responses.activate
def test_authentication_session_tokens(
django_assert_num_queries, monkeypatch, rf, settings
):
"""Ensure SuspiciousOperation is raised if essential claims are missing."""
"""
Test that the session contains oidc_refresh_token and oidc_access_token after authentication.
"""
settings.OIDC_OP_TOKEN_ENDPOINT = "http://oidc.endpoint.test/token"
settings.OIDC_OP_USER_ENDPOINT = "http://oidc.endpoint.test/userinfo"
settings.OIDC_OP_JWKS_ENDPOINT = "http://oidc.endpoint.test/jwks"
settings.OIDC_STORE_ACCESS_TOKEN = True
settings.OIDC_STORE_REFRESH_TOKEN = True
settings.OIDC_STORE_REFRESH_TOKEN_KEY = Fernet.generate_key()
klass = OIDCAuthenticationBackend()
request = rf.get("/some-url", {"state": "test-state", "code": "test-code"})
request.session = {}
def get_userinfo_mocked(*args):
return {
"sub": "123",
"last_name": "Doe",
}
def verify_token_mocked(*args, **kwargs):
return {"sub": "123", "email": "test@example.com"}
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
monkeypatch.setattr(OIDCAuthenticationBackend, "verify_token", verify_token_mocked)
with (
django_assert_num_queries(0),
pytest.raises(
SuspiciousOperation,
match="Claims verification failed",
),
override_settings(USER_OIDC_ESSENTIAL_CLAIMS=essential_claims),
):
klass.get_or_create_user(access_token="test-token", id_token=None, payload=None)
responses.add(
responses.POST,
re.compile(settings.OIDC_OP_TOKEN_ENDPOINT),
json={
"access_token": "test-access-token",
"refresh_token": "test-refresh-token",
},
status=200,
)
assert models.User.objects.exists() is False
mock_logger.assert_called_once_with("Missing essential claims: %s", missing_claims)
@override_settings(
OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo",
USER_OIDC_ESSENTIAL_CLAIMS=["email", "last_name"],
)
def test_authentication_verify_claims_success(django_assert_num_queries, monkeypatch):
"""Ensure user is authenticated when all essential claims are present."""
klass = OIDCAuthenticationBackend()
def get_userinfo_mocked(*args):
return {
"email": "john.doe@example.com",
"last_name": "Doe",
"sub": "123",
}
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
responses.add(
responses.GET,
re.compile(settings.OIDC_OP_USER_ENDPOINT),
json={"sub": "123", "email": "test@example.com"},
status=200,
)
with django_assert_num_queries(6):
user = klass.get_or_create_user(
access_token="test-token", id_token=None, payload=None
user = klass.authenticate(
request,
code="test-code",
nonce="test-nonce",
code_verifier="test-code-verifier",
)
assert models.User.objects.filter(id=user.id).exists()
assert user.sub == "123"
assert user.full_name == "Doe"
assert user.short_name is None
assert user.email == "john.doe@example.com"
assert user is not None
assert request.session["oidc_access_token"] == "test-access-token"
assert get_oidc_refresh_token(request.session) == "test-refresh-token"

View File

@@ -1,10 +0,0 @@
"""Unit tests for the Authentication URLs."""
from core.authentication.urls import urlpatterns
def test_urls_override_default_mozilla_django_oidc():
"""Custom URL patterns should override default ones from Mozilla Django OIDC."""
url_names = [u.name for u in urlpatterns]
assert url_names.index("oidc_logout_custom") < url_names.index("oidc_logout")

View File

@@ -1,231 +0,0 @@
"""Unit tests for the Authentication Views."""
from unittest import mock
from urllib.parse import parse_qs, urlparse
from django.contrib.auth.models import AnonymousUser
from django.contrib.sessions.middleware import SessionMiddleware
from django.core.exceptions import SuspiciousOperation
from django.test import RequestFactory
from django.test.utils import override_settings
from django.urls import reverse
from django.utils import crypto
import pytest
from rest_framework.test import APIClient
from core import factories
from core.authentication.views import OIDCLogoutCallbackView, OIDCLogoutView
pytestmark = pytest.mark.django_db
@override_settings(LOGOUT_REDIRECT_URL="/example-logout")
def test_view_logout_anonymous():
"""Anonymous users calling the logout url,
should be redirected to the specified LOGOUT_REDIRECT_URL."""
url = reverse("oidc_logout_custom")
response = APIClient().get(url)
assert response.status_code == 302
assert response.url == "/example-logout"
@mock.patch.object(
OIDCLogoutView, "construct_oidc_logout_url", return_value="/example-logout"
)
def test_view_logout(mocked_oidc_logout_url):
"""Authenticated users should be redirected to OIDC provider for logout."""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
url = reverse("oidc_logout_custom")
response = client.get(url)
mocked_oidc_logout_url.assert_called_once()
assert response.status_code == 302
assert response.url == "/example-logout"
@override_settings(LOGOUT_REDIRECT_URL="/default-redirect-logout")
@mock.patch.object(
OIDCLogoutView, "construct_oidc_logout_url", return_value="/default-redirect-logout"
)
def test_view_logout_no_oidc_provider(mocked_oidc_logout_url):
"""Authenticated users should be logged out when no OIDC provider is available."""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
url = reverse("oidc_logout_custom")
with mock.patch("mozilla_django_oidc.views.auth.logout") as mock_logout:
response = client.get(url)
mocked_oidc_logout_url.assert_called_once()
mock_logout.assert_called_once()
assert response.status_code == 302
assert response.url == "/default-redirect-logout"
@override_settings(LOGOUT_REDIRECT_URL="/example-logout")
def test_view_logout_callback_anonymous():
"""Anonymous users calling the logout callback url,
should be redirected to the specified LOGOUT_REDIRECT_URL."""
url = reverse("oidc_logout_callback")
response = APIClient().get(url)
assert response.status_code == 302
assert response.url == "/example-logout"
@pytest.mark.parametrize(
"initial_oidc_states",
[{}, {"other_state": "foo"}],
)
def test_view_logout_persist_state(initial_oidc_states):
"""State value should be persisted in session's data."""
user = factories.UserFactory()
request = RequestFactory().request()
request.user = user
middleware = SessionMiddleware(get_response=lambda x: x)
middleware.process_request(request)
if initial_oidc_states:
request.session["oidc_states"] = initial_oidc_states
request.session.save()
mocked_state = "mock_state"
OIDCLogoutView().persist_state(request, mocked_state)
assert "oidc_states" in request.session
assert request.session["oidc_states"] == {
"mock_state": {},
**initial_oidc_states,
}
@override_settings(OIDC_OP_LOGOUT_ENDPOINT="/example-logout")
@mock.patch.object(OIDCLogoutView, "persist_state")
@mock.patch.object(crypto, "get_random_string", return_value="mocked_state")
def test_view_logout_construct_oidc_logout_url(
mocked_get_random_string, mocked_persist_state
):
"""Should construct the logout URL to initiate the logout flow with the OIDC provider."""
user = factories.UserFactory()
request = RequestFactory().request()
request.user = user
middleware = SessionMiddleware(get_response=lambda x: x)
middleware.process_request(request)
request.session["oidc_id_token"] = "mocked_oidc_id_token"
request.session.save()
redirect_url = OIDCLogoutView().construct_oidc_logout_url(request)
mocked_persist_state.assert_called_once()
mocked_get_random_string.assert_called_once()
params = parse_qs(urlparse(redirect_url).query)
assert params["id_token_hint"][0] == "mocked_oidc_id_token"
assert params["state"][0] == "mocked_state"
url = reverse("oidc_logout_callback")
assert url in params["post_logout_redirect_uri"][0]
@override_settings(LOGOUT_REDIRECT_URL="/")
def test_view_logout_construct_oidc_logout_url_none_id_token():
"""If no ID token is available in the session,
the user should be redirected to the final URL."""
user = factories.UserFactory()
request = RequestFactory().request()
request.user = user
middleware = SessionMiddleware(get_response=lambda x: x)
middleware.process_request(request)
redirect_url = OIDCLogoutView().construct_oidc_logout_url(request)
assert redirect_url == "/"
@pytest.mark.parametrize(
"initial_state",
[None, {"other_state": "foo"}],
)
def test_view_logout_callback_wrong_state(initial_state):
"""Should raise an error if OIDC state doesn't match session data."""
user = factories.UserFactory()
request = RequestFactory().request()
request.user = user
middleware = SessionMiddleware(get_response=lambda x: x)
middleware.process_request(request)
if initial_state:
request.session["oidc_states"] = initial_state
request.session.save()
callback_view = OIDCLogoutCallbackView.as_view()
with pytest.raises(SuspiciousOperation) as excinfo:
callback_view(request)
assert (
str(excinfo.value) == "OIDC callback state not found in session `oidc_states`!"
)
@override_settings(LOGOUT_REDIRECT_URL="/example-logout")
def test_view_logout_callback():
"""If state matches, callback should clear OIDC state and redirects."""
user = factories.UserFactory()
request = RequestFactory().get("/logout-callback/", data={"state": "mocked_state"})
request.user = user
middleware = SessionMiddleware(get_response=lambda x: x)
middleware.process_request(request)
mocked_state = "mocked_state"
request.session["oidc_states"] = {mocked_state: {}}
request.session.save()
callback_view = OIDCLogoutCallbackView.as_view()
with mock.patch("mozilla_django_oidc.views.auth.logout") as mock_logout:
def clear_user(request):
# Assert state is cleared prior to logout
assert request.session["oidc_states"] == {}
request.user = AnonymousUser()
mock_logout.side_effect = clear_user
response = callback_view(request)
mock_logout.assert_called_once()
assert response.status_code == 302
assert response.url == "/example-logout"

View File

@@ -304,7 +304,7 @@ def test_api_document_accesses_create_email_in_receivers_language(via, mock_user
)
elif expected_language == "fr-fr":
assert (
f"{user.full_name} a partagé un document avec vous: {document.title}".lower()
f"{user.full_name} a partagé un document avec vous : {document.title}".lower()
in email_subject.lower()
)
assert "docs/" + str(document.id) + "/" in email_content.lower()

View File

@@ -575,7 +575,7 @@ def test_api_document_invitations_create_cannot_invite_existing_users():
document = factories.DocumentFactory(users=[(user, "owner")])
existing_user = factories.UserFactory()
# Build an invitation to the email of an exising identity in the db
# Build an invitation to the email of an existing identity in the db
invitation_values = {
"email": existing_user.email,
"role": random.choice(models.RoleChoices.values),

View File

@@ -150,7 +150,7 @@ def test_api_documents_ai_transform_authenticated_forbidden(reach, role):
@patch("openai.resources.chat.completions.Completions.create")
def test_api_documents_ai_transform_authenticated_success(mock_create, reach, role):
"""
Autenticated who are not related to a document should be able to request AI transform
Authenticated who are not related to a document should be able to request AI transform
if the link reach and role permit it.
"""
user = factories.UserFactory()

View File

@@ -99,7 +99,7 @@ def test_api_documents_ai_translate_anonymous_success(mock_create):
{
"role": "system",
"content": (
"Keep the same html stucture and formatting. "
"Keep the same html structure and formatting. "
"Translate the content in the html to the specified language Spanish. "
"Check the translation for accuracy and make any necessary corrections. "
"Do not provide any other information."
@@ -172,7 +172,7 @@ def test_api_documents_ai_translate_authenticated_forbidden(reach, role):
@patch("openai.resources.chat.completions.Completions.create")
def test_api_documents_ai_translate_authenticated_success(mock_create, reach, role):
"""
Autenticated who are not related to a document should be able to request AI translate
Authenticated who are not related to a document should be able to request AI translate
if the link reach and role permit it.
"""
user = factories.UserFactory()
@@ -197,7 +197,7 @@ def test_api_documents_ai_translate_authenticated_success(mock_create, reach, ro
{
"role": "system",
"content": (
"Keep the same html stucture and formatting. "
"Keep the same html structure and formatting. "
"Translate the content in the html to the "
"specified language Colombian Spanish. "
"Check the translation for accuracy and make any necessary corrections. "
@@ -274,7 +274,7 @@ def test_api_documents_ai_translate_success(mock_create, via, role, mock_user_te
{
"role": "system",
"content": (
"Keep the same html stucture and formatting. "
"Keep the same html structure and formatting. "
"Translate the content in the html to the "
"specified language Colombian Spanish. "
"Check the translation for accuracy and make any necessary corrections. "

View File

@@ -127,7 +127,7 @@ def test_api_documents_attachment_upload_authenticated_forbidden(reach, role):
)
def test_api_documents_attachment_upload_authenticated_success(reach, role):
"""
Autenticated users who are not related to a document should be able to upload
Authenticated users who are not related to a document should be able to upload
a file when the link reach and role permit it.
"""
user = factories.UserFactory()
@@ -255,7 +255,7 @@ def test_api_documents_attachment_upload_invalid(client):
def test_api_documents_attachment_upload_size_limit_exceeded(settings):
"""The uploaded file should not exceeed the maximum size in settings."""
"""The uploaded file should not exceed the maximum size in settings."""
settings.DOCUMENT_IMAGE_MAX_SIZE = 1048576 # 1 MB for test
user = factories.UserFactory()

View File

@@ -2,6 +2,7 @@
Tests for Documents API endpoint in impress's core app: children create
"""
from concurrent.futures import ThreadPoolExecutor
from uuid import uuid4
import pytest
@@ -249,3 +250,41 @@ def test_api_documents_children_create_force_id_existing():
assert response.json() == {
"id": ["A document with this ID already exists. You cannot override it."]
}
@pytest.mark.django_db(transaction=True)
def test_api_documents_create_document_children_race_condition():
"""
It should be possible to create several documents at the same time
without causing any race conditions or data integrity issues.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
factories.UserDocumentAccessFactory(user=user, document=document, role="owner")
def create_document():
return client.post(
f"/api/v1.0/documents/{document.id}/children/",
{
"title": "my child",
},
)
with ThreadPoolExecutor(max_workers=2) as executor:
future1 = executor.submit(create_document)
future2 = executor.submit(create_document)
response1 = future1.result()
response2 = future2.result()
assert response1.status_code == 201
assert response2.status_code == 201
document.refresh_from_db()
assert document.numchild == 2

View File

@@ -2,6 +2,7 @@
Tests for Documents API endpoint in impress's core app: create
"""
from concurrent.futures import ThreadPoolExecutor
from uuid import uuid4
import pytest
@@ -51,6 +52,36 @@ def test_api_documents_create_authenticated_success():
assert document.accesses.filter(role="owner", user=user).exists()
@pytest.mark.django_db(transaction=True)
def test_api_documents_create_document_race_condition():
"""
It should be possible to create several documents at the same time
without causing any race conditions or data integrity issues.
"""
def create_document(title):
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
return client.post(
"/api/v1.0/documents/",
{
"title": title,
},
format="json",
)
with ThreadPoolExecutor(max_workers=2) as executor:
future1 = executor.submit(create_document, "my document 1")
future2 = executor.submit(create_document, "my document 2")
response1 = future1.result()
response2 = future2.result()
assert response1.status_code == 201
assert response2.status_code == 201
def test_api_documents_create_authenticated_title_null():
"""It should be possible to create several documents with a null title."""
user = factories.UserFactory()

View File

@@ -4,6 +4,7 @@ Tests for Documents API endpoint in impress's core app: create
# pylint: disable=W0621
from concurrent.futures import ThreadPoolExecutor
from unittest.mock import patch
from django.core import mail
@@ -278,7 +279,7 @@ def test_api_documents_create_for_owner_existing_user_email_no_sub_with_fallback
"""
It should be possible to create a document on behalf of a pre-existing user for
who the sub was not found if the settings allow it. This edge case should not
happen in a healthy OIDC federation but can be usefull if an OIDC provider modifies
happen in a healthy OIDC federation but can be useful if an OIDC provider modifies
users sub on each login for example...
"""
user = factories.UserFactory(language="en-us")
@@ -425,6 +426,36 @@ def test_api_documents_create_for_owner_new_user_no_sub_no_fallback_allow_duplic
assert document.creator == user
@pytest.mark.django_db(transaction=True)
def test_api_documents_create_document_race_condition():
"""
It should be possible to create several documents at the same time
without causing any race conditions or data integrity issues.
"""
def create_document(title):
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
return client.post(
"/api/v1.0/documents/",
{
"title": title,
},
format="json",
)
with ThreadPoolExecutor(max_workers=2) as executor:
future1 = executor.submit(create_document, "my document 1")
future2 = executor.submit(create_document, "my document 2")
response1 = future1.result()
response2 = future2.result()
assert response1.status_code == 201
assert response2.status_code == 201
@patch.object(ServerCreateDocumentSerializer, "_send_email_notification")
@override_settings(SERVER_TO_SERVER_API_TOKENS=["DummyToken"], LANGUAGE_CODE="de-de")
def test_api_documents_create_for_owner_with_default_language(

View File

@@ -7,6 +7,7 @@ from faker import Faker
from rest_framework.test import APIClient
from core import factories
from core.api.filters import remove_accents
fake = Faker()
pytestmark = pytest.mark.django_db
@@ -49,14 +50,16 @@ def test_api_documents_descendants_filter_unknown_field():
[
("Project Alpha", 1), # Exact match
("project", 2), # Partial match (case-insensitive)
("Guide", 1), # Word match within a title
("Guide", 2), # Word match within a title
("Special", 0), # No match (nonexistent keyword)
("2024", 2), # Match by numeric keyword
("", 5), # Empty string
("", 6), # Empty string
("velo", 1), # Accent-insensitive match (velo vs vélo)
("bêta", 1), # Accent-insensitive match (bêta vs beta)
],
)
def test_api_documents_descendants_filter_title(query, nb_results):
"""Authenticated users should be able to search documents by their title."""
"""Authenticated users should be able to search documents by their unaccented title."""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
@@ -70,6 +73,7 @@ def test_api_documents_descendants_filter_title(query, nb_results):
"User Guide",
"Financial Report 2024",
"Annual Review 2024",
"Guide du vélo urbain", # <-- Title with accent for accent-insensitive test
]
for title in titles:
factories.DocumentFactory(title=title, parent=document)
@@ -85,4 +89,7 @@ def test_api_documents_descendants_filter_title(query, nb_results):
# Ensure all results contain the query in their title
for result in results:
assert query.lower().strip() in result["title"].lower()
assert (
remove_accents(query).lower().strip()
in remove_accents(result["title"]).lower()
)

View File

@@ -310,7 +310,7 @@ def test_api_documents_move_authenticated_deleted_target_as_child(position):
def test_api_documents_move_authenticated_deleted_target_as_sibling(position):
"""
It should not be possible to move a document as a sibling of a deleted target document
if the user has no rigths on its parent.
if the user has no rights on its parent.
"""
user = factories.UserFactory()
client = APIClient()

View File

@@ -19,7 +19,6 @@ pytestmark = pytest.mark.django_db
COLLABORATION_WS_URL="http://testcollab/",
CRISP_WEBSITE_ID="123",
FRONTEND_CSS_URL="http://testcss/",
FRONTEND_HOMEPAGE_FEATURE_ENABLED=True,
FRONTEND_FOOTER_FEATURE_ENABLED=True,
FRONTEND_THEME="test-theme",
MEDIA_BASE_URL="http://testserver/",
@@ -50,6 +49,7 @@ def test_api_config(is_authenticated):
["fr-fr", "Français"],
["de-de", "Deutsch"],
["nl-nl", "Nederlands"],
["es-es", "Español"],
],
"LANGUAGE_CODE": "en-us",
"MEDIA_BASE_URL": "http://testserver/",

View File

@@ -791,7 +791,7 @@ def test_models_documents__email_invitation__success_fr():
assert (
f"Test Sender2 (sender2@example.com) vous a invité avec le rôle &quot;propriétaire&quot; "
f"sur le document suivant: {document.title}" in email_content
f"sur le document suivant : {document.title}" in email_content
)
assert f"docs/{document.id}/" in email_content

View File

@@ -3,10 +3,10 @@
from django.conf import settings
from django.urls import include, path, re_path
from lasuite.oidc_login.urls import urlpatterns as oidc_urls
from rest_framework.routers import DefaultRouter
from core.api import viewsets
from core.authentication.urls import urlpatterns as oidc_urls
# - Main endpoints
router = DefaultRouter()

View File

@@ -23,7 +23,7 @@ from sentry_sdk.integrations.logging import ignore_logger
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA_DIR = os.path.join("/", "data")
DATA_DIR = os.getenv("DATA_DIR", os.path.join("/", "data"))
def get_release():
@@ -239,6 +239,7 @@ class Base(Configuration):
("fr-fr", "Français"),
("de-de", "Deutsch"),
("nl-nl", "Nederlands"),
("es-es", "Español"),
)
)
@@ -332,6 +333,12 @@ class Base(Configuration):
"rest_framework.parsers.JSONParser",
"nested_multipart_parser.drf.DrfNestedParser",
],
"DEFAULT_RENDERER_CLASSES": [
# 🔒️ Disable BrowsableAPIRenderer which provides forms allowing a user to
# see all the data in the database (ie a serializer with a ForeignKey field
# will generate a form with a field with all possible values of the FK).
"rest_framework.renderers.JSONRenderer",
],
"EXCEPTION_HANDLER": "core.api.exception_handler",
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
@@ -411,7 +418,7 @@ class Base(Configuration):
None, environ_name="FRONTEND_THEME", environ_prefix=None
)
FRONTEND_HOMEPAGE_FEATURE_ENABLED = values.BooleanValue(
default=False,
default=True,
environ_name="FRONTEND_HOMEPAGE_FEATURE_ENABLED",
environ_prefix=None,
)
@@ -520,6 +527,28 @@ class Base(Configuration):
environ_name="OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION",
environ_prefix=None,
)
OIDC_USE_PKCE = values.BooleanValue(
default=False, environ_name="OIDC_USE_PKCE", environ_prefix=None
)
OIDC_PKCE_CODE_CHALLENGE_METHOD = values.Value(
default="S256",
environ_name="OIDC_PKCE_CODE_CHALLENGE_METHOD",
environ_prefix=None,
)
OIDC_PKCE_CODE_VERIFIER_SIZE = values.IntegerValue(
default=64, environ_name="OIDC_PKCE_CODE_VERIFIER_SIZE", environ_prefix=None
)
OIDC_STORE_ACCESS_TOKEN = values.BooleanValue(
default=False, environ_name="OIDC_STORE_ACCESS_TOKEN", environ_prefix=None
)
OIDC_STORE_REFRESH_TOKEN = values.BooleanValue(
default=False, environ_name="OIDC_STORE_REFRESH_TOKEN", environ_prefix=None
)
OIDC_STORE_REFRESH_TOKEN_KEY = values.Value(
default=None,
environ_name="OIDC_STORE_REFRESH_TOKEN_KEY",
environ_prefix=None,
)
# WARNING: Enabling this setting allows multiple user accounts to share the same email
# address. This may cause security issues and is not recommended for production use when
@@ -533,14 +562,23 @@ class Base(Configuration):
USER_OIDC_ESSENTIAL_CLAIMS = values.ListValue(
default=[], environ_name="USER_OIDC_ESSENTIAL_CLAIMS", environ_prefix=None
)
USER_OIDC_FIELDS_TO_FULLNAME = values.ListValue(
default=["first_name", "last_name"],
environ_name="USER_OIDC_FIELDS_TO_FULLNAME",
OIDC_USERINFO_FULLNAME_FIELDS = values.ListValue(
default=values.ListValue( # retrocompatibility
default=["first_name", "last_name"],
environ_name="USER_OIDC_FIELDS_TO_FULLNAME",
environ_prefix=None,
),
environ_name="OIDC_USERINFO_FULLNAME_FIELDS",
environ_prefix=None,
)
USER_OIDC_FIELD_TO_SHORTNAME = values.Value(
default="first_name",
environ_name="USER_OIDC_FIELD_TO_SHORTNAME",
OIDC_USERINFO_SHORTNAME_FIELD = values.Value(
default=values.Value( # retrocompatibility
default="first_name",
environ_name="USER_OIDC_FIELD_TO_SHORTNAME",
environ_prefix=None,
),
environ_name="OIDC_USERINFO_SHORTNAME_FIELD",
environ_prefix=None,
)

View File

@@ -0,0 +1,390 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"Last-Translator: \n"
"Language-Team: Breton\n"
"Language: br_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=5; plural=(n%10==1 && (n%100!=11 || n%100!=71 || n%100!=91) ? 0 : n%10==2 && (n%100!=12 || n%100!=72 || n%100!=92) ? 1 : ((n%10>=3 && n%10<=4) || n%10==9) && ((n%100 < 10 || n%100 > 19) || (n%100 < 70 || n%100 > 79) || (n%100 < 90 || n%100 > 99)) ? 2 : (n!=0 && n%1;\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Language: br-FR\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
#: build/lib/core/admin.py:37 core/admin.py:37
msgid "Personal info"
msgstr "Titouroù personel"
#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50
#: core/admin.py:138
msgid "Permissions"
msgstr "Aotreoù"
#: build/lib/core/admin.py:62 core/admin.py:62
msgid "Important dates"
msgstr "Deiziadoù a-bouez"
#: build/lib/core/admin.py:148 core/admin.py:148
msgid "Tree structure"
msgstr "Gwezennadur"
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
msgid "Title"
msgstr "Titl"
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
msgid "Creator is me"
msgstr "Me eo an aozer"
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
msgid "Favorite"
msgstr "Sinedoù"
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr "Ur restr nevez a zo bet krouet ganeoc'h!"
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr "C'hwi zo bet disklaeriet perc'henn ur restr nevez:"
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr "Korf"
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr "Doare korf"
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr "Stumm"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#, python-brace-format
msgid "copy of {title}"
msgstr "eilenn {title}"
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr "Bugel kentañ"
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr "Bugel diwezhañ"
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr ""
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr ""
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr "Kleiz"
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr "Dehoù"
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr "Lenner"
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr ""
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr "Merour"
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr "Perc'henn"
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr ""
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr ""
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr "Publik"
#: build/lib/core/models.py:154 core/models.py:154
msgid "id"
msgstr "id"
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr ""
#: build/lib/core/models.py:161 core/models.py:161
msgid "created on"
msgstr "krouet d'ar/al"
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr ""
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr "hizivaet d'ar/al"
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr ""
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr ""
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr ""
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr ""
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr ""
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr "anv klok"
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr "anv berr"
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr ""
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr ""
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr "yezh"
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr ""
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr ""
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr "trevnad"
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr ""
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr ""
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr ""
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr ""
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr ""
#: build/lib/core/models.py:286 core/models.py:286
msgid "user"
msgstr "implijer"
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr "implijerien"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr "titl"
#: build/lib/core/models.py:471 core/models.py:471
msgid "excerpt"
msgstr ""
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr ""
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr ""
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr ""
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr "publik"
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr "Patrom"
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr "Patromoù"
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr ""
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr ""
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3
msgid "Logo email"
msgstr ""
#: core/templates/mail/html/invitation.html:209
#: core/templates/mail/text/invitation.txt:10
msgid "Open"
msgstr "Digeriñ"
#: core/templates/mail/html/invitation.html:226
#: core/templates/mail/text/invitation.txt:14
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
msgstr ""
#: core/templates/mail/html/invitation.html:233
#: core/templates/mail/text/invitation.txt:16
#, python-format
msgid " Brought to you by %(brandname)s "
msgstr ""

View File

@@ -0,0 +1,399 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-04 13:46+0000\n"
"PO-Revision-Date: 2025-04-16 16:32\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Language: zh-CN\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
#: build/lib/core/admin.py:37 core/admin.py:37
msgid "Personal info"
msgstr "个人信息"
#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50
#: core/admin.py:138
msgid "Permissions"
msgstr "权限"
#: build/lib/core/admin.py:62 core/admin.py:62
msgid "Important dates"
msgstr "重要日期"
#: build/lib/core/admin.py:148 core/admin.py:148
msgid "Tree structure"
msgstr "树状结构"
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
msgid "Title"
msgstr "标题"
#: build/lib/core/api/filters.py:30 core/api/filters.py:30
msgid "Creator is me"
msgstr "创建者是我"
#: build/lib/core/api/filters.py:33 core/api/filters.py:33
msgid "Favorite"
msgstr "收藏"
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr "已为您创建了一份新文档!"
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr "您已被授予新文档的所有权:"
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr "正文"
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr "正文类型"
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr "格式"
#: build/lib/core/api/viewsets.py:944 core/api/viewsets.py:944
#, python-brace-format
msgid "copy of {title}"
msgstr "{title} 的副本"
#: build/lib/core/authentication/backends.py:61
#: core/authentication/backends.py:61
msgid "Invalid response format or token verification failed"
msgstr "响应格式无效或令牌验证失败"
#: build/lib/core/authentication/backends.py:108
#: core/authentication/backends.py:108
msgid "User account is disabled"
msgstr "用户账户已被禁用"
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr "第一个子项"
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr "最后一个子项"
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr "第一个同级项"
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr "最后一个同级项"
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr "左"
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr "右"
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr "阅读者"
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr "编辑者"
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr "超级管理员"
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr "所有者"
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr "受限的"
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr "已验证"
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr "公开"
#: build/lib/core/models.py:154 core/models.py:154
msgid "id"
msgstr "id"
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr "记录的主密钥为 UUID"
#: build/lib/core/models.py:161 core/models.py:161
msgid "created on"
msgstr "创建时间"
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr "记录的创建日期和时间"
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr "更新时间"
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr "记录的最后更新时间"
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr "未找到具有该 sub 的用户,但该邮箱已关联到一个注册用户。"
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr "请输入有效的 sub。该值只能包含字母、数字及 @/./+/-/_/: 字符。"
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr "sub"
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr "必填。最多 255 个字符,仅允许字母、数字及 @/./+/-/_/: 字符。"
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr "全名"
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr "简称"
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr "身份电子邮件地址"
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr "管理员电子邮件地址"
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr "语言"
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr "用户希望看到的界面语言。"
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr "用户查看时间希望的时区。"
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr "设备"
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr "用户是设备还是真实用户。"
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr "员工状态"
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr "用户是否可以登录该管理员站点。"
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr "激活"
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr "是否应将此用户视为活跃用户。取消选择此选项而不是删除账户。"
#: build/lib/core/models.py:286 core/models.py:286
msgid "user"
msgstr "用户"
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr "个用户"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr "标题"
#: build/lib/core/models.py:471 core/models.py:471
msgid "excerpt"
msgstr "摘要"
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr "文档"
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr "个文档"
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr "未命名文档"
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} 与您共享了一个文档!"
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} 邀请您以“{role}”角色访问以下文档:"
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} 与您共享了一个文档:{title}"
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr "文档/用户链接跟踪"
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr "个文档/用户链接跟踪"
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr "此文档/用户的链接跟踪已存在。"
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr "文档收藏"
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr "文档收藏夹"
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "该文档已被同一用户的收藏关系实例关联。"
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr "文档/用户关系"
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relations"
msgstr "文档/用户关系集"
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "This user is already in this document."
msgstr "该用户已在此文档中。"
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "This team is already in this document."
msgstr "该团队已在此文档中。"
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr "必须设置用户或团队之一,不能同时设置两者。"
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr "说明"
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr "代码"
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr "公开"
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr "该模板是否公开供任何人使用。"
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr "模板"
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr "模板"
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr "模板/用户关系"
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr "模板/用户关系集"
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr "该用户已在此模板中。"
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr "该团队已在此模板中。"
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr "电子邮件地址"
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr "文档邀请"
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr "文档邀请"
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr "此电子邮件已经与现有注册用户关联。"
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3
msgid "Logo email"
msgstr "徽标邮件"
#: core/templates/mail/html/invitation.html:209
#: core/templates/mail/text/invitation.txt:10
msgid "Open"
msgstr "打开"
#: core/templates/mail/html/invitation.html:226
#: core/templates/mail/text/invitation.txt:14
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
msgstr " Docs——您的全新必备工具帮助团队组织、共享和协作处理文档。 "
#: core/templates/mail/html/invitation.html:233
#: core/templates/mail/text/invitation.txt:16
#, python-format
msgid " Brought to you by %(brandname)s "
msgstr " 由 %(brandname)s 倾力打造。 "

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-13 11:41+0000\n"
"PO-Revision-Date: 2025-03-17 13:58\n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -34,204 +34,199 @@ msgstr "Wichtige Daten"
msgid "Tree structure"
msgstr "Baumstruktur"
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
msgid "Title"
msgstr "Titel"
#: build/lib/core/api/filters.py:30 core/api/filters.py:30
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
msgid "Creator is me"
msgstr "Ersteller bin ich"
#: build/lib/core/api/filters.py:33 core/api/filters.py:33
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
msgid "Favorite"
msgstr "Favorit"
#: build/lib/core/api/serializers.py:354 core/api/serializers.py:354
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr "Ein neues Dokument wurde in Ihrem Namen erstellt!"
#: build/lib/core/api/serializers.py:358 core/api/serializers.py:358
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr "Sie sind Besitzer eines neuen Dokuments:"
#: build/lib/core/api/serializers.py:473 core/api/serializers.py:473
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr "Inhalt"
#: build/lib/core/api/serializers.py:476 core/api/serializers.py:476
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr "Typ"
#: build/lib/core/api/serializers.py:482 core/api/serializers.py:482
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr ""
msgstr "Format"
#: build/lib/core/authentication/backends.py:61
#: core/authentication/backends.py:61
msgid "Invalid response format or token verification failed"
msgstr "Ungültiges Antwortformat oder Token-Verifizierung fehlgeschlagen"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#, python-brace-format
msgid "copy of {title}"
msgstr "Kopie von {title}"
#: build/lib/core/authentication/backends.py:108
#: core/authentication/backends.py:108
msgid "User account is disabled"
msgstr "Benutzerkonto ist deaktiviert"
#: build/lib/core/enums.py:19 core/enums.py:19
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr "Erstes Unterelement"
#: build/lib/core/enums.py:20 core/enums.py:20
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr "Letztes Unterelement"
#: build/lib/core/enums.py:21 core/enums.py:21
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr "Erstes Nebenelement"
#: build/lib/core/enums.py:22 core/enums.py:22
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr "Letztes Nebenelement"
#: build/lib/core/enums.py:23 core/enums.py:23
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr "Links"
#: build/lib/core/enums.py:24 core/enums.py:24
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr "Rechts"
#: build/lib/core/models.py:55 build/lib/core/models.py:62 core/models.py:55
#: core/models.py:62
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr "Lesen"
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr "Bearbeiten"
#: build/lib/core/models.py:64 core/models.py:64
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr "Administrator"
#: build/lib/core/models.py:65 core/models.py:65
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr "Besitzer"
#: build/lib/core/models.py:76 core/models.py:76
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr "Beschränkt"
#: build/lib/core/models.py:80 core/models.py:80
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr "Authentifiziert"
#: build/lib/core/models.py:82 core/models.py:82
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr "Öffentlich"
#: build/lib/core/models.py:153 core/models.py:153
msgid "id"
msgstr ""
#: build/lib/core/models.py:154 core/models.py:154
msgid "id"
msgstr "id"
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr "primärer Schlüssel für den Datensatz als UUID"
#: build/lib/core/models.py:160 core/models.py:160
#: build/lib/core/models.py:161 core/models.py:161
msgid "created on"
msgstr "Erstellt"
#: build/lib/core/models.py:161 core/models.py:161
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr "Datum und Uhrzeit, an dem ein Datensatz erstellt wurde"
#: build/lib/core/models.py:166 core/models.py:166
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr "Aktualisiert"
#: build/lib/core/models.py:167 core/models.py:167
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr "Datum und Uhrzeit, an dem zuletzt aktualisiert wurde"
#: build/lib/core/models.py:203 core/models.py:203
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr "Wir konnten keinen Benutzer mit diesem Abo finden, aber die E-Mail-Adresse ist bereits einem registrierten Benutzer zugeordnet."
#: build/lib/core/models.py:216 core/models.py:216
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr "Geben Sie eine gültige Unterseite ein. Dieser Wert darf nur Buchstaben, Zahlen und die @/./+/-/_/: Zeichen enthalten."
#: build/lib/core/models.py:222 core/models.py:222
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr "unter"
#: build/lib/core/models.py:224 core/models.py:224
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr "Erforderlich. 255 Zeichen oder weniger. Buchstaben, Zahlen und die Zeichen @/./+/-/_/:"
#: build/lib/core/models.py:233 core/models.py:233
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr "Name"
#: build/lib/core/models.py:234 core/models.py:234
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr "Kurzbezeichnung"
#: build/lib/core/models.py:236 core/models.py:236
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr "Identitäts-E-Mail-Adresse"
#: build/lib/core/models.py:241 core/models.py:241
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr "Admin E-Mail-Adresse"
#: build/lib/core/models.py:248 core/models.py:248
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr "Sprache"
#: build/lib/core/models.py:249 core/models.py:249
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr "Die Sprache, in der der Benutzer die Benutzeroberfläche sehen möchte."
#: build/lib/core/models.py:257 core/models.py:257
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr "Die Zeitzone, in der der Nutzer Zeiten sehen möchte."
#: build/lib/core/models.py:260 core/models.py:260
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr "Gerät"
#: build/lib/core/models.py:262 core/models.py:262
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr "Ob der Benutzer ein Gerät oder ein echter Benutzer ist."
#: build/lib/core/models.py:265 core/models.py:265
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr "Status des Teammitgliedes"
#: build/lib/core/models.py:267 core/models.py:267
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr "Gibt an, ob der Benutzer sich in diese Admin-Seite einloggen kann."
#: build/lib/core/models.py:270 core/models.py:270
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr "aktiviert"
#: build/lib/core/models.py:273 core/models.py:273
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr "Ob dieser Benutzer als aktiviert behandelt werden soll. Deaktivieren Sie diese Option, anstatt Konten zu löschen."
#: build/lib/core/models.py:285 core/models.py:285
#: build/lib/core/models.py:286 core/models.py:286
msgid "user"
msgstr "Benutzer"
#: build/lib/core/models.py:286 core/models.py:286
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr "Benutzer"
#: build/lib/core/models.py:470 build/lib/core/models.py:1074
#: core/models.py:470 core/models.py:1074
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr "Titel"
@@ -239,136 +234,136 @@ msgstr "Titel"
msgid "excerpt"
msgstr "Auszug"
#: build/lib/core/models.py:504 core/models.py:504
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr "Dokument"
#: build/lib/core/models.py:505 core/models.py:505
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr "Dokumente"
#: build/lib/core/models.py:517 build/lib/core/models.py:826 core/models.py:517
#: core/models.py:826
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr "Unbenanntes Dokument"
#: build/lib/core/models.py:861 core/models.py:861
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} hat ein Dokument mit Ihnen geteilt!"
#: build/lib/core/models.py:865 core/models.py:865
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} hat Sie mit der Rolle \"{role}\" zu folgendem Dokument eingeladen:"
#: build/lib/core/models.py:871 core/models.py:871
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} hat ein Dokument mit Ihnen geteilt: {title}"
#: build/lib/core/models.py:969 core/models.py:969
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr "Dokument/Benutzer Linkverfolgung"
#: build/lib/core/models.py:970 core/models.py:970
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr "Dokument/Benutzer Linkverfolgung"
#: build/lib/core/models.py:976 core/models.py:976
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr ""
msgstr "Für dieses Dokument/ diesen Benutzer ist bereits eine Linkverfolgung vorhanden."
#: build/lib/core/models.py:999 core/models.py:999
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr "Dokumentenfavorit"
#: build/lib/core/models.py:1000 core/models.py:1000
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr "Dokumentfavoriten"
#: build/lib/core/models.py:1006 core/models.py:1006
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "Dieses Dokument ist bereits durch den gleichen Benutzer favorisiert worden."
#: build/lib/core/models.py:1028 core/models.py:1028
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr "Dokument/Benutzerbeziehung"
#: build/lib/core/models.py:1029 core/models.py:1029
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relations"
msgstr "Dokument/Benutzerbeziehungen"
#: build/lib/core/models.py:1035 core/models.py:1035
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "This user is already in this document."
msgstr "Dieser Benutzer befindet sich bereits in diesem Dokument."
#: build/lib/core/models.py:1041 core/models.py:1041
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "This team is already in this document."
msgstr "Dieses Team befindet sich bereits in diesem Dokument."
#: build/lib/core/models.py:1047 build/lib/core/models.py:1161
#: core/models.py:1047 core/models.py:1161
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr "Benutzer oder Team müssen gesetzt werden, nicht beides."
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr "Beschreibung"
#: build/lib/core/models.py:1076 core/models.py:1076
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr "Code"
#: build/lib/core/models.py:1077 core/models.py:1077
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr "CSS"
#: build/lib/core/models.py:1079 core/models.py:1079
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr "öffentlich"
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr "Ob diese Vorlage für jedermann öffentlich ist."
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr "Vorlage"
#: build/lib/core/models.py:1088 core/models.py:1088
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr "Vorlagen"
#: build/lib/core/models.py:1142 core/models.py:1142
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr "Vorlage/Benutzer-Beziehung"
#: build/lib/core/models.py:1143 core/models.py:1143
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr "Vorlage/Benutzerbeziehungen"
#: build/lib/core/models.py:1149 core/models.py:1149
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr "Dieser Benutzer ist bereits in dieser Vorlage."
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr "Dieses Team ist bereits in diesem Template."
#: build/lib/core/models.py:1178 core/models.py:1178
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr "E-Mail-Adresse"
#: build/lib/core/models.py:1197 core/models.py:1197
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr "Einladung zum Dokument"
#: build/lib/core/models.py:1198 core/models.py:1198
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr "Dokumenteinladungen"
#: build/lib/core/models.py:1218 core/models.py:1218
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr "Diese E-Mail ist bereits einem registrierten Benutzer zugeordnet."

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-13 11:41+0000\n"
"PO-Revision-Date: 2025-03-17 13:58\n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"Last-Translator: \n"
"Language-Team: English\n"
"Language: en_US\n"
@@ -34,204 +34,199 @@ msgstr ""
msgid "Tree structure"
msgstr ""
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
msgid "Title"
msgstr ""
#: build/lib/core/api/filters.py:30 core/api/filters.py:30
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
msgid "Creator is me"
msgstr ""
#: build/lib/core/api/filters.py:33 core/api/filters.py:33
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
msgid "Favorite"
msgstr ""
#: build/lib/core/api/serializers.py:354 core/api/serializers.py:354
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr ""
#: build/lib/core/api/serializers.py:358 core/api/serializers.py:358
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr ""
#: build/lib/core/api/serializers.py:473 core/api/serializers.py:473
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr ""
#: build/lib/core/api/serializers.py:476 core/api/serializers.py:476
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr ""
#: build/lib/core/api/serializers.py:482 core/api/serializers.py:482
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr ""
#: build/lib/core/authentication/backends.py:61
#: core/authentication/backends.py:61
msgid "Invalid response format or token verification failed"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#, python-brace-format
msgid "copy of {title}"
msgstr ""
#: build/lib/core/authentication/backends.py:108
#: core/authentication/backends.py:108
msgid "User account is disabled"
msgstr ""
#: build/lib/core/enums.py:19 core/enums.py:19
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr ""
#: build/lib/core/enums.py:20 core/enums.py:20
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr ""
#: build/lib/core/enums.py:21 core/enums.py:21
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr ""
#: build/lib/core/enums.py:22 core/enums.py:22
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr ""
#: build/lib/core/enums.py:23 core/enums.py:23
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr ""
#: build/lib/core/enums.py:24 core/enums.py:24
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr ""
#: build/lib/core/models.py:55 build/lib/core/models.py:62 core/models.py:55
#: core/models.py:62
msgid "Reader"
msgstr ""
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr ""
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr ""
#: build/lib/core/models.py:64 core/models.py:64
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr ""
#: build/lib/core/models.py:65 core/models.py:65
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr ""
#: build/lib/core/models.py:76 core/models.py:76
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr ""
#: build/lib/core/models.py:80 core/models.py:80
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr ""
#: build/lib/core/models.py:82 core/models.py:82
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr ""
#: build/lib/core/models.py:153 core/models.py:153
#: build/lib/core/models.py:154 core/models.py:154
msgid "id"
msgstr ""
#: build/lib/core/models.py:154 core/models.py:154
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr ""
#: build/lib/core/models.py:160 core/models.py:160
#: build/lib/core/models.py:161 core/models.py:161
msgid "created on"
msgstr ""
#: build/lib/core/models.py:161 core/models.py:161
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr ""
#: build/lib/core/models.py:166 core/models.py:166
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr ""
#: build/lib/core/models.py:167 core/models.py:167
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr ""
#: build/lib/core/models.py:203 core/models.py:203
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr ""
#: build/lib/core/models.py:216 core/models.py:216
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr ""
#: build/lib/core/models.py:222 core/models.py:222
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr ""
#: build/lib/core/models.py:224 core/models.py:224
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr ""
#: build/lib/core/models.py:233 core/models.py:233
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr ""
#: build/lib/core/models.py:234 core/models.py:234
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr ""
#: build/lib/core/models.py:236 core/models.py:236
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr ""
#: build/lib/core/models.py:241 core/models.py:241
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr ""
#: build/lib/core/models.py:248 core/models.py:248
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr ""
#: build/lib/core/models.py:249 core/models.py:249
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr ""
#: build/lib/core/models.py:257 core/models.py:257
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr ""
#: build/lib/core/models.py:260 core/models.py:260
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr ""
#: build/lib/core/models.py:262 core/models.py:262
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr ""
#: build/lib/core/models.py:265 core/models.py:265
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr ""
#: build/lib/core/models.py:267 core/models.py:267
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr ""
#: build/lib/core/models.py:270 core/models.py:270
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr ""
#: build/lib/core/models.py:273 core/models.py:273
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr ""
#: build/lib/core/models.py:285 core/models.py:285
#: build/lib/core/models.py:286 core/models.py:286
msgid "user"
msgstr ""
#: build/lib/core/models.py:286 core/models.py:286
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr ""
#: build/lib/core/models.py:470 build/lib/core/models.py:1074
#: core/models.py:470 core/models.py:1074
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr ""
@@ -239,136 +234,136 @@ msgstr ""
msgid "excerpt"
msgstr ""
#: build/lib/core/models.py:504 core/models.py:504
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr ""
#: build/lib/core/models.py:505 core/models.py:505
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:517 build/lib/core/models.py:826 core/models.py:517
#: core/models.py:826
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:861 core/models.py:861
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:865 core/models.py:865
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:871 core/models.py:871
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:969 core/models.py:969
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:970 core/models.py:970
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:976 core/models.py:976
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:999 core/models.py:999
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:1000 core/models.py:1000
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1006 core/models.py:1006
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1028 core/models.py:1028
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1029 core/models.py:1029
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1035 core/models.py:1035
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1041 core/models.py:1041
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1047 build/lib/core/models.py:1161
#: core/models.py:1047 core/models.py:1161
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr ""
#: build/lib/core/models.py:1076 core/models.py:1076
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr ""
#: build/lib/core/models.py:1077 core/models.py:1077
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr ""
#: build/lib/core/models.py:1079 core/models.py:1079
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr ""
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr ""
#: build/lib/core/models.py:1088 core/models.py:1088
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1142 core/models.py:1142
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1143 core/models.py:1143
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1149 core/models.py:1149
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1178 core/models.py:1178
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr ""
#: build/lib/core/models.py:1197 core/models.py:1197
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:1198 core/models.py:1198
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:1218 core/models.py:1218
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr ""

View File

@@ -0,0 +1,390 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Language: es-ES\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
#: build/lib/core/admin.py:37 core/admin.py:37
msgid "Personal info"
msgstr "Información Personal"
#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50
#: core/admin.py:138
msgid "Permissions"
msgstr "Permisos"
#: build/lib/core/admin.py:62 core/admin.py:62
msgid "Important dates"
msgstr "Fechas importantes"
#: build/lib/core/admin.py:148 core/admin.py:148
msgid "Tree structure"
msgstr "Estructura en árbol"
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
msgid "Title"
msgstr "Título"
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
msgid "Creator is me"
msgstr "Yo soy el creador"
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
msgid "Favorite"
msgstr "Favorito"
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr "¡Un nuevo documento se ha creado por ti!"
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr "Se le ha concedido la propiedad de un nuevo documento :"
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr "Cuerpo"
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr "Tipo de Cuerpo"
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr "Formato"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#, python-brace-format
msgid "copy of {title}"
msgstr "copia de {title}"
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr "Primer nodo"
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr "Último nodo"
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr "Primera relación"
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr "Última relación"
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr "Izquierda"
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr "Derecha"
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr "Lector"
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr "Editor"
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr "Administrador"
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr "Propietario"
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr "Restringido"
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr "Autentificado"
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr "Público"
#: build/lib/core/models.py:154 core/models.py:154
msgid "id"
msgstr "id"
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr "clave primaria para el registro como UUID"
#: build/lib/core/models.py:161 core/models.py:161
msgid "created on"
msgstr "creado el"
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr "fecha y hora en la que se creó un registro"
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr "actualizado el"
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr "fecha y hora en la que un registro fue actualizado por última vez"
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr "No se ha podido encontrar un usuario con este sub (UUID), pero el correo electrónico ya está asociado con un usuario."
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr "Introduzca un sub (UUID) válido. Este valor solo puede contener letras, números y los siguientes caracteres @/./+/-/_/:"
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr "sub (UUID)"
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr "Requerido. 255 caracteres o menos. Letras, números y los siguientes caracteres @/./+/-/_/: solamente."
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr "nombre completo"
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr "nombre abreviado"
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr "correo electrónico de identidad"
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr "correo electrónico del administrador"
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr "idioma"
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr "El idioma en el que el usuario desea ver la interfaz."
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr "La zona horaria en la que el usuario quiere ver los tiempos."
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr "dispositivo"
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr "Si el usuario es un dispositivo o un usuario real."
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr "rol en el equipo"
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr "Si el usuario puede iniciar sesión en esta página web de administración."
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr "activo"
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr "Si este usuario debe ser considerado como activo. Deseleccionar en lugar de eliminar cuentas."
#: build/lib/core/models.py:286 core/models.py:286
msgid "user"
msgstr "usuario"
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr "usuarios"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr "título"
#: build/lib/core/models.py:471 core/models.py:471
msgid "excerpt"
msgstr "resumen"
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr "Documento"
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr "Documentos"
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr "Documento sin título"
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "¡{name} ha compartido un documento contigo!"
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "Te ha invitado {name} al siguiente documento con el rol \"{role}\" :"
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} ha compartido un documento contigo: {title}"
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr "Traza del enlace de documento/usuario"
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr "Trazas del enlace de documento/usuario"
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr "Ya existe una traza de enlace para este documento/usuario."
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr "Documento favorito"
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr "Documentos favoritos"
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "Este documento ya ha sido marcado como favorito por el usuario."
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr "Relación documento/usuario"
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relations"
msgstr "Relaciones documento/usuario"
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "This user is already in this document."
msgstr "Este usuario ya forma parte del documento."
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "This team is already in this document."
msgstr "Este equipo ya forma parte del documento."
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr "Debe establecerse un usuario o un equipo, no ambos."
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr "descripción"
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr "código"
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr "público"
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr "Si esta plantilla es pública para que cualquiera la utilice."
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr "Plantilla"
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr "Plantillas"
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr "Relación plantilla/usuario"
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr "Relaciones plantilla/usuario"
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr "Este usuario ya forma parte de la plantilla."
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr "Este equipo ya se encuentra en esta plantilla."
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr "dirección de correo electrónico"
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr "Invitación al documento"
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr "Invitaciones a documentos"
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr "Este correo electrónico está asociado a un usuario registrado."
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3
msgid "Logo email"
msgstr "Logo de correo electrónico"
#: core/templates/mail/html/invitation.html:209
#: core/templates/mail/text/invitation.txt:10
msgid "Open"
msgstr "Abrir"
#: core/templates/mail/html/invitation.html:226
#: core/templates/mail/text/invitation.txt:14
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
msgstr "Docs, su nueva herramienta esencial para organizar, compartir y colaborar en sus documentos como equipo."
#: core/templates/mail/html/invitation.html:233
#: core/templates/mail/text/invitation.txt:16
#, python-format
msgid " Brought to you by %(brandname)s "
msgstr " Presentado por %(brandname)s "

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-13 11:41+0000\n"
"PO-Revision-Date: 2025-03-17 13:58\n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 09:05\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -24,7 +24,7 @@ msgstr "Infos Personnelles"
#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50
#: core/admin.py:138
msgid "Permissions"
msgstr ""
msgstr "Permissions"
#: build/lib/core/admin.py:62 core/admin.py:62
msgid "Important dates"
@@ -32,345 +32,340 @@ msgstr "Dates importantes"
#: build/lib/core/admin.py:148 core/admin.py:148
msgid "Tree structure"
msgstr ""
msgstr "Arborescence"
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
msgid "Title"
msgstr ""
msgstr "Titre"
#: build/lib/core/api/filters.py:30 core/api/filters.py:30
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
msgid "Creator is me"
msgstr ""
msgstr "Je suis l'auteur"
#: build/lib/core/api/filters.py:33 core/api/filters.py:33
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
msgid "Favorite"
msgstr ""
msgstr "Favoris"
#: build/lib/core/api/serializers.py:354 core/api/serializers.py:354
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr "Un nouveau document a été créé pour vous !"
#: build/lib/core/api/serializers.py:358 core/api/serializers.py:358
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr "Vous avez été déclaré propriétaire d'un nouveau document :"
#: build/lib/core/api/serializers.py:473 core/api/serializers.py:473
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr ""
msgstr "Corps"
#: build/lib/core/api/serializers.py:476 core/api/serializers.py:476
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr ""
msgstr "Type de corps"
#: build/lib/core/api/serializers.py:482 core/api/serializers.py:482
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr ""
msgstr "Format"
#: build/lib/core/authentication/backends.py:61
#: core/authentication/backends.py:61
msgid "Invalid response format or token verification failed"
msgstr ""
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#, python-brace-format
msgid "copy of {title}"
msgstr "copie de {title}"
#: build/lib/core/authentication/backends.py:108
#: core/authentication/backends.py:108
msgid "User account is disabled"
msgstr ""
#: build/lib/core/enums.py:19 core/enums.py:19
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr ""
msgstr "Premier enfant"
#: build/lib/core/enums.py:20 core/enums.py:20
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr ""
msgstr "Dernier enfant"
#: build/lib/core/enums.py:21 core/enums.py:21
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr ""
msgstr "Premier frère ou sœur"
#: build/lib/core/enums.py:22 core/enums.py:22
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr ""
msgstr "Dernière relation"
#: build/lib/core/enums.py:23 core/enums.py:23
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr ""
msgstr "Gauche"
#: build/lib/core/enums.py:24 core/enums.py:24
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr ""
#: build/lib/core/models.py:55 build/lib/core/models.py:62 core/models.py:55
#: core/models.py:62
msgid "Reader"
msgstr "Lecteur"
msgstr "Droite"
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr "Lecteur"
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr "Éditeur"
#: build/lib/core/models.py:64 core/models.py:64
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr "Administrateur"
#: build/lib/core/models.py:65 core/models.py:65
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr "Propriétaire"
#: build/lib/core/models.py:76 core/models.py:76
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr "Restreint"
#: build/lib/core/models.py:80 core/models.py:80
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr "Authentifié"
#: build/lib/core/models.py:82 core/models.py:82
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr ""
#: build/lib/core/models.py:153 core/models.py:153
msgid "id"
msgstr ""
msgstr "Public"
#: build/lib/core/models.py:154 core/models.py:154
msgid "primary key for the record as UUID"
msgstr ""
msgid "id"
msgstr "identifiant/id"
#: build/lib/core/models.py:160 core/models.py:160
msgid "created on"
msgstr ""
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr "clé primaire pour l'enregistrement en tant que UUID"
#: build/lib/core/models.py:161 core/models.py:161
msgid "date and time at which a record was created"
msgstr ""
msgid "created on"
msgstr "créé le"
#: build/lib/core/models.py:166 core/models.py:166
msgid "updated on"
msgstr ""
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr "date et heure de création de l'enregistrement"
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr "mis à jour le"
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr ""
msgstr "date et heure de la dernière mise à jour de l'enregistrement"
#: build/lib/core/models.py:203 core/models.py:203
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr ""
msgstr "Nous n'avons pas pu trouver un utilisateur avec ce sous-groupe mais l'e-mail est déjà associé à un utilisateur enregistré."
#: build/lib/core/models.py:216 core/models.py:216
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr ""
msgstr "Saisissez un sous-groupe valide. Cette valeur ne peut contenir que des lettres, des chiffres et les caractères @/./+/-/_/: uniquement."
#: build/lib/core/models.py:222 core/models.py:222
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr ""
msgstr "sous-groupe"
#: build/lib/core/models.py:224 core/models.py:224
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr ""
#: build/lib/core/models.py:233 core/models.py:233
msgid "full name"
msgstr ""
msgstr "Obligatoire. 255 caractères ou moins. Lettres, chiffres et caractères @/./+/-/_/: uniquement."
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr "nom complet"
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr ""
msgstr "nom court"
#: build/lib/core/models.py:236 core/models.py:236
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr ""
msgstr "adresse e-mail d'identité"
#: build/lib/core/models.py:241 core/models.py:241
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr ""
#: build/lib/core/models.py:248 core/models.py:248
msgid "language"
msgstr ""
msgstr "adresse e-mail de l'administrateur"
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr "langue"
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr ""
msgstr "La langue dans laquelle l'utilisateur veut voir l'interface."
#: build/lib/core/models.py:257 core/models.py:257
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr ""
msgstr "Le fuseau horaire dans lequel l'utilisateur souhaite voir les heures."
#: build/lib/core/models.py:260 core/models.py:260
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr ""
msgstr "appareil"
#: build/lib/core/models.py:262 core/models.py:262
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr ""
msgstr "Si l'utilisateur est un appareil ou un utilisateur réel."
#: build/lib/core/models.py:265 core/models.py:265
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr ""
msgstr "statut d'équipe"
#: build/lib/core/models.py:267 core/models.py:267
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr ""
msgstr "Si l'utilisateur peut se connecter à ce site d'administration."
#: build/lib/core/models.py:270 core/models.py:270
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr ""
msgstr "actif"
#: build/lib/core/models.py:273 core/models.py:273
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr ""
#: build/lib/core/models.py:285 core/models.py:285
msgid "user"
msgstr ""
msgstr "Si cet utilisateur doit être traité comme actif. Désélectionnez ceci au lieu de supprimer des comptes."
#: build/lib/core/models.py:286 core/models.py:286
msgid "users"
msgstr ""
msgid "user"
msgstr "utilisateur"
#: build/lib/core/models.py:470 build/lib/core/models.py:1074
#: core/models.py:470 core/models.py:1074
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr "utilisateurs"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr ""
msgstr "titre"
#: build/lib/core/models.py:471 core/models.py:471
msgid "excerpt"
msgstr ""
msgstr "extrait"
#: build/lib/core/models.py:504 core/models.py:504
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr ""
msgstr "Document"
#: build/lib/core/models.py:505 core/models.py:505
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr ""
msgstr "Documents"
#: build/lib/core/models.py:517 build/lib/core/models.py:826 core/models.py:517
#: core/models.py:826
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr "Document sans titre"
#: build/lib/core/models.py:861 core/models.py:861
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} a partagé un document avec vous!"
#: build/lib/core/models.py:865 core/models.py:865
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} vous a invité avec le rôle \"{role}\" sur le document suivant:"
msgstr "{name} vous a invité avec le rôle \"{role}\" sur le document suivant :"
#: build/lib/core/models.py:871 core/models.py:871
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} a partagé un document avec vous: {title}"
msgstr "{name} a partagé un document avec vous : {title}"
#: build/lib/core/models.py:969 core/models.py:969
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr ""
msgstr "Trace du lien document/utilisateur"
#: build/lib/core/models.py:970 core/models.py:970
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr ""
msgstr "Traces du lien document/utilisateur"
#: build/lib/core/models.py:976 core/models.py:976
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr ""
msgstr "Une trace de lien existe déjà pour ce document/utilisateur."
#: build/lib/core/models.py:999 core/models.py:999
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr ""
msgstr "Document favori"
#: build/lib/core/models.py:1000 core/models.py:1000
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr ""
msgstr "Documents favoris"
#: build/lib/core/models.py:1006 core/models.py:1006
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
msgstr "Ce document est déjà un favori de cet utilisateur."
#: build/lib/core/models.py:1028 core/models.py:1028
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1041 core/models.py:1041
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1047 build/lib/core/models.py:1161
#: core/models.py:1047 core/models.py:1161
msgid "Either user or team must be set, not both."
msgstr ""
msgstr "Relation document/utilisateur"
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "description"
msgstr ""
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "code"
msgstr ""
#: build/lib/core/models.py:1077 core/models.py:1077
msgid "css"
msgstr ""
#: build/lib/core/models.py:1079 core/models.py:1079
msgid "public"
msgstr ""
msgid "Document/user relations"
msgstr "Relations document/utilisateur"
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "Whether this template is public for anyone to use."
msgstr ""
msgid "This user is already in this document."
msgstr "Cet utilisateur est déjà dans ce document."
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "Template"
msgstr ""
msgid "This team is already in this document."
msgstr "Cette équipe est déjà dans ce document."
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1142 core/models.py:1142
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1143 core/models.py:1143
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1149 core/models.py:1149
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr "L'utilisateur ou l'équipe doivent être définis, pas les deux."
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr "description"
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr "code"
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr "CSS"
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr "public"
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr "Si ce modèle est public, utilisable par n'importe qui."
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr "Modèle"
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr "Modèles"
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr "Relation modèle/utilisateur"
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr "Relations modèle/utilisateur"
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr "Cet utilisateur est déjà dans ce modèle."
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr ""
msgstr "Cette équipe est déjà modèle."
#: build/lib/core/models.py:1178 core/models.py:1178
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr ""
msgstr "adresse e-mail"
#: build/lib/core/models.py:1197 core/models.py:1197
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr ""
msgstr "Invitation à un document"
#: build/lib/core/models.py:1198 core/models.py:1198
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr ""
msgstr "Invitations à un document"
#: build/lib/core/models.py:1218 core/models.py:1218
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr ""
msgstr "Cette adresse email est déjà associée à un utilisateur inscrit."
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3

View File

@@ -0,0 +1,390 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Language: it\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
#: build/lib/core/admin.py:37 core/admin.py:37
msgid "Personal info"
msgstr "Informazioni personali"
#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50
#: core/admin.py:138
msgid "Permissions"
msgstr "Permessi"
#: build/lib/core/admin.py:62 core/admin.py:62
msgid "Important dates"
msgstr "Date importanti"
#: build/lib/core/admin.py:148 core/admin.py:148
msgid "Tree structure"
msgstr "Struttura ad albero"
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
msgid "Title"
msgstr "Titolo"
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
msgid "Creator is me"
msgstr "Il creatore sono io"
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
msgid "Favorite"
msgstr "Preferiti"
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr "Un nuovo documento è stato creato a tuo nome!"
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr "Sei ora proprietario di un nuovo documento:"
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr "Corpo"
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr ""
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr "Formato"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#, python-brace-format
msgid "copy of {title}"
msgstr "copia di {title}"
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr ""
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr ""
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr ""
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr ""
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr "Sinistra"
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr "Destra"
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr "Lettore"
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr "Editor"
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr "Amministratore"
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr "Proprietario"
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr "Limitato"
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr "Autenticato"
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr "Pubblico"
#: build/lib/core/models.py:154 core/models.py:154
msgid "id"
msgstr "Id"
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr "chiave primaria per il record come UUID"
#: build/lib/core/models.py:161 core/models.py:161
msgid "created on"
msgstr "creato il"
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr "data e ora in cui è stato creato un record"
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr "aggiornato il"
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr "data e ora in cui lultimo record è stato aggiornato"
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr ""
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr ""
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr ""
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr "Richiesto. 255 caratteri o meno. Solo lettere, numeri e @/./+/-/_/: caratteri."
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr "nome completo"
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr "nome"
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr "indirizzo email di identità"
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr "Indirizzo email dell'amministratore"
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr "lingua"
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr "La lingua in cui l'utente vuole vedere l'interfaccia."
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr "Il fuso orario in cui l'utente vuole vedere gli orari."
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr "dispositivo"
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr "Se l'utente è un dispositivo o un utente reale."
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr "stato del personale"
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr "Indica se l'utente può accedere a questo sito amministratore."
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr "attivo"
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr "Indica se questo utente deve essere trattato come attivo. Deseleziona invece di eliminare gli account."
#: build/lib/core/models.py:286 core/models.py:286
msgid "user"
msgstr "utente"
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr "utenti"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr "titolo"
#: build/lib/core/models.py:471 core/models.py:471
msgid "excerpt"
msgstr ""
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr "Documento"
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr "Documenti"
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr "Documento senza titolo"
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} ha condiviso un documento con te!"
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} ti ha invitato con il ruolo \"{role}\" nel seguente documento:"
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} ha condiviso un documento con te: {title}"
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr "Documento preferito"
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr "Documenti preferiti"
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "This user is already in this document."
msgstr "Questo utente è già presente in questo documento."
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "This team is already in this document."
msgstr "Questo team è già presente in questo documento."
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr "descrizione"
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr "code"
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr "pubblico"
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr "Indica se questo modello è pubblico per chiunque."
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr "Modello"
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr "Modelli"
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr "Questo utente è già in questo modello."
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr "Questo team è già in questo modello."
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr "indirizzo e-mail"
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr "Invito al documento"
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr "Inviti al documento"
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr "Questa email è già associata a un utente registrato."
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3
msgid "Logo email"
msgstr "Logo e-mail"
#: core/templates/mail/html/invitation.html:209
#: core/templates/mail/text/invitation.txt:10
msgid "Open"
msgstr "Apri"
#: core/templates/mail/html/invitation.html:226
#: core/templates/mail/text/invitation.txt:14
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
msgstr ""
#: core/templates/mail/html/invitation.html:233
#: core/templates/mail/text/invitation.txt:16
#, python-format
msgid " Brought to you by %(brandname)s "
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-13 11:41+0000\n"
"PO-Revision-Date: 2025-03-17 13:58\n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Language: nl_NL\n"
@@ -34,204 +34,199 @@ msgstr "Belangrijke datums"
msgid "Tree structure"
msgstr "Document structuur"
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
msgid "Title"
msgstr "Titel"
#: build/lib/core/api/filters.py:30 core/api/filters.py:30
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
msgid "Creator is me"
msgstr "Ik ben Eigenaar"
#: build/lib/core/api/filters.py:33 core/api/filters.py:33
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
msgid "Favorite"
msgstr "Favoriete"
#: build/lib/core/api/serializers.py:354 core/api/serializers.py:354
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr "Een nieuw document was gecreëerd voor u!"
#: build/lib/core/api/serializers.py:358 core/api/serializers.py:358
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr "U heeft eigenaarschap van een nieuw document:"
#: build/lib/core/api/serializers.py:473 core/api/serializers.py:473
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr "Text"
#: build/lib/core/api/serializers.py:476 core/api/serializers.py:476
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr "Text type"
#: build/lib/core/api/serializers.py:482 core/api/serializers.py:482
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr "Formaat"
#: build/lib/core/authentication/backends.py:61
#: core/authentication/backends.py:61
msgid "Invalid response format or token verification failed"
msgstr "Invalide response formaat of token verificatie gefaald"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#, python-brace-format
msgid "copy of {title}"
msgstr "kopie van {title}"
#: build/lib/core/authentication/backends.py:108
#: core/authentication/backends.py:108
msgid "User account is disabled"
msgstr "Gebruikersaccount is buiten gebruik gesteld"
#: build/lib/core/enums.py:19 core/enums.py:19
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr "Eerste node"
#: build/lib/core/enums.py:20 core/enums.py:20
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr "Laatste node"
#: build/lib/core/enums.py:21 core/enums.py:21
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr "Eerste naaste"
#: build/lib/core/enums.py:22 core/enums.py:22
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr "Laatste naaste"
#: build/lib/core/enums.py:23 core/enums.py:23
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr "Links"
#: build/lib/core/enums.py:24 core/enums.py:24
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr "Rechts"
#: build/lib/core/models.py:55 build/lib/core/models.py:62 core/models.py:55
#: core/models.py:62
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr "Lezer"
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr "Bewerker"
#: build/lib/core/models.py:64 core/models.py:64
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr "Administrator"
#: build/lib/core/models.py:65 core/models.py:65
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr "Eigenaar"
#: build/lib/core/models.py:76 core/models.py:76
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr "Niet toegestaan"
#: build/lib/core/models.py:80 core/models.py:80
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr "Geauthenticeerd"
#: build/lib/core/models.py:82 core/models.py:82
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr "Publiek"
#: build/lib/core/models.py:153 core/models.py:153
#: build/lib/core/models.py:154 core/models.py:154
msgid "id"
msgstr "id"
#: build/lib/core/models.py:154 core/models.py:154
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr "primaire sleutel voor dossier als UUID"
#: build/lib/core/models.py:160 core/models.py:160
#: build/lib/core/models.py:161 core/models.py:161
msgid "created on"
msgstr "gemaakt op"
#: build/lib/core/models.py:161 core/models.py:161
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr "datum en tijd wanneer dossier was gecreëerd"
#: build/lib/core/models.py:166 core/models.py:166
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr "Laatst gewijzigd op"
#: build/lib/core/models.py:167 core/models.py:167
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr "datum en tijd waarop dossier laatst was gewijzigd"
#: build/lib/core/models.py:203 core/models.py:203
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr "Wij konden geen gebruiker vinden met deze id, maar de email is al geassocieerd met een geregistreerde gebruiker."
#: build/lib/core/models.py:216 core/models.py:216
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr ".Geef een valide id. De waarde mag alleen letters, nummers en @/./.+/-/_: karakters bevatten."
#: build/lib/core/models.py:222 core/models.py:222
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr "id"
#: build/lib/core/models.py:224 core/models.py:224
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr "Verplicht. 255 karakters of minder. Alleen letters, nummers en @/./+/-/_/: karakters zijn toegestaan."
#: build/lib/core/models.py:233 core/models.py:233
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr "volledige naam"
#: build/lib/core/models.py:234 core/models.py:234
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr "gebruikersnaam"
#: build/lib/core/models.py:236 core/models.py:236
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr "identiteit email adres"
#: build/lib/core/models.py:241 core/models.py:241
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr "admin email adres"
#: build/lib/core/models.py:248 core/models.py:248
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr "taal"
#: build/lib/core/models.py:249 core/models.py:249
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr "De taal waarin de gebruiker de interface wilt zien."
#: build/lib/core/models.py:257 core/models.py:257
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr "De tijdzone waarin de gebruiker de tijden wilt zien."
#: build/lib/core/models.py:260 core/models.py:260
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr "apparaat"
#: build/lib/core/models.py:262 core/models.py:262
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr "Of de gebruiker een apparaat is of een echte gebruiker."
#: build/lib/core/models.py:265 core/models.py:265
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr "beheerder status"
#: build/lib/core/models.py:267 core/models.py:267
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr "Of de gebruiker kan inloggen in het admin gedeelte."
#: build/lib/core/models.py:270 core/models.py:270
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr "actief"
#: build/lib/core/models.py:273 core/models.py:273
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr "Of een gebruiker als actief moet worden beschouwd. Deselecteer dit in plaats van het account te deleten."
#: build/lib/core/models.py:285 core/models.py:285
#: build/lib/core/models.py:286 core/models.py:286
msgid "user"
msgstr "gebruiker"
#: build/lib/core/models.py:286 core/models.py:286
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr "gebruikers"
#: build/lib/core/models.py:470 build/lib/core/models.py:1074
#: core/models.py:470 core/models.py:1074
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr "titel"
@@ -239,136 +234,136 @@ msgstr "titel"
msgid "excerpt"
msgstr "uittreksel"
#: build/lib/core/models.py:504 core/models.py:504
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr "Document"
#: build/lib/core/models.py:505 core/models.py:505
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr "Documenten"
#: build/lib/core/models.py:517 build/lib/core/models.py:826 core/models.py:517
#: core/models.py:826
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr "Naamloos Document"
#: build/lib/core/models.py:861 core/models.py:861
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} heeft een document met gedeeld!"
#: build/lib/core/models.py:865 core/models.py:865
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} heeft u uitgenodigd met de rol \"{role}\" op het volgende document:"
#: build/lib/core/models.py:871 core/models.py:871
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} heeft een document met u gedeeld: {title}"
#: build/lib/core/models.py:969 core/models.py:969
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr "Document/gebruiker url"
#: build/lib/core/models.py:970 core/models.py:970
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr "Document/gebruiker url"
#: build/lib/core/models.py:976 core/models.py:976
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr "Een url bestaat al voor dit document/deze gebruiker."
#: build/lib/core/models.py:999 core/models.py:999
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr "Document favoriet"
#: build/lib/core/models.py:1000 core/models.py:1000
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr "Document favorieten"
#: build/lib/core/models.py:1006 core/models.py:1006
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "Dit document is al in gebruik als favoriete door dezelfde gebruiker."
#: build/lib/core/models.py:1028 core/models.py:1028
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr "Document/gebruiker relatie"
#: build/lib/core/models.py:1029 core/models.py:1029
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relations"
msgstr "Document/gebruiker relaties"
#: build/lib/core/models.py:1035 core/models.py:1035
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "This user is already in this document."
msgstr "De gebruiker is al in dit document."
#: build/lib/core/models.py:1041 core/models.py:1041
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "This team is already in this document."
msgstr "Het team is al in dit document."
#: build/lib/core/models.py:1047 build/lib/core/models.py:1161
#: core/models.py:1047 core/models.py:1161
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr "Een gebruiker of team moet gekozen worden, maar niet beide."
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr "omschrijving"
#: build/lib/core/models.py:1076 core/models.py:1076
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr "code"
#: build/lib/core/models.py:1077 core/models.py:1077
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1079 core/models.py:1079
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr "publiek"
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr "Of dit template als publiek is en door iedereen te gebruiken is."
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr "Template"
#: build/lib/core/models.py:1088 core/models.py:1088
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr "Templates"
#: build/lib/core/models.py:1142 core/models.py:1142
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr "Template/gebruiker relatie"
#: build/lib/core/models.py:1143 core/models.py:1143
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr "Template/gebruiker relaties"
#: build/lib/core/models.py:1149 core/models.py:1149
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr "De gebruiker bestaat al in dit template."
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr "Het team bestaat al in dit template."
#: build/lib/core/models.py:1178 core/models.py:1178
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr "email adres"
#: build/lib/core/models.py:1197 core/models.py:1197
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr "Document uitnodiging"
#: build/lib/core/models.py:1198 core/models.py:1198
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr "Document uitnodigingen"
#: build/lib/core/models.py:1218 core/models.py:1218
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr "Deze email is al geassocieerd met een geregistreerde gebruiker."

View File

@@ -0,0 +1,390 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Language: pt_PT\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Language: pt-PT\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
#: build/lib/core/admin.py:37 core/admin.py:37
msgid "Personal info"
msgstr ""
#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50
#: core/admin.py:138
msgid "Permissions"
msgstr ""
#: build/lib/core/admin.py:62 core/admin.py:62
msgid "Important dates"
msgstr ""
#: build/lib/core/admin.py:148 core/admin.py:148
msgid "Tree structure"
msgstr ""
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
msgid "Title"
msgstr ""
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
msgid "Creator is me"
msgstr ""
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
msgid "Favorite"
msgstr ""
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr ""
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr ""
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr ""
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr ""
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr ""
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#, python-brace-format
msgid "copy of {title}"
msgstr ""
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr ""
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr ""
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr ""
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr ""
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr ""
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr ""
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr ""
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr ""
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr ""
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr ""
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr ""
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr ""
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr ""
#: build/lib/core/models.py:154 core/models.py:154
msgid "id"
msgstr ""
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr ""
#: build/lib/core/models.py:161 core/models.py:161
msgid "created on"
msgstr ""
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr ""
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr ""
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr ""
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr ""
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr ""
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr ""
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr ""
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr ""
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr ""
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr ""
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr ""
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr ""
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr ""
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr ""
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr ""
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr ""
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr ""
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr ""
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr ""
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr ""
#: build/lib/core/models.py:286 core/models.py:286
msgid "user"
msgstr ""
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr ""
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr ""
#: build/lib/core/models.py:471 core/models.py:471
msgid "excerpt"
msgstr ""
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr ""
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr ""
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr ""
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr ""
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr ""
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr ""
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr ""
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr ""
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3
msgid "Logo email"
msgstr ""
#: core/templates/mail/html/invitation.html:209
#: core/templates/mail/text/invitation.txt:10
msgid "Open"
msgstr ""
#: core/templates/mail/html/invitation.html:226
#: core/templates/mail/text/invitation.txt:14
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
msgstr ""
#: core/templates/mail/html/invitation.html:233
#: core/templates/mail/text/invitation.txt:16
#, python-format
msgid " Brought to you by %(brandname)s "
msgstr ""

View File

@@ -0,0 +1,390 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"Last-Translator: \n"
"Language-Team: Slovenian\n"
"Language: sl_SI\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Language: sl\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
#: build/lib/core/admin.py:37 core/admin.py:37
msgid "Personal info"
msgstr "Osebni podatki"
#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50
#: core/admin.py:138
msgid "Permissions"
msgstr "Dovoljenja"
#: build/lib/core/admin.py:62 core/admin.py:62
msgid "Important dates"
msgstr "Pomembni datumi"
#: build/lib/core/admin.py:148 core/admin.py:148
msgid "Tree structure"
msgstr "Drevesna struktura"
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
msgid "Title"
msgstr "Naslov"
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
msgid "Creator is me"
msgstr "Ustvaril sem jaz"
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
msgid "Favorite"
msgstr "Priljubljena"
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr "Nov dokument je bil ustvarjen v vašem imenu!"
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr "Dodeljeno vam je bilo lastništvo nad novim dokumentom:"
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr "Telo"
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr "Vrsta telesa"
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr "Oblika"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#, python-brace-format
msgid "copy of {title}"
msgstr ""
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr "Prvi otrok"
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr "Zadnji otrok"
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr "Prvi brat in sestra"
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr "Zadnji brat in sestra"
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr "Levo"
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr "Desno"
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr "Bralec"
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr "Urednik"
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr "Skrbnik"
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr "Lastnik"
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr "Omejeno"
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr "Preverjeno"
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr "Javno"
#: build/lib/core/models.py:154 core/models.py:154
msgid "id"
msgstr ""
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr "primarni ključ za zapis kot UUID"
#: build/lib/core/models.py:161 core/models.py:161
msgid "created on"
msgstr "ustvarjen na"
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr "datum in čas, ko je bil zapis ustvarjen"
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr "posodobljeno dne"
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr "datum in čas, ko je bil zapis nazadnje posodobljen"
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr "Nismo mogli najti uporabnika s tem sub, vendar je e-poštni naslov že povezan z registriranim uporabnikom."
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr "Vnesite veljavno sub. Ta vrednost lahko vsebuje samo črke, številke in znake @/./+/-/_/:."
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr ""
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr "Obvezno. 255 znakov ali manj. Samo črke, številke in znaki @/./+/-/_/: ."
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr "polno ime"
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr "kratko ime"
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr "elektronski naslov identitete"
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr "elektronski naslov skrbnika"
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr "jezik"
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr "Jezik, v katerem uporabnik želi videti vmesnik."
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr "Časovni pas, v katerem želi uporabnik videti uro."
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr "naprava"
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr "Ali je uporabnik naprava ali pravi uporabnik."
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr "kadrovski status"
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr "Ali se uporabnik lahko prijavi na to skrbniško mesto."
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr "aktivni"
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr "Ali je treba tega uporabnika obravnavati kot aktivnega. Namesto brisanja računov počistite to izbiro."
#: build/lib/core/models.py:286 core/models.py:286
msgid "user"
msgstr "uporabnik"
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr "uporabniki"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr "naslov"
#: build/lib/core/models.py:471 core/models.py:471
msgid "excerpt"
msgstr "odlomek"
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr "Dokument"
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr "Dokumenti"
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr "Dokument brez naslova"
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} je delil dokument z vami!"
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} vas je povabil z vlogo \"{role}\" na naslednjem dokumentu:"
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} je delil dokument z vami: {title}"
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr "Dokument/sled povezave uporabnika"
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr "Sledi povezav dokumenta/uporabnika"
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr "Za ta dokument/uporabnika že obstaja sled povezave."
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr "Priljubljeni dokument"
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr "Priljubljeni dokumenti"
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "Ta dokument je že ciljno usmerjen s priljubljenim primerkom relacije za istega uporabnika."
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr "Odnos dokument/uporabnik"
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relations"
msgstr "Odnosi dokument/uporabnik"
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "This user is already in this document."
msgstr "Ta uporabnik je že v tem dokumentu."
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "This team is already in this document."
msgstr "Ta ekipa je že v tem dokumentu."
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr "Nastaviti je treba bodisi uporabnika ali ekipo, a ne obojega."
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr "opis"
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr "koda"
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr "javno"
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr "Ali je ta predloga javna za uporabo."
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr "Predloga"
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr "Predloge"
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr "Odnos predloga/uporabnik"
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr "Odnosi med predlogo in uporabnikom"
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr "Ta uporabnik je že v tej predlogi."
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr "Ta ekipa je že v tej predlogi."
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr "elektronski naslov"
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr "Vabilo na dokument"
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr "Vabila na dokument"
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr "Ta e-poštni naslov je že povezan z registriranim uporabnikom."
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3
msgid "Logo email"
msgstr "E-pošta z logotipom"
#: core/templates/mail/html/invitation.html:209
#: core/templates/mail/text/invitation.txt:10
msgid "Open"
msgstr "Odpri"
#: core/templates/mail/html/invitation.html:226
#: core/templates/mail/text/invitation.txt:14
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
msgstr " Dokumenti, vaše novo bistveno orodje za organiziranje, skupno rabo in skupinsko sodelovanje pri dokumentih. "
#: core/templates/mail/html/invitation.html:233
#: core/templates/mail/text/invitation.txt:16
#, python-format
msgid " Brought to you by %(brandname)s "
msgstr " Pod okriljem %(brandname)s "

View File

@@ -0,0 +1,390 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"Last-Translator: \n"
"Language-Team: Swedish\n"
"Language: sv_SE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Language: sv-SE\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
#: build/lib/core/admin.py:37 core/admin.py:37
msgid "Personal info"
msgstr "Personuppgifter"
#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50
#: core/admin.py:138
msgid "Permissions"
msgstr "Behörigheter"
#: build/lib/core/admin.py:62 core/admin.py:62
msgid "Important dates"
msgstr "Viktiga datum"
#: build/lib/core/admin.py:148 core/admin.py:148
msgid "Tree structure"
msgstr ""
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
msgid "Title"
msgstr "Titel"
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
msgid "Creator is me"
msgstr "Skaparen är jag"
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
msgid "Favorite"
msgstr "Favoriter"
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr "Ett nytt dokument skapades åt dig!"
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr "Du har beviljats äganderätt till ett nytt dokument:"
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr ""
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr ""
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr "Format"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#, python-brace-format
msgid "copy of {title}"
msgstr ""
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr ""
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr ""
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr ""
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr ""
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr ""
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr ""
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr ""
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr ""
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr "Administratör"
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr ""
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr ""
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr ""
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr "Publik"
#: build/lib/core/models.py:154 core/models.py:154
msgid "id"
msgstr ""
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr ""
#: build/lib/core/models.py:161 core/models.py:161
msgid "created on"
msgstr ""
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr ""
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr ""
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr ""
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr ""
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr ""
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr ""
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr ""
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr ""
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr ""
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr ""
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr ""
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr ""
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr ""
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr ""
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr ""
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr ""
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr ""
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr ""
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr "aktiv"
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr ""
#: build/lib/core/models.py:286 core/models.py:286
msgid "user"
msgstr ""
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr ""
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr ""
#: build/lib/core/models.py:471 core/models.py:471
msgid "excerpt"
msgstr ""
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr ""
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr ""
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr ""
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr ""
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr ""
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr ""
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr "e-postadress"
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr "Bjud in dokument"
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr "Inbjudningar dokument"
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr "Denna e-postadress är redan associerad med en registrerad användare."
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3
msgid "Logo email"
msgstr "Logotyp e-post"
#: core/templates/mail/html/invitation.html:209
#: core/templates/mail/text/invitation.txt:10
msgid "Open"
msgstr "Öppna"
#: core/templates/mail/html/invitation.html:226
#: core/templates/mail/text/invitation.txt:14
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
msgstr ""
#: core/templates/mail/html/invitation.html:233
#: core/templates/mail/text/invitation.txt:16
#, python-format
msgid " Brought to you by %(brandname)s "
msgstr ""

View File

@@ -0,0 +1,390 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"Language: tr_TR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Language: tr\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
#: build/lib/core/admin.py:37 core/admin.py:37
msgid "Personal info"
msgstr ""
#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50
#: core/admin.py:138
msgid "Permissions"
msgstr ""
#: build/lib/core/admin.py:62 core/admin.py:62
msgid "Important dates"
msgstr ""
#: build/lib/core/admin.py:148 core/admin.py:148
msgid "Tree structure"
msgstr ""
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
msgid "Title"
msgstr ""
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
msgid "Creator is me"
msgstr ""
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
msgid "Favorite"
msgstr ""
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr ""
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr ""
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr ""
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr ""
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr ""
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#, python-brace-format
msgid "copy of {title}"
msgstr ""
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr ""
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr ""
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr ""
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr ""
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr ""
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr ""
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr ""
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr ""
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr ""
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr ""
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr ""
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr ""
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr ""
#: build/lib/core/models.py:154 core/models.py:154
msgid "id"
msgstr ""
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr ""
#: build/lib/core/models.py:161 core/models.py:161
msgid "created on"
msgstr ""
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr ""
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr ""
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr ""
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr ""
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr ""
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr ""
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr ""
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr ""
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr ""
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr ""
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr ""
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr ""
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr ""
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr ""
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr ""
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr ""
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr ""
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr ""
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr ""
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr ""
#: build/lib/core/models.py:286 core/models.py:286
msgid "user"
msgstr ""
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr ""
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr ""
#: build/lib/core/models.py:471 core/models.py:471
msgid "excerpt"
msgstr ""
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr ""
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr ""
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr ""
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr ""
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr ""
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr ""
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr ""
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr ""
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3
msgid "Logo email"
msgstr ""
#: core/templates/mail/html/invitation.html:209
#: core/templates/mail/text/invitation.txt:10
msgid "Open"
msgstr ""
#: core/templates/mail/html/invitation.html:226
#: core/templates/mail/text/invitation.txt:14
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
msgstr ""
#: core/templates/mail/html/invitation.html:233
#: core/templates/mail/text/invitation.txt:16
#, python-format
msgid " Brought to you by %(brandname)s "
msgstr ""

View File

@@ -0,0 +1,390 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Language: zh-CN\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
#: build/lib/core/admin.py:37 core/admin.py:37
msgid "Personal info"
msgstr "个人信息"
#: build/lib/core/admin.py:50 build/lib/core/admin.py:138 core/admin.py:50
#: core/admin.py:138
msgid "Permissions"
msgstr "权限"
#: build/lib/core/admin.py:62 core/admin.py:62
msgid "Important dates"
msgstr "重要日期"
#: build/lib/core/admin.py:148 core/admin.py:148
msgid "Tree structure"
msgstr "树状结构"
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
msgid "Title"
msgstr "标题"
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
msgid "Creator is me"
msgstr "创建者是我"
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
msgid "Favorite"
msgstr "收藏"
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
msgid "A new document was created on your behalf!"
msgstr "已为您创建了一份新文档!"
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
msgid "You have been granted ownership of a new document:"
msgstr "您已被授予新文档的所有权:"
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
msgid "Body"
msgstr "正文"
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
msgid "Body type"
msgstr "正文类型"
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
msgid "Format"
msgstr "格式"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#, python-brace-format
msgid "copy of {title}"
msgstr "{title} 的副本"
#: build/lib/core/enums.py:35 core/enums.py:35
msgid "First child"
msgstr "第一个子项"
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "Last child"
msgstr "最后一个子项"
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "First sibling"
msgstr "第一个同级项"
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "Last sibling"
msgstr "最后一个同级项"
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Left"
msgstr "左"
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Right"
msgstr "右"
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
#: core/models.py:63
msgid "Reader"
msgstr "阅读者"
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
#: core/models.py:64
msgid "Editor"
msgstr "编辑者"
#: build/lib/core/models.py:65 core/models.py:65
msgid "Administrator"
msgstr "超级管理员"
#: build/lib/core/models.py:66 core/models.py:66
msgid "Owner"
msgstr "所有者"
#: build/lib/core/models.py:77 core/models.py:77
msgid "Restricted"
msgstr "受限的"
#: build/lib/core/models.py:81 core/models.py:81
msgid "Authenticated"
msgstr "已验证"
#: build/lib/core/models.py:83 core/models.py:83
msgid "Public"
msgstr "公开"
#: build/lib/core/models.py:154 core/models.py:154
msgid "id"
msgstr "id"
#: build/lib/core/models.py:155 core/models.py:155
msgid "primary key for the record as UUID"
msgstr "记录的主密钥为 UUID"
#: build/lib/core/models.py:161 core/models.py:161
msgid "created on"
msgstr "创建时间"
#: build/lib/core/models.py:162 core/models.py:162
msgid "date and time at which a record was created"
msgstr "记录的创建日期和时间"
#: build/lib/core/models.py:167 core/models.py:167
msgid "updated on"
msgstr "更新时间"
#: build/lib/core/models.py:168 core/models.py:168
msgid "date and time at which a record was last updated"
msgstr "记录的最后更新时间"
#: build/lib/core/models.py:204 core/models.py:204
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr "未找到具有该 sub 的用户,但该邮箱已关联到一个注册用户。"
#: build/lib/core/models.py:217 core/models.py:217
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr "请输入有效的 sub。该值只能包含字母、数字及 @/./+/-/_/: 字符。"
#: build/lib/core/models.py:223 core/models.py:223
msgid "sub"
msgstr "sub"
#: build/lib/core/models.py:225 core/models.py:225
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr "必填。最多 255 个字符,仅允许字母、数字及 @/./+/-/_/: 字符。"
#: build/lib/core/models.py:234 core/models.py:234
msgid "full name"
msgstr "全名"
#: build/lib/core/models.py:235 core/models.py:235
msgid "short name"
msgstr "简称"
#: build/lib/core/models.py:237 core/models.py:237
msgid "identity email address"
msgstr "身份电子邮件地址"
#: build/lib/core/models.py:242 core/models.py:242
msgid "admin email address"
msgstr "管理员电子邮件地址"
#: build/lib/core/models.py:249 core/models.py:249
msgid "language"
msgstr "语言"
#: build/lib/core/models.py:250 core/models.py:250
msgid "The language in which the user wants to see the interface."
msgstr "用户希望看到的界面语言。"
#: build/lib/core/models.py:258 core/models.py:258
msgid "The timezone in which the user wants to see times."
msgstr "用户查看时间希望的时区。"
#: build/lib/core/models.py:261 core/models.py:261
msgid "device"
msgstr "设备"
#: build/lib/core/models.py:263 core/models.py:263
msgid "Whether the user is a device or a real user."
msgstr "用户是设备还是真实用户。"
#: build/lib/core/models.py:266 core/models.py:266
msgid "staff status"
msgstr "员工状态"
#: build/lib/core/models.py:268 core/models.py:268
msgid "Whether the user can log into this admin site."
msgstr "用户是否可以登录该管理员站点。"
#: build/lib/core/models.py:271 core/models.py:271
msgid "active"
msgstr "激活"
#: build/lib/core/models.py:274 core/models.py:274
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr "是否应将此用户视为活跃用户。取消选择此选项而不是删除账户。"
#: build/lib/core/models.py:286 core/models.py:286
msgid "user"
msgstr "用户"
#: build/lib/core/models.py:287 core/models.py:287
msgid "users"
msgstr "个用户"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
msgid "title"
msgstr "标题"
#: build/lib/core/models.py:471 core/models.py:471
msgid "excerpt"
msgstr "摘要"
#: build/lib/core/models.py:519 core/models.py:519
msgid "Document"
msgstr "文档"
#: build/lib/core/models.py:520 core/models.py:520
msgid "Documents"
msgstr "个文档"
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
msgid "Untitled Document"
msgstr "未命名文档"
#: build/lib/core/models.py:907 core/models.py:907
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} 与您共享了一个文档!"
#: build/lib/core/models.py:911 core/models.py:911
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} 邀请您以“{role}”角色访问以下文档:"
#: build/lib/core/models.py:917 core/models.py:917
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} 与您共享了一个文档:{title}"
#: build/lib/core/models.py:1015 core/models.py:1015
msgid "Document/user link trace"
msgstr "文档/用户链接跟踪"
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link traces"
msgstr "个文档/用户链接跟踪"
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "A link trace already exists for this document/user."
msgstr "此文档/用户的链接跟踪已存在。"
#: build/lib/core/models.py:1045 core/models.py:1045
msgid "Document favorite"
msgstr "文档收藏"
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorites"
msgstr "文档收藏夹"
#: build/lib/core/models.py:1052 core/models.py:1052
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "该文档已被同一用户的收藏关系实例关联。"
#: build/lib/core/models.py:1074 core/models.py:1074
msgid "Document/user relation"
msgstr "文档/用户关系"
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relations"
msgstr "文档/用户关系集"
#: build/lib/core/models.py:1081 core/models.py:1081
msgid "This user is already in this document."
msgstr "该用户已在此文档中。"
#: build/lib/core/models.py:1087 core/models.py:1087
msgid "This team is already in this document."
msgstr "该团队已在此文档中。"
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
msgid "Either user or team must be set, not both."
msgstr "必须设置用户或团队之一,不能同时设置两者。"
#: build/lib/core/models.py:1155 core/models.py:1155
msgid "description"
msgstr "说明"
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "code"
msgstr "代码"
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1159 core/models.py:1159
msgid "public"
msgstr "公开"
#: build/lib/core/models.py:1161 core/models.py:1161
msgid "Whether this template is public for anyone to use."
msgstr "该模板是否公开供任何人使用。"
#: build/lib/core/models.py:1167 core/models.py:1167
msgid "Template"
msgstr "模板"
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Templates"
msgstr "模板"
#: build/lib/core/models.py:1222 core/models.py:1222
msgid "Template/user relation"
msgstr "模板/用户关系"
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relations"
msgstr "模板/用户关系集"
#: build/lib/core/models.py:1229 core/models.py:1229
msgid "This user is already in this template."
msgstr "该用户已在此模板中。"
#: build/lib/core/models.py:1235 core/models.py:1235
msgid "This team is already in this template."
msgstr "该团队已在此模板中。"
#: build/lib/core/models.py:1258 core/models.py:1258
msgid "email address"
msgstr "电子邮件地址"
#: build/lib/core/models.py:1277 core/models.py:1277
msgid "Document invitation"
msgstr "文档邀请"
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitations"
msgstr "文档邀请"
#: build/lib/core/models.py:1298 core/models.py:1298
msgid "This email is already associated to a registered user."
msgstr "此电子邮件已经与现有注册用户关联。"
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3
msgid "Logo email"
msgstr "徽标邮件"
#: core/templates/mail/html/invitation.html:209
#: core/templates/mail/text/invitation.txt:10
msgid "Open"
msgstr "打开"
#: core/templates/mail/html/invitation.html:226
#: core/templates/mail/text/invitation.txt:14
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
msgstr " Docs——您的全新必备工具帮助团队组织、共享和协作处理文档。 "
#: core/templates/mail/html/invitation.html:233
#: core/templates/mail/text/invitation.txt:16
#, python-format
msgid " Brought to you by %(brandname)s "
msgstr " 由 %(brandname)s 倾力打造。 "

View File

@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "impress"
version = "3.1.0"
version = "3.2.1"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -25,18 +25,18 @@ license = { file = "LICENSE" }
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"beautifulsoup4==4.13.3",
"boto3==1.37.24",
"beautifulsoup4==4.13.4",
"boto3==1.38.9",
"Brotli==1.1.0",
"celery[redis]==5.5.0",
"celery[redis]==5.5.2",
"django-configurations==2.5.1",
"django-cors-headers==4.7.0",
"django-countries==7.6.1",
"django-filter==25.1",
"django-lasuite==0.0.7",
"django-parler==2.3",
"redis==5.2.1",
"django-redis==5.4.0",
"django-storages[s3]==1.14.5",
"django-storages[s3]==1.14.6",
"django-timezone-field>=5.1",
"django==5.1.8",
"django-treebeard==4.7.1",
@@ -47,17 +47,18 @@ dependencies = [
"factory_boy==3.3.3",
"gunicorn==23.0.0",
"jsonschema==4.23.0",
"lxml==5.3.1",
"markdown==3.7",
"lxml==5.4.0",
"markdown==3.8",
"mozilla-django-oidc==4.0.1",
"nested-multipart-parser==1.5.0",
"openai==1.70.0",
"psycopg[binary]==3.2.6",
"pycrdt==0.12.10",
"openai==1.77.0",
"psycopg[binary]==3.2.7",
"pycrdt==0.12.15",
"PyJWT==2.10.1",
"python-magic==0.4.27",
"redis<6.0.0",
"requests==2.32.3",
"sentry-sdk==2.25.0",
"sentry-sdk==2.27.0",
"whitenoise==6.9.0",
]
@@ -69,22 +70,22 @@ dependencies = [
[project.optional-dependencies]
dev = [
"django-extensions==3.2.3",
"django-test-migrations==1.4.0",
"drf-spectacular-sidecar==2025.3.1",
"django-extensions==4.1",
"django-test-migrations==1.5.0",
"drf-spectacular-sidecar==2025.5.1",
"freezegun==1.5.1",
"ipdb==0.13.13",
"ipython==9.0.2",
"ipython==9.2.0",
"pyfakefs==5.8.0",
"pylint-django==2.6.1",
"pylint==3.3.6",
"pytest-cov==6.0.0",
"pytest-django==4.10.0",
"pylint==3.3.7",
"pytest-cov==6.1.1",
"pytest-django==4.11.1",
"pytest==8.3.5",
"pytest-icdiff==0.9",
"pytest-xdist==3.6.1",
"responses==0.25.7",
"ruff==0.11.2",
"ruff==0.11.8",
"types-requests==2.32.0.20250328",
]

View File

@@ -1,9 +1,9 @@
module.exports = {
root: true,
extends: ["impress/playwright"],
extends: ['impress/playwright'],
parserOptions: {
tsconfigRootDir: __dirname,
project: ["./tsconfig.json"],
project: ['./tsconfig.json'],
},
ignorePatterns: ["node_modules"],
ignorePatterns: ['node_modules'],
};

View File

@@ -15,6 +15,7 @@ export const CONFIG = {
['fr-fr', 'Français'],
['de-de', 'Deutsch'],
['nl-nl', 'Nederlands'],
['es-es', 'Español'],
],
LANGUAGE_CODE: 'en-us',
POSTHOG_KEY: {},
@@ -78,6 +79,7 @@ export const createDoc = async (
});
const input = page.getByLabel('doc title input');
await expect(input).toBeVisible();
await expect(input).toHaveText('');
await input.click();

View File

@@ -79,11 +79,14 @@ test.describe('Config', () => {
test('it checks that collaboration server is configured from config endpoint', async ({
page,
browserName,
}) => {
await page.goto('/');
void createDoc(page, 'doc-collaboration', browserName, 1);
void page
.getByRole('button', {
name: 'New doc',
})
.click();
const webSocket = await page.waitForEvent('websocket', (webSocket) => {
return webSocket.url().includes('ws://localhost:4444/collaboration/ws/');

View File

@@ -79,7 +79,7 @@ test.describe('Doc Export', () => {
})
.click();
await page
void page
.getByRole('button', {
name: 'Download',
})
@@ -129,7 +129,7 @@ test.describe('Doc Export', () => {
await page.getByRole('combobox', { name: 'Format' }).click();
await page.getByRole('option', { name: 'Docx' }).click();
await page
void page
.getByRole('button', {
name: 'Download',
})
@@ -206,7 +206,7 @@ test.describe('Doc Export', () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
await page
void page
.getByRole('button', {
name: 'Download',
})
@@ -235,7 +235,7 @@ test.describe('Doc Export', () => {
// Trigger slash menu to show menu
await editor.click();
await editor.fill('/');
await page.getByText('Add a quote block').click();
await page.getByText('Quote or excerpt').click();
await expect(
editor.locator('.bn-block-content[data-content-type="quote"]'),
@@ -254,7 +254,7 @@ test.describe('Doc Export', () => {
})
.click();
await page
void page
.getByRole('button', {
name: 'Download',
})
@@ -298,7 +298,7 @@ test.describe('Doc Export', () => {
})
.click();
await page
void page
.getByRole('button', {
name: 'Download',
})

View File

@@ -152,12 +152,7 @@ test.describe('Document list members', () => {
await expect(soloOwner).toBeHidden();
await list.click();
const otherOwner = page.getByText(
`You cannot update the role or remove other owner.`,
);
await newUserRoles.click();
await expect(otherOwner).toBeVisible();
await list.click();
await currentUserRole.click();

View File

@@ -1,3 +1,5 @@
import crypto from 'crypto';
import { expect, test } from '@playwright/test';
import {
@@ -101,8 +103,9 @@ test.describe('Doc Routing: Not loggued', () => {
page,
browserName,
}) => {
await mockedDocument(page, { link_reach: 'public' });
await page.goto('/docs/mocked-document-id/');
const uuid = crypto.randomUUID();
await mockedDocument(page, { link_reach: 'public', id: uuid });
await page.goto(`/docs/${uuid}/`);
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
await page.getByRole('button', { name: 'Login' }).click();
await keyCloakSignIn(page, browserName, false);

View File

@@ -54,18 +54,11 @@ test.describe.serial('Language', () => {
}) => {
// Helper function to intercept and assert 404 response
const check404Response = async (expectedDetail: string) => {
const expectedBackendResponse = page.waitForResponse(
(response) =>
response.url().includes('/api') &&
response.url().includes('non-existent-doc-uuid') &&
response.status() === 404,
const interceptedBackendResponse = await page.request.get(
'http://localhost:8071/api/v1.0/documents/non-existent-doc-uuid/',
);
// Trigger the specific 404 XHR response by navigating to a non-existent document
await page.goto('/docs/non-existent-doc-uuid');
// Assert that the intercepted error message is in the expected language
const interceptedBackendResponse = await expectedBackendResponse;
expect(await interceptedBackendResponse.json()).toStrictEqual({
detail: expectedDetail,
});

View File

@@ -1,6 +1,6 @@
{
"name": "app-e2e",
"version": "3.1.0",
"version": "3.2.1",
"private": true,
"scripts": {
"lint": "eslint . --ext .ts",
@@ -12,9 +12,9 @@
"test:ui::chromium": "yarn test:ui --project=chromium"
},
"devDependencies": {
"@playwright/test": "1.50.1",
"@playwright/test": "1.52.0",
"@types/node": "*",
"@types/pdf-parse": "1.1.4",
"@types/pdf-parse": "1.1.5",
"eslint-config-impress": "*",
"typescript": "*"
},

View File

@@ -9,8 +9,8 @@ server {
try_files $uri index.html $uri/ =404;
}
location /docs/ {
error_page 404 /docs/[id]/;
location ~ "^/docs/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$" {
try_files $uri /docs/[id]/index.html;
}
error_page 404 /404.html;

View File

@@ -1,6 +1,6 @@
{
"name": "app-impress",
"version": "3.1.0",
"version": "3.2.1",
"private": true,
"scripts": {
"dev": "next dev",
@@ -15,70 +15,71 @@
"test:watch": "jest --watch"
},
"dependencies": {
"@ag-media/react-pdf-table": "2.0.1",
"@blocknote/core": "0.23.2-hotfix.0",
"@blocknote/mantine": "0.23.2-hotfix.0",
"@blocknote/react": "0.23.2-hotfix.0",
"@blocknote/xl-docx-exporter": "0.23.2-hotfix.0",
"@blocknote/xl-pdf-exporter": "0.23.2-hotfix.0",
"@ag-media/react-pdf-table": "2.0.2",
"@blocknote/code-block": "0.29.1",
"@blocknote/core": "0.29.1",
"@blocknote/mantine": "0.29.1",
"@blocknote/react": "0.29.1",
"@blocknote/xl-docx-exporter": "0.29.1",
"@blocknote/xl-pdf-exporter": "0.29.1",
"@fontsource/material-icons": "5.2.5",
"@gouvfr-lasuite/integration": "1.0.2",
"@gouvfr-lasuite/ui-kit": "0.1.3",
"@gouvfr-lasuite/integration": "1.0.3",
"@gouvfr-lasuite/ui-kit": "0.4.1",
"@hocuspocus/provider": "2.15.2",
"@openfun/cunningham-react": "3.0.0",
"@react-pdf/renderer": "4.1.6",
"@sentry/nextjs": "9.3.0",
"@tanstack/react-query": "5.67.1",
"@react-pdf/renderer": "4.3.0",
"@sentry/nextjs": "9.15.0",
"@tanstack/react-query": "5.75.4",
"canvg": "4.0.3",
"clsx": "2.1.1",
"cmdk": "1.0.4",
"cmdk": "1.1.1",
"crisp-sdk-web": "1.0.25",
"docx": "9.1.1",
"i18next": "24.2.2",
"i18next-browser-languagedetector": "8.0.4",
"docx": "9.4.1",
"i18next": "25.1.1",
"i18next-browser-languagedetector": "8.1.0",
"idb": "8.0.2",
"lodash": "4.17.21",
"luxon": "3.5.0",
"next": "15.2.4",
"posthog-js": "1.227.0",
"luxon": "3.6.1",
"next": "15.3.1",
"posthog-js": "1.239.1",
"react": "*",
"react-aria-components": "1.6.0",
"react-aria-components": "1.8.0",
"react-dom": "*",
"react-i18next": "15.4.1",
"react-intersection-observer": "9.15.1",
"react-i18next": "15.5.1",
"react-intersection-observer": "9.16.0",
"react-select": "5.10.1",
"styled-components": "6.1.15",
"styled-components": "6.1.17",
"use-debounce": "10.0.4",
"y-protocols": "1.0.6",
"yjs": "*",
"zustand": "5.0.3"
"zustand": "5.0.4"
},
"devDependencies": {
"@svgr/webpack": "8.1.0",
"@tanstack/react-query-devtools": "5.67.1",
"@tanstack/react-query-devtools": "5.75.4",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.2.0",
"@testing-library/react": "16.3.0",
"@testing-library/user-event": "14.6.1",
"@types/jest": "29.5.14",
"@types/lodash": "4.17.16",
"@types/luxon": "3.4.2",
"@types/luxon": "3.6.2",
"@types/node": "*",
"@types/react": "*",
"@types/react-dom": "*",
"cross-env": "7.0.3",
"dotenv": "16.4.7",
"dotenv": "16.5.0",
"eslint-config-impress": "*",
"fetch-mock": "9.11.0",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"node-fetch": "2.7.0",
"prettier": "3.5.3",
"stylelint": "16.15.0",
"stylelint-config-standard": "37.0.0",
"stylelint": "16.19.1",
"stylelint-config-standard": "38.0.0",
"stylelint-prettier": "5.0.3",
"typescript": "*",
"webpack": "5.98.0",
"webpack": "5.99.7",
"workbox-webpack-plugin": "7.1.0"
}
}

View File

@@ -18,7 +18,7 @@ export const Card = ({
$background="white"
$radius="4px"
$css={css`
border: 1px solid ${colorsTokens()['greyscale-200']};
border: 1px solid ${colorsTokens['greyscale-200']};
${$css}
`}
{...props}

View File

@@ -35,9 +35,7 @@ export const DropdownMenu = ({
label,
topMessage,
}: PropsWithChildren<DropdownMenuProps>) => {
const theme = useCunninghamTheme();
const spacings = theme.spacingsTokens();
const colors = theme.colorsTokens();
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const [isOpen, setIsOpen] = useState(false);
const blockButtonRef = useRef<HTMLDivElement>(null);
@@ -120,11 +118,11 @@ export const DropdownMenu = ({
key={option.label}
$align="center"
$justify="space-between"
$background={colors['greyscale-000']}
$color={colors['primary-600']}
$background={colorsTokens['greyscale-000']}
$color={colorsTokens['primary-600']}
$padding={{ vertical: 'xs', horizontal: 'base' }}
$width="100%"
$gap={spacings['base']}
$gap={spacingsTokens['base']}
$css={css`
border: none;
${index === 0 &&
@@ -148,7 +146,11 @@ export const DropdownMenu = ({
}
`}
>
<Box $direction="row" $align="center" $gap={spacings['base']}>
<Box
$direction="row"
$align="center"
$gap={spacingsTokens['base']}
>
{option.icon && (
<Icon
$size="20px"

View File

@@ -27,7 +27,6 @@ export const QuickSearchInput = ({
}: Props) => {
const { t } = useTranslation();
const { spacingsTokens } = useCunninghamTheme();
const spacing = spacingsTokens();
if (children) {
return (
@@ -44,7 +43,7 @@ export const QuickSearchInput = ({
$direction="row"
$align="center"
className="quick-search-input"
$gap={spacing['2xs']}
$gap={spacingsTokens['2xs']}
$padding={{ all: 'base' }}
>
{!loading && <Icon iconName="search" $variation="600" />}

View File

@@ -17,7 +17,6 @@ export const QuickSearchItemContent = ({
right,
}: QuickSearchItemContentProps) => {
const { spacingsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const { isDesktop } = useResponsiveStore();
@@ -32,7 +31,7 @@ export const QuickSearchItemContent = ({
<Box
$direction="row"
$align="center"
$gap={spacings['2xs']}
$gap={spacingsTokens['2xs']}
$width="100%"
>
{left}

View File

@@ -26,7 +26,7 @@ export const HorizontalSeparator = ({
$background={
variant === SeparatorVariant.DARK
? '#e5e5e533'
: colorsTokens()['greyscale-100']
: colorsTokens['greyscale-100']
}
className="--docs--horizontal-separator"
/>

View File

@@ -13,17 +13,15 @@ export const SeparatedSection = ({
showSeparator = true,
children,
}: PropsWithChildren<Props>) => {
const theme = useCunninghamTheme();
const colors = theme.colorsTokens();
const spacings = theme.spacingsTokens();
const { colorsTokens, spacingsTokens } = useCunninghamTheme();
return (
<Box
$css={css`
width: 100%;
padding: ${spacings['sm']} 0;
padding: ${spacingsTokens['sm']} 0;
${showSeparator &&
css`
border-bottom: 1px solid ${colors?.['greyscale-200']};
border-bottom: 1px solid ${colorsTokens['greyscale-200']};
`}
`}
>

View File

@@ -19,6 +19,21 @@ interface ConfigResponse {
SENTRY_DSN?: string;
}
const LOCAL_STORAGE_KEY = 'docs_config';
function getCachedTranslation() {
try {
const jsonString = localStorage.getItem(LOCAL_STORAGE_KEY);
return jsonString ? (JSON.parse(jsonString) as ConfigResponse) : undefined;
} catch {
return undefined;
}
}
function setCachedTranslation(translations: ConfigResponse) {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(translations));
}
export const getConfig = async (): Promise<ConfigResponse> => {
const response = await fetchAPI(`config/`);
@@ -26,15 +41,25 @@ export const getConfig = async (): Promise<ConfigResponse> => {
throw new APIError('Failed to get the doc', await errorCauses(response));
}
return response.json() as Promise<ConfigResponse>;
const config = response.json() as Promise<ConfigResponse>;
setCachedTranslation(await config);
return config;
};
export const KEY_CONFIG = 'config';
export function useConfig() {
return useQuery<ConfigResponse, APIError, ConfigResponse>({
const cachedData = getCachedTranslation();
const oneHour = 1000 * 60 * 60;
const response = useQuery<ConfigResponse, APIError, ConfigResponse>({
queryKey: [KEY_CONFIG],
queryFn: () => getConfig(),
staleTime: Infinity,
initialData: cachedData,
staleTime: oneHour,
initialDataUpdatedAt: Date.now() - oneHour, // Force initial data to be considered stale
});
return response;
}

View File

@@ -4,7 +4,7 @@ describe('<useCunninghamTheme />', () => {
it('has the logo correctly set', () => {
const { themeTokens, setTheme } = useCunninghamTheme.getState();
setTheme('default');
const logo = themeTokens().logo;
const logo = themeTokens.logo;
expect(logo?.src).toBe('/assets/logo-gouv.svg');
expect(logo?.widthHeader).toBe('110px');
expect(logo?.widthFooter).toBe('220px');

View File

@@ -3,39 +3,53 @@ import { create } from 'zustand';
import { tokens } from './cunningham-tokens';
type Tokens = typeof tokens.themes.default;
type Tokens = typeof tokens.themes.default &
Partial<(typeof tokens.themes)[keyof typeof tokens.themes]>;
type ColorsTokens = Tokens['theme']['colors'];
type FontSizesTokens = Tokens['theme']['font']['sizes'];
type SpacingsTokens = Tokens['theme']['spacings'];
type ComponentTokens = Tokens['components'];
export type Theme = keyof typeof tokens.themes;
interface AuthStore {
theme: string;
interface ThemeStore {
theme: Theme;
setTheme: (theme: Theme) => void;
themeTokens: () => Partial<Tokens['theme']>;
colorsTokens: () => Partial<ColorsTokens>;
fontSizesTokens: () => Partial<FontSizesTokens>;
spacingsTokens: () => Partial<SpacingsTokens>;
componentTokens: () => ComponentTokens;
themeTokens: Partial<Tokens['theme']>;
colorsTokens: Partial<ColorsTokens>;
fontSizesTokens: Partial<FontSizesTokens>;
spacingsTokens: Partial<SpacingsTokens>;
componentTokens: ComponentTokens;
}
export const useCunninghamTheme = create<AuthStore>((set, get) => {
const currentTheme = () =>
merge(
tokens.themes['default'],
tokens.themes[get().theme as keyof typeof tokens.themes],
) as Tokens;
const getMergedTokens = (theme: Theme) => {
return merge({}, tokens.themes['default'], tokens.themes[theme]);
};
return {
theme: 'default',
themeTokens: () => currentTheme().theme,
colorsTokens: () => currentTheme().theme.colors,
componentTokens: () => currentTheme().components,
spacingsTokens: () => currentTheme().theme.spacings,
fontSizesTokens: () => currentTheme().theme.font.sizes,
setTheme: (theme: Theme) => {
set({ theme });
},
};
});
const DEFAULT_THEME: Theme = 'default';
const defaultTokens = getMergedTokens(DEFAULT_THEME);
const initialState: ThemeStore = {
theme: DEFAULT_THEME,
setTheme: () => {},
themeTokens: defaultTokens.theme,
colorsTokens: defaultTokens.theme.colors,
componentTokens: defaultTokens.components,
spacingsTokens: defaultTokens.theme.spacings,
fontSizesTokens: defaultTokens.theme.font.sizes,
};
export const useCunninghamTheme = create<ThemeStore>((set) => ({
...initialState,
setTheme: (theme: Theme) => {
const newTokens = getMergedTokens(theme);
set({
theme,
themeTokens: newTokens.theme,
colorsTokens: newTokens.theme.colors,
componentTokens: newTokens.components,
spacingsTokens: newTokens.theme.spacings,
fontSizesTokens: newTokens.theme.font.sizes,
});
},
}));

View File

@@ -1,11 +1,11 @@
import { codeBlock } from '@blocknote/code-block';
import {
BlockNoteSchema,
Dictionary,
defaultBlockSpecs,
locales,
withPageBreak,
} from '@blocknote/core';
import '@blocknote/core/fonts/inter.css';
import * as locales from '@blocknote/core/locales';
import { BlockNoteView } from '@blocknote/mantine';
import '@blocknote/mantine/style.css';
import { useCreateBlockNote } from '@blocknote/react';
@@ -27,14 +27,13 @@ import { randomColor } from '../utils';
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar';
import { DividerBlock, QuoteBlock } from './custom-blocks';
import { DividerBlock } from './custom-blocks';
export const blockNoteSchema = withPageBreak(
BlockNoteSchema.create({
blockSpecs: {
...defaultBlockSpecs,
divider: DividerBlock,
quote: QuoteBlock,
},
}),
);
@@ -63,6 +62,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
const editor = useCreateBlockNote(
{
codeBlock,
collaboration: {
provider,
fragment: provider.document.getXmlFragment('document-store'),
@@ -112,7 +112,13 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
},
showCursorLabels: showCursorLabels as 'always' | 'activity',
},
dictionary: locales[lang as keyof typeof locales] as Dictionary,
dictionary: locales[lang as keyof typeof locales],
tables: {
splitCells: true,
cellBackgroundColor: true,
cellTextColor: true,
headers: true,
},
uploadFile,
schema: blockNoteSchema,
},

View File

@@ -11,10 +11,7 @@ import { useTranslation } from 'react-i18next';
import { DocsBlockSchema } from '../types';
import {
getDividerReactSlashMenuItems,
getQuoteReactSlashMenuItems,
} from './custom-blocks';
import { getDividerReactSlashMenuItems } from './custom-blocks';
export const BlockNoteSuggestionMenu = () => {
const editor = useBlockNoteEditor<DocsBlockSchema>();
@@ -28,7 +25,6 @@ export const BlockNoteSuggestionMenu = () => {
combineByGroup(
getDefaultReactSlashMenuItems(editor),
getPageBreakReactSlashMenuItems(editor),
getQuoteReactSlashMenuItems(editor, t, basicBlocksName),
getDividerReactSlashMenuItems(editor, t, basicBlocksName),
),
query,

View File

@@ -6,12 +6,9 @@ import {
useDictionary,
} from '@blocknote/react';
import React, { JSX, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useConfig } from '@/core/config/api';
import { getQuoteFormattingToolbarItems } from '../custom-blocks';
import { AIGroupButton } from './AIButton';
import { FileDownloadButton } from './FileDownloadButton';
import { MarkdownButton } from './MarkdownButton';
@@ -21,13 +18,11 @@ export const BlockNoteToolbar = () => {
const dict = useDictionary();
const [confirmOpen, setIsConfirmOpen] = useState(false);
const [onConfirm, setOnConfirm] = useState<() => void | Promise<void>>();
const { t } = useTranslation();
const { data: conf } = useConfig();
const toolbarItems = useMemo(() => {
const toolbarItems = getFormattingToolbarItems([
...blockTypeSelectItems(dict),
getQuoteFormattingToolbarItems(t),
]);
const fileDownloadButtonIndex = toolbarItems.findIndex(
(item) =>
@@ -51,7 +46,7 @@ export const BlockNoteToolbar = () => {
}
return toolbarItems as JSX.Element[];
}, [dict, t]);
}, [dict]);
const formattingToolbar = useCallback(() => {
return (

View File

@@ -15,6 +15,7 @@ import { useCallback, useMemo } from 'react';
import { RiDownload2Fill } from 'react-icons/ri';
import { downloadFile, exportResolveFileUrl } from '@/docs/doc-export';
import { isSafeUrl } from '@/utils/url';
export const FileDownloadButton = ({
open,
@@ -59,7 +60,11 @@ export const FileDownloadButton = ({
*/
if (!url.includes(window.location.hostname) && !url.includes('base64')) {
if (!editor.resolveFileUrl) {
window.open(url);
if (!isSafeUrl(url)) {
return;
}
window.open(url, '_blank', 'noopener,noreferrer');
} else {
void editor
.resolveFileUrl(url)

View File

@@ -67,7 +67,7 @@ export const DocEditor = ({ doc, versionId }: DocEditorProps) => {
</Box>
<Box
$background={colorsTokens()['primary-bg']}
$background={colorsTokens['primary-bg']}
$direction="row"
$width="100%"
$css="overflow-x: clip; flex: 1;"

View File

@@ -22,9 +22,9 @@ export const DividerBlock = createReactBlockSpec(
<Box
as="hr"
$width="100%"
$background={colorsTokens()['greyscale-300']}
$background={colorsTokens['greyscale-300']}
$margin="1rem 0"
$css={`border: 1px solid ${colorsTokens()['greyscale-300']};`}
$css={`border: 1px solid ${colorsTokens['greyscale-300']};`}
/>
);
},

View File

@@ -1,68 +0,0 @@
import { defaultProps, insertOrUpdateBlock } from '@blocknote/core';
import { BlockTypeSelectItem, createReactBlockSpec } from '@blocknote/react';
import { TFunction } from 'i18next';
import { Box, Icon } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { DocsBlockNoteEditor } from '../../types';
export const QuoteBlock = createReactBlockSpec(
{
type: 'quote',
propSchema: {
textAlignment: defaultProps.textAlignment,
},
content: 'inline',
},
{
render: (props) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { colorsTokens } = useCunninghamTheme();
return (
<Box
as="blockquote"
className="inline-content"
$margin="0 0 1rem 0"
$padding="0.5rem 1rem"
style={{
borderLeft: `4px solid ${colorsTokens()['greyscale-300']}`,
fontStyle: 'italic',
flexGrow: 1,
}}
$color="var(--c--theme--colors--greyscale-500)"
ref={props.contentRef}
/>
);
},
},
);
export const getQuoteReactSlashMenuItems = (
editor: DocsBlockNoteEditor,
t: TFunction<'translation', undefined>,
group: string,
) => [
{
title: t('Quote'),
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: 'quote',
});
},
aliases: ['quote', 'blockquote', 'citation'],
group,
icon: <Icon iconName="format_quote" $size="18px" />,
subtext: t('Add a quote block'),
},
];
export const getQuoteFormattingToolbarItems = (
t: TFunction<'translation', undefined>,
): BlockTypeSelectItem => ({
name: t('Quote'),
type: 'quote',
icon: () => <Icon iconName="format_quote" $size="16px" />,
isSelected: (block) => block.type === 'quote',
});

View File

@@ -1,2 +1 @@
export * from './DividerBlock';
export * from './QuoteBlock';

View File

@@ -61,7 +61,7 @@ const useSaveDoc = (docId: string, yDoc: Y.Doc, canSave: boolean) => {
const isSaving = saveDoc();
/**
* Firefox does not trigger the request everytime the user leaves the page.
* Firefox does not trigger the request every time the user leaves the page.
* Plus the request is not intercepted by the service worker.
* So we prevent the default behavior to have the popup asking the user
* if he wants to leave the page, by adding the popup, we let the time to the

View File

@@ -57,9 +57,6 @@ export const cssEditor = (readonly: boolean) => css`
.bn-side-menu[data-block-type='heading'][data-level='3'] {
height: 35px;
}
.bn-side-menu[data-block-type='quote'] {
height: 46px;
}
.bn-side-menu[data-block-type='divider'] {
height: 38px;
}
@@ -84,11 +81,19 @@ export const cssEditor = (readonly: boolean) => css`
}
}
.bn-editor {
& .bn-editor {
color: var(--c--theme--colors--greyscale-700);
/**
* Quotes
*/
blockquote {
border-left: 4px solid var(--c--theme--colors--greyscale-300);
font-style: italic;
}
}
.bn-block-outer:not(:first-child) {
& .bn-block-outer:not(:first-child) {
&:has(h1) {
margin-top: 32px;
}
@@ -105,12 +110,6 @@ export const cssEditor = (readonly: boolean) => css`
padding: 2px;
border-radius: 4px;
}
& .bn-inline-content {
width: 100%;
}
.bn-block-content[data-content-type='checkListItem'] > div {
width: 100%;
}
@media screen and (width <= 768px) {
& .bn-editor {

View File

@@ -14,7 +14,7 @@ export const blockMappingDividerDocx: DocsExporterDocx['mappings']['blockMapping
},
border: {
top: {
color: colorsTokens()['greyscale-300'],
color: colorsTokens['greyscale-300'],
size: 1,
style: 'single',
space: 1,

View File

@@ -12,7 +12,7 @@ export const blockMappingDividerPDF: DocsExporterPDF['mappings']['blockMapping']
<Text
style={{
marginVertical: 10,
backgroundColor: colorsTokens()['greyscale-300'],
backgroundColor: colorsTokens['greyscale-300'],
height: '2px',
}}
/>

View File

@@ -1,46 +1,120 @@
import { TD, TH, TR, Table } from '@ag-media/react-pdf-table';
import { View } from '@react-pdf/renderer';
/**
* We use mainly the Blocknotes code, mixed with @ag-media/react-pdf-table
* to have a better Table support.
* See:
* https://github.com/TypeCellOS/BlockNote/blob/004c0bf720fe1415c497ad56449015c5f4dd7ba0/packages/xl-pdf-exporter/src/pdf/util/table/Table.tsx
*
* We succeeded to manage the colspan, but rowspan is not supported yet.
*/
import { TD, TR, Table } from '@ag-media/react-pdf-table';
import { mapTableCell } from '@blocknote/core';
import { StyleSheet, Text } from '@react-pdf/renderer';
import { DocsExporterPDF } from '../types';
const PIXELS_PER_POINT = 0.75;
const styles = StyleSheet.create({
tableContainer: {
border: '1px solid #ddd',
},
row: {
flexDirection: 'row',
flexWrap: 'wrap',
display: 'flex',
},
cell: {
paddingHorizontal: 5 * PIXELS_PER_POINT,
paddingTop: 3 * PIXELS_PER_POINT,
wordWrap: 'break-word',
whiteSpace: 'pre-wrap',
},
headerCell: {
fontWeight: 'bold',
},
});
export const blockMappingTablePDF: DocsExporterPDF['mappings']['blockMapping']['table'] =
(block, exporter) => {
const { options } = exporter;
const blockContent = block.content;
// If headerRows is 1, then the first row is a header row
const headerRows = new Array(blockContent.headerRows ?? 0).fill(
true,
) as boolean[];
// If headerCols is 1, then the first column is a header column
const headerCols = new Array(blockContent.headerCols ?? 0).fill(
true,
) as boolean[];
/**
* Calculate the table scale based on the column widths.
*/
const columnWidths = blockContent.columnWidths.map((w) => w || 120);
const fullWidth = 730;
const totalWidth = Math.min(
columnWidths.reduce((sum, w) => sum + w, 0),
fullWidth,
);
const tableScale = (totalWidth * 100) / fullWidth;
return (
<Table>
{block.content.rows.map((row, index) => {
if (index === 0) {
return (
<TH key={index}>
{row.cells.map((cell, index) => {
// Make empty cells are rendered.
if (cell.length === 0) {
cell.push({
styles: {},
text: ' ',
type: 'text',
});
}
return (
<TD key={index}>{exporter.transformInlineContent(cell)}</TD>
);
})}
</TH>
);
}
<Table style={[styles.tableContainer, { width: `${tableScale}%` }]}>
{blockContent.rows.map((row, rowIndex) => {
const isHeaderRow = headerRows[rowIndex];
return (
<TR key={index}>
{row.cells.map((cell, index) => {
// Make empty cells are rendered.
if (cell.length === 0) {
<TR key={rowIndex}>
{row.cells.map((c, colIndex) => {
const formatCell = mapTableCell(c);
const isHeaderCol = headerCols[colIndex];
const cell = formatCell.content;
const cellProps = formatCell.props;
// Make empty cells rendered.
if (Array.isArray(cell) && cell.length === 0) {
cell.push({
styles: {},
text: ' ',
type: 'text',
});
}
const weight = columnWidths
.slice(colIndex, colIndex + (cellProps.colspan || 1))
.reduce((sum, w) => sum + w, 0);
const flexCell = {
flex: `${weight} ${weight} 0%`,
};
const arrayStyle = [
isHeaderRow || isHeaderCol ? styles.headerCell : {},
flexCell,
{
color:
cellProps.textColor === 'default'
? undefined
: options.colors[
cellProps.textColor as keyof typeof options.colors
].text,
backgroundColor:
cellProps.backgroundColor === 'default'
? undefined
: options.colors[
cellProps.backgroundColor as keyof typeof options.colors
].background,
textAlign: cellProps.textAlignment,
},
];
return (
<TD key={index}>
<View>{exporter.transformInlineContent(cell)}</View>
<TD key={colIndex} style={arrayStyle}>
<Text style={styles.cell}>
{exporter.transformInlineContent(cell)}
</Text>
</TD>
);
})}

View File

@@ -9,7 +9,7 @@ import {
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import { pdf } from '@react-pdf/renderer';
import { DocumentProps, pdf } from '@react-pdf/renderer';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
@@ -92,7 +92,10 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
const exporter = new PDFExporter(editor.schema, pdfDocsSchemaMappings, {
resolveFileUrl: async (url) => exportCorsResolveFileUrl(doc.id, url),
});
const pdfDocument = await exporter.toReactPDFDocument(exportDocument);
const pdfDocument = (await exporter.toReactPDFDocument(
exportDocument,
)) as React.ReactElement<DocumentProps>;
blobExport = await pdf(pdfDocument).toBlob();
} else {
const exporter = new DOCXExporter(editor.schema, docxDocsSchemaMappings, {

View File

@@ -22,8 +22,6 @@ interface DocHeaderProps {
export const DocHeader = ({ doc }: DocHeaderProps) => {
const { colorsTokens, spacingsTokens } = useCunninghamTheme();
const { isDesktop } = useResponsiveStore();
const spacings = spacingsTokens();
const colors = colorsTokens();
const { t } = useTranslation();
const docIsPublic = doc.link_reach === LinkReach.PUBLIC;
@@ -36,21 +34,21 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
<Box
$width="100%"
$padding={{ top: isDesktop ? '4xl' : 'md' }}
$gap={spacings['base']}
$gap={spacingsTokens['base']}
aria-label={t('It is the card information about the document.')}
className="--docs--doc-header"
>
{(docIsPublic || docIsAuth) && (
<Box
aria-label={t('Public document')}
$color={colors['primary-800']}
$background={colors['primary-050']}
$radius={spacings['3xs']}
$color={colorsTokens['primary-800']}
$background={colorsTokens['primary-050']}
$radius={spacingsTokens['3xs']}
$direction="row"
$padding="xs"
$flex={1}
$align="center"
$gap={spacings['3xs']}
$gap={spacingsTokens['3xs']}
$css={css`
border: 1px solid var(--c--theme--colors--primary-300, #e3e3fd);
`}
@@ -82,7 +80,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
$align="center"
$maxWidth="100%"
>
<Box $gap={spacings['3xs']} $overflow="auto">
<Box $gap={spacingsTokens['3xs']} $overflow="auto">
<DocTitle doc={doc} />
<Box $direction="row">

View File

@@ -116,7 +116,7 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
onBlurCapture={(event) =>
handleTitleSubmit(event.target.textContent || '')
}
$color={colorsTokens()['greyscale-1000']}
$color={colorsTokens['greyscale-1000']}
$minHeight="40px"
$padding={{ right: 'big' }}
$css={css`

View File

@@ -47,9 +47,6 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const colors = colorsTokens();
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
const [isModalExportOpen, setIsModalExportOpen] = useState(false);
const selectHistoryModal = useModal();
@@ -182,7 +179,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
$direction="row"
$align="center"
$margin={{ left: 'auto' }}
$gap={spacings['2xs']}
$gap={spacingsTokens['2xs']}
>
{!isSmallMobile && (
<>
@@ -245,12 +242,12 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
$css={css`
border-radius: 4px;
&:hover {
background-color: ${colors['greyscale-100']};
background-color: ${colorsTokens['greyscale-100']};
}
${isSmallMobile
? css`
padding: 10px;
border: 1px solid ${colors['greyscale-300']};
border: 1px solid ${colorsTokens['greyscale-300']};
`
: ''}
`}

View File

@@ -12,7 +12,6 @@ interface DocVersionHeaderProps {
export const DocVersionHeader = ({ title }: DocVersionHeaderProps) => {
const { spacingsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const { t } = useTranslation();
return (
@@ -20,7 +19,7 @@ export const DocVersionHeader = ({ title }: DocVersionHeaderProps) => {
<Box
$width="100%"
$padding={{ vertical: 'base' }}
$gap={spacings['base']}
$gap={spacingsTokens['base']}
aria-label={t('It is the document title')}
className="--docs--doc-version-header"
>

View File

@@ -12,34 +12,11 @@ export const useTrans = () => {
[Role.OWNER]: t('Owner'),
};
const getNotAllowedMessage = (
canUpdate: boolean,
isLastOwner: boolean,
isOtherOwner: boolean,
) => {
if (!canUpdate) {
return undefined;
}
if (isLastOwner) {
return t(
'You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.',
);
}
if (isOtherOwner) {
return t('You cannot update the role or remove other owner.');
}
return undefined;
};
return {
transRole: (role: Role) => {
return translatedRoles[role];
},
untitledDocument: t('Untitled document'),
translatedRoles,
getNotAllowedMessage,
};
};

View File

@@ -3,32 +3,22 @@ import { css } from 'styled-components';
import { DropdownMenu, DropdownMenuOption, Text } from '@/components';
import { Role, useTrans } from '@/docs/doc-management/';
type Props = {
currentRole: Role;
onSelectRole?: (role: Role) => void;
type DocRoleDropdownProps = {
canUpdate?: boolean;
isLastOwner?: boolean;
isOtherOwner?: boolean;
currentRole: Role;
message?: string;
onSelectRole: (role: Role) => void;
rolesAllowed?: Role[];
};
export const DocRoleDropdown = ({
canUpdate = true,
currentRole,
message,
onSelectRole,
isLastOwner,
isOtherOwner,
}: Props) => {
const { transRole, translatedRoles, getNotAllowedMessage } = useTrans();
const roles: DropdownMenuOption[] = Object.keys(translatedRoles).map(
(key) => {
return {
label: transRole(key as Role),
callback: () => onSelectRole?.(key as Role),
disabled: isLastOwner || isOtherOwner,
isSelected: currentRole === (key as Role),
};
},
);
rolesAllowed,
}: DocRoleDropdownProps) => {
const { transRole, translatedRoles } = useTrans();
if (!canUpdate) {
return (
@@ -38,13 +28,20 @@ export const DocRoleDropdown = ({
);
}
const roles: DropdownMenuOption[] = Object.keys(translatedRoles).map(
(key) => {
return {
label: transRole(key as Role),
callback: () => onSelectRole?.(key as Role),
disabled: rolesAllowed && !rolesAllowed.includes(key as Role),
isSelected: currentRole === (key as Role),
};
},
);
return (
<DropdownMenu
topMessage={getNotAllowedMessage(
canUpdate,
!!isLastOwner,
!!isOtherOwner,
)}
topMessage={message}
label="doc-role-dropdown"
showArrow={true}
options={roles}

View File

@@ -43,8 +43,6 @@ export const DocShareAddMemberList = ({
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const [invitationRole, setInvitationRole] = useState<Role>(Role.EDITOR);
const canShare = doc.abilities.accesses_manage;
const spacing = spacingsTokens();
const color = colorsTokens();
const { mutateAsync: createInvitation } = useCreateDocInvitation();
const { mutateAsync: createDocAccess } = useCreateDocAccess();
@@ -115,12 +113,12 @@ export const DocShareAddMemberList = ({
<Box
data-testid="doc-share-add-member-list"
$direction="row"
$padding={spacing.sm}
$padding={spacingsTokens.sm}
$align="center"
$background={color['greyscale-050']}
$radius={spacing['3xs']}
$background={colorsTokens['greyscale-050']}
$radius={spacingsTokens['3xs']}
$css={css`
border: 1px solid ${color['greyscale-200']};
border: 1px solid ${colorsTokens['greyscale-200']};
`}
className="--docs--doc-share-add-member-list"
>
@@ -129,7 +127,7 @@ export const DocShareAddMemberList = ({
$align="center"
$wrap="wrap"
$flex={1}
$gap={spacing.xs}
$gap={spacingsTokens.xs}
>
{selectedUsers.map((user) => (
<DocShareAddMemberListItem
@@ -139,7 +137,7 @@ export const DocShareAddMemberList = ({
/>
))}
</Box>
<Box $direction="row" $align="center" $gap={spacing.xs}>
<Box $direction="row" $align="center" $gap={spacingsTokens.xs}>
<DocRoleDropdown
canUpdate={canShare}
currentRole={invitationRole}

View File

@@ -12,27 +12,25 @@ type Props = {
export const DocShareAddMemberListItem = ({ user, onRemoveUser }: Props) => {
const { spacingsTokens, colorsTokens, fontSizesTokens } =
useCunninghamTheme();
const spacing = spacingsTokens();
const color = colorsTokens();
const fontSize = fontSizesTokens();
return (
<Box
data-testid={`doc-share-add-member-${user.email}`}
$radius={spacing['3xs']}
$radius={spacingsTokens['3xs']}
$direction="row"
$height="fit-content"
$justify="center"
$align="center"
$gap={spacing['3xs']}
$background={color['greyscale-250']}
$gap={spacingsTokens['3xs']}
$background={colorsTokens['greyscale-250']}
$padding={{
left: spacing['xs'],
right: spacing['4xs'],
vertical: spacing['4xs'],
left: spacingsTokens['xs'],
right: spacingsTokens['4xs'],
vertical: spacingsTokens['4xs'],
}}
$css={css`
color: ${color['greyscale-1000']};
font-size: ${fontSize['xs']};
color: ${colorsTokens['greyscale-1000']};
font-size: ${fontSizesTokens['xs']};
`}
className="--docs--doc-share-add-member-list-item"
>

View File

@@ -24,7 +24,6 @@ type Props = {
export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
const { t } = useTranslation();
const { spacingsTokens } = useCunninghamTheme();
const spacing = spacingsTokens();
const fakeUser: User = {
id: invitation.email,
full_name: invitation.email,
@@ -91,7 +90,7 @@ export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
alwaysShowRight={true}
user={fakeUser}
right={
<Box $direction="row" $align="center" $gap={spacing['2xs']}>
<Box $direction="row" $align="center" $gap={spacingsTokens['2xs']}>
<DocRoleDropdown
currentRole={invitation.role}
onSelectRole={onUpdate}

View File

@@ -23,13 +23,16 @@ type Props = {
};
export const DocShareMemberItem = ({ doc, access }: Props) => {
const { t } = useTranslation();
const { isLastOwner, isOtherOwner } = useWhoAmI(access);
const { isLastOwner } = useWhoAmI(access);
const { toast } = useToastProvider();
const { isDesktop } = useResponsiveStore();
const { spacingsTokens } = useCunninghamTheme();
const spacing = spacingsTokens();
const isNotAllowed =
isOtherOwner || !!isLastOwner || !doc.abilities.accesses_manage;
const message = isLastOwner
? t(
'You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.',
)
: undefined;
const { mutate: updateDocAccess } = useUpdateDocAccess({
onError: () => {
@@ -64,7 +67,7 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
label: t('Delete'),
icon: 'delete',
callback: onRemove,
disabled: isNotAllowed,
disabled: !access.abilities.destroy,
},
];
@@ -78,13 +81,13 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
alwaysShowRight={true}
user={access.user}
right={
<Box $direction="row" $align="center" $gap={spacing['2xs']}>
<Box $direction="row" $align="center" $gap={spacingsTokens['2xs']}>
<DocRoleDropdown
currentRole={access.role}
onSelectRole={onUpdate}
canUpdate={doc.abilities.accesses_manage}
isLastOwner={isLastOwner}
isOtherOwner={!!isOtherOwner}
message={message}
rolesAllowed={access.abilities.set_role_to}
/>
{isDesktop && doc.abilities.accesses_manage && (

View File

@@ -70,10 +70,6 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
docId: doc.id,
});
const invitationQuery = useDocInvitationsInfinite({
docId: doc.id,
});
const searchUsersQuery = useUsers(
{ query: userQuery, docId: doc.id },
{
@@ -107,52 +103,6 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
};
}, [membersQuery, t]);
const invitationsData: QuickSearchData<Invitation> = useMemo(() => {
const invitations =
invitationQuery.data?.pages.flatMap((page) => page.results) || [];
return {
groupName: t('Pending invitations'),
elements: invitations,
endActions: invitationQuery.hasNextPage
? [
{
content: <LoadMoreText data-testid="load-more-invitations" />,
onSelect: () => void invitationQuery.fetchNextPage(),
},
]
: undefined,
};
}, [invitationQuery, t]);
const searchUserData: QuickSearchData<User> = useMemo(() => {
const users = searchUsersQuery.data || [];
const isEmail = isValidEmail(userQuery);
const newUser: User = {
id: userQuery,
full_name: '',
email: userQuery,
short_name: '',
language: '',
};
const hasEmailInUsers = users.some((user) => user.email === userQuery);
return {
groupName: t('Search user result'),
elements: users,
endActions:
isEmail && !hasEmailInUsers
? [
{
content: <DocShareModalInviteUserRow user={newUser} />,
onSelect: () => void onSelect(newUser),
},
]
: undefined,
};
}, [searchUsersQuery.data, t, userQuery]);
const onFilter = useDebouncedCallback((str: string) => {
setUserQuery(str);
}, 300);
@@ -254,44 +204,17 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
loading={searchUsersQuery.isLoading}
placeholder={t('Type a name or email')}
>
{canViewAccesses && (
<>
{!showMemberSection && inputValue !== '' && (
<QuickSearchGroup
group={searchUserData}
onSelect={onSelect}
renderElement={(user) => (
<DocShareModalInviteUserRow user={user} />
)}
/>
)}
{showMemberSection && (
<>
{invitationsData.elements.length > 0 && (
<Box aria-label={t('List invitation card')}>
<QuickSearchGroup
group={invitationsData}
renderElement={(invitation) => (
<DocShareInvitationItem
doc={doc}
invitation={invitation}
/>
)}
/>
</Box>
)}
<Box aria-label={t('List members card')}>
<QuickSearchGroup
group={membersData}
renderElement={(access) => (
<DocShareMemberItem doc={doc} access={access} />
)}
/>
</Box>
</>
)}
</>
{showMemberSection ? (
<QuickSearchMemberSection
doc={doc}
membersData={membersData}
/>
) : (
<QuickSearchInviteInputSection
searchUsersRawData={searchUsersQuery.data}
onSelect={onSelect}
userQuery={userQuery}
/>
)}
</QuickSearch>
)}
@@ -306,3 +229,109 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
</>
);
};
interface QuickSearchInviteInputSectionProps {
onSelect: (usr: User) => void;
searchUsersRawData: User[] | undefined;
userQuery: string;
}
const QuickSearchInviteInputSection = ({
onSelect,
searchUsersRawData,
userQuery,
}: QuickSearchInviteInputSectionProps) => {
const { t } = useTranslation();
const searchUserData: QuickSearchData<User> = useMemo(() => {
const users = searchUsersRawData || [];
const isEmail = isValidEmail(userQuery);
const newUser: User = {
id: userQuery,
full_name: '',
email: userQuery,
short_name: '',
language: '',
};
const hasEmailInUsers = users.some((user) => user.email === userQuery);
return {
groupName: t('Search user result'),
elements: users,
endActions:
isEmail && !hasEmailInUsers
? [
{
content: <DocShareModalInviteUserRow user={newUser} />,
onSelect: () => void onSelect(newUser),
},
]
: undefined,
};
}, [onSelect, searchUsersRawData, t, userQuery]);
return (
<QuickSearchGroup
group={searchUserData}
onSelect={onSelect}
renderElement={(user) => <DocShareModalInviteUserRow user={user} />}
/>
);
};
interface QuickSearchMemberSectionProps {
doc: Doc;
membersData: QuickSearchData<Access>;
}
const QuickSearchMemberSection = ({
doc,
membersData,
}: QuickSearchMemberSectionProps) => {
const { t } = useTranslation();
const { data, hasNextPage, fetchNextPage } = useDocInvitationsInfinite({
docId: doc.id,
});
const invitationsData: QuickSearchData<Invitation> = useMemo(() => {
const invitations = data?.pages.flatMap((page) => page.results) || [];
return {
groupName: t('Pending invitations'),
elements: invitations,
endActions: hasNextPage
? [
{
content: <LoadMoreText data-testid="load-more-invitations" />,
onSelect: () => void fetchNextPage(),
},
]
: undefined,
};
}, [data?.pages, fetchNextPage, hasNextPage, t]);
return (
<>
{invitationsData.elements.length > 0 && (
<Box aria-label={t('List invitation card')}>
<QuickSearchGroup
group={invitationsData}
renderElement={(invitation) => (
<DocShareInvitationItem doc={doc} invitation={invitation} />
)}
/>
</Box>
)}
<Box aria-label={t('List members card')}>
<QuickSearchGroup
group={membersData}
renderElement={(access) => (
<DocShareMemberItem doc={doc} access={access} />
)}
/>
</Box>
</>
);
};

View File

@@ -32,8 +32,6 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
const { toast } = useToastProvider();
const { isDesktop } = useResponsiveStore();
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const spacing = spacingsTokens();
const colors = colorsTokens();
const canManage = doc.abilities.accesses_manage;
const [linkReach, setLinkReach] = useState<LinkReach>(doc.link_reach);
const [docLinkRole, setDocLinkRole] = useState<LinkRole>(doc.link_role);
@@ -90,7 +88,7 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
<Box
$padding={{ horizontal: 'base' }}
aria-label={t('Doc visibility card')}
$gap={spacing['base']}
$gap={spacingsTokens['base']}
className="--docs--doc-visibility"
>
<Text $weight="700" $size="sm" $variation="700">
@@ -100,7 +98,7 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
$direction="row"
$align="center"
$justify="space-between"
$gap={spacing['xs']}
$gap={spacingsTokens['xs']}
$width="100%"
$wrap="nowrap"
>
@@ -108,18 +106,18 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
$direction="row"
$align={isDesktop ? 'center' : undefined}
$padding={{ horizontal: '2xs' }}
$gap={canManage ? spacing['3xs'] : spacing['base']}
$gap={canManage ? spacingsTokens['3xs'] : spacingsTokens['base']}
>
<DropdownMenu
label={t('Visibility')}
arrowCss={css`
color: ${colors['primary-800']} !important;
color: ${colorsTokens['primary-800']} !important;
`}
disabled={!canManage}
showArrow={true}
options={linkReachOptions}
>
<Box $direction="row" $align="center" $gap={spacing['3xs']}>
<Box $direction="row" $align="center" $gap={spacingsTokens['3xs']}>
<Icon
$theme={canManage ? 'primary' : 'greyscale'}
$variation={canManage ? '800' : '600'}
@@ -142,7 +140,7 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
)}
</Box>
{showLinkRoleOptions && (
<Box $direction="row" $align="center" $gap={spacing['3xs']}>
<Box $direction="row" $align="center" $gap={spacingsTokens['3xs']}>
{linkReach !== LinkReach.RESTRICTED && (
<DropdownMenu
disabled={!canManage}

View File

@@ -23,8 +23,6 @@ export const SearchUserRow = ({
}: Props) => {
const hasFullName = user.full_name != null && user.full_name !== '';
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const colors = colorsTokens();
return (
<QuickSearchItemContent
@@ -34,12 +32,14 @@ export const SearchUserRow = ({
<Box
$direction="row"
$align="center"
$gap={spacings['xs']}
$gap={spacingsTokens['xs']}
className="--docs--search-user-row"
>
<UserAvatar
user={user}
background={isInvitation ? colors['greyscale-400'] : undefined}
background={
isInvitation ? colorsTokens['greyscale-400'] : undefined
}
/>
<Box $direction="column">
<Text $size="sm" $weight="500" $variation="1000">

View File

@@ -58,7 +58,7 @@ export const Heading = ({
});
}}
$radius="4px"
$background={isActive ? `${colorsTokens()['greyscale-100']}` : 'none'}
$background={isActive ? `${colorsTokens['greyscale-100']}` : 'none'}
$css="text-align: left;"
className="--docs--table-content-heading"
>

View File

@@ -13,7 +13,6 @@ export const TableContent = () => {
const { headings } = useHeadingStore();
const { editor } = useEditorStore();
const { spacingsTokens } = useCunninghamTheme();
const spacing = spacingsTokens();
const [headingIdHighlight, setHeadingIdHighlight] = useState<string>();
@@ -158,7 +157,7 @@ export const TableContent = () => {
</BoxButton>
</Box>
<Box
$gap={spacing['3xs']}
$gap={spacingsTokens['3xs']}
$css={css`
overflow-y: auto;
`}

View File

@@ -24,7 +24,6 @@ export const VersionItem = ({
isActive,
}: VersionItemProps) => {
const { colorsTokens, spacingsTokens } = useCunninghamTheme();
const spacing = spacingsTokens();
const [isModalVersionOpen, setIsModalVersionOpen] = useState(false);
@@ -33,13 +32,13 @@ export const VersionItem = ({
<Box
$width="100%"
as="li"
$background={isActive ? colorsTokens()['greyscale-100'] : 'transparent'}
$radius={spacing['3xs']}
$background={isActive ? colorsTokens['greyscale-100'] : 'transparent'}
$radius={spacingsTokens['3xs']}
$css={`
cursor: pointer;
&:hover {
background: ${colorsTokens()['greyscale-100']};
background: ${colorsTokens['greyscale-100']};
}
`}
$hasTransition

Some files were not shown because too many files have changed in this diff Show More