mirror of
https://github.com/suitenumerique/docs.git
synced 2026-05-06 23:22:15 +02:00
Compare commits
1 Commits
fix/docker
...
documentat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e1a9b6a27 |
4
.github/workflows/docker-hub.yml
vendored
4
.github/workflows/docker-hub.yml
vendored
@@ -79,9 +79,7 @@ jobs:
|
||||
context: .
|
||||
file: ./src/frontend/Dockerfile
|
||||
target: frontend-production
|
||||
build-args: |
|
||||
DOCKER_USER=${{ env.DOCKER_USER }}:-1000
|
||||
PUBLISH_AS_MIT=false
|
||||
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
19
.github/workflows/impress.yml
vendored
19
.github/workflows/impress.yml
vendored
@@ -61,25 +61,6 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
lint-spell-mistakes:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Install codespell
|
||||
run: pip install --user codespell
|
||||
- name: Check for typos
|
||||
run: |
|
||||
codespell \
|
||||
--check-filenames \
|
||||
--ignore-words-list "Dokument,afterAll,excpt,statics" \
|
||||
--skip "./git/" \
|
||||
--skip "**/*.po" \
|
||||
--skip "**/*.pot" \
|
||||
--skip "**/*.json" \
|
||||
--skip "**/yarn.lock"
|
||||
|
||||
lint-back:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
|
||||
54
CHANGELOG.md
54
CHANGELOG.md
@@ -10,55 +10,8 @@ and this project adheres to
|
||||
|
||||
## Added
|
||||
|
||||
- ✨(back) allow theme customnization using a configuration file #948
|
||||
- ✨ Add a custom callout block to the editor #892
|
||||
- 🚩(frontend) version MIT only #911
|
||||
- ✨(backend) integrate maleware_detection from django-lasuite #936
|
||||
- 🩺(CI) add lint spell mistakes #954
|
||||
|
||||
## Changed
|
||||
|
||||
- 📝(frontend) Update documentation
|
||||
- ✅(frontend) Improve tests coverage
|
||||
|
||||
### Removed
|
||||
|
||||
- 🔥(back) remove footer endpoint
|
||||
|
||||
## [3.2.1] - 2025-05-06
|
||||
|
||||
## Fixed
|
||||
|
||||
- 🐛(frontend) fix list copy paste #943
|
||||
- 📝(doc) update contributing policy (commit signatures are now mandatory) #895
|
||||
|
||||
|
||||
## [3.2.0] - 2025-05-05
|
||||
|
||||
## Added
|
||||
|
||||
- 🚸(backend) make document search on title accent-insensitive #874
|
||||
- 🚩 add homepage feature flag #861
|
||||
- 📝(doc) update contributing policy (commit signatures are now mandatory) #895
|
||||
- ✨(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
|
||||
|
||||
@@ -187,6 +140,7 @@ and this project adheres to
|
||||
- ♻️(frontend) improve table pdf rendering
|
||||
- 🐛(email) invitation emails in receivers language
|
||||
|
||||
|
||||
## [2.2.0] - 2025-02-10
|
||||
|
||||
## Added
|
||||
@@ -557,7 +511,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 occurrences of pad to doc (#99)
|
||||
- ✏️(frontend) change all occurences of pad to doc (#99)
|
||||
|
||||
## Fixed
|
||||
|
||||
@@ -575,9 +529,7 @@ 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.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
|
||||
[unreleased]: https://github.com/numerique-gouv/impress/compare/v3.1.0...main
|
||||
[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
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
Thank you for taking the time to contribute! Please follow these guidelines to ensure a smooth and productive workflow. 🚀🚀🚀
|
||||
|
||||
To get started with the project, please refer to the [README.md](https://github.com/suitenumerique/docs/blob/main/README.md) for detailed instructions on how to run Docs locally.
|
||||
To get started with the project, please refer to the [README.md](https://github.com/suitenumerique/docs/blob/main/README.md) for detailed instructions.
|
||||
|
||||
Contributors are required to sign off their commits with `git commit --signoff`: this confirms that they have read and accepted the [Developer's Certificate of Origin 1.1](https://developercertificate.org/). For security reasons we also require [signing your commits with your SSH or GPG key](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) with `git commit -S`.
|
||||
Contributors are required to sign off their commits with `git commit --sign-off`: this confirms that they have read and accepted the [Developer's Certificate of Origin 1.1](https://developercertificate.org/).
|
||||
|
||||
Please also check out our [dev handbook](https://suitenumerique.gitbook.io/handbook) to learn our best practices.
|
||||
|
||||
## Help us with translations
|
||||
|
||||
You can help us with translations on [Crowdin](https://crowdin.com/project/lasuite-docs).
|
||||
Your language is not there? Request it on our Crowdin page 😊 or ping us on [Matrix](https://matrix.to/#/#docs-official:matrix.org) and let us know if you can help with translations and/or proofreading.
|
||||
Your language is not there? Request it on our Crowdin page 😊.
|
||||
|
||||
## Creating an Issue
|
||||
|
||||
@@ -35,14 +35,10 @@ All commit messages must adhere to the following format:
|
||||
|
||||
`<gitmoji>(type) title description`
|
||||
|
||||
* <**gitmoji**>: Use a gitmoji to represent the purpose of the commit. For example, ✨ for adding a new feature or 🔥 for removing something, see the list [here](https://gitmoji.dev/).
|
||||
* <**gitmoji**>: Use a gitmoji to represent the purpose of the commit. For example, ✨ for adding a new feature or 🔥 for removing something, see the list here: <https://gitmoji.dev/>.
|
||||
* **(type)**: Describe the type of change. Common types include `backend`, `frontend`, `CI`, `docker` etc...
|
||||
* **title**: A short, descriptive title for the change (*)
|
||||
* **blank line after the commit title
|
||||
* **description**: Include additional details on why you made the changes (**).
|
||||
|
||||
(*) ⚠️ **Make sure you add no space between the emoji and the (type) but add a space after the closing parenthesis of the type and use no caps!**
|
||||
(**) ⚠️ **Commit description message is mandatory and shouldn't be too long**
|
||||
* **title**: A short, descriptive title for the change.
|
||||
* **description**: Include additional details about what was changed and why.
|
||||
|
||||
### Example Commit Message
|
||||
|
||||
@@ -52,6 +48,10 @@ 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.
|
||||
@@ -70,9 +70,7 @@ Please add a line to the changelog describing your development. The changelog en
|
||||
It is nice to add information about the purpose of the pull request to help reviewers understand the context and intent of the changes. If you can, add some pictures or a small video to show the changes.
|
||||
|
||||
### Don't forget to:
|
||||
- signoff your commits
|
||||
- sign your commits with your key (SSH, GPG etc.)
|
||||
- check your commits (see warnings above)
|
||||
- check your commits
|
||||
- check the linting: `make lint && make frontend-lint`
|
||||
- check the tests: `make test`
|
||||
- add a changelog entry
|
||||
@@ -92,11 +90,3 @@ Make sure that all new features or fixes have corresponding tests. Run the test
|
||||
If you need any help while contributing, feel free to open a discussion or ask for guidance in the issue tracker. We are more than happy to assist!
|
||||
|
||||
Thank you for your contributions! 👍
|
||||
|
||||
## Contribute to BlockNote
|
||||
We use [BlockNote](https://www.blocknotejs.org/) for the text editing features of Docs.
|
||||
If you find and issue with the editor you can [report it](https://github.com/TypeCellOS/BlockNote/issues) directly on their repository.
|
||||
|
||||
Please consider contributing to BlockNotejs, as a library, it's useful to many projects not just Docs.
|
||||
|
||||
The project is licended with Mozilla Public License Version 2.0 but be aware that [XL packages](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-docx-exporter/LICENSE) are dual licenced with GNU AFFERO GENERAL PUBLIC LICENCE Version 3 and proprietary licence if you are [sponsor](https://www.blocknotejs.org/pricing).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Django impress
|
||||
|
||||
# ---- base image to inherit from ----
|
||||
FROM python:3.12.10-alpine AS base
|
||||
FROM python:3.12.6-alpine3.20 AS base
|
||||
|
||||
# Upgrade pip to its latest release to speed up dependencies installation
|
||||
RUN python -m pip install --upgrade pip setuptools
|
||||
@@ -30,13 +30,12 @@ RUN mkdir /install && \
|
||||
|
||||
|
||||
# ---- mails ----
|
||||
FROM node:24-alpine AS mail-builder
|
||||
FROM node:20 AS mail-builder
|
||||
|
||||
COPY ./src/mail /mail/app
|
||||
|
||||
WORKDIR /mail/app
|
||||
|
||||
RUN apk update && apk add --no-cache bash
|
||||
RUN yarn install --frozen-lockfile && \
|
||||
yarn build
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ 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
|
||||
@@ -37,13 +39,11 @@ 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 05/2025`
|
||||
* 📚 Built-in wiki functionality to turn your team's collaborative work into organized knowledge `ETA 02/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,7 +118,6 @@ $ make run-backend
|
||||
```
|
||||
|
||||
**Adding content**
|
||||
|
||||
You can create a basic demo site by running:
|
||||
|
||||
```shellscript
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
Security is very important to us.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
We appreciate your effort to make Docs more secure.
|
||||
|
||||
|
||||
@@ -155,7 +155,8 @@ services:
|
||||
target: frontend-production
|
||||
args:
|
||||
API_ORIGIN: "http://localhost:8071"
|
||||
PUBLISH_AS_MIT: "false"
|
||||
Y_PROVIDER_URL: "ws://localhost:4444"
|
||||
MEDIA_URL: "http://localhost:8083"
|
||||
SW_DEACTIVATED: "true"
|
||||
image: impress:frontend-development
|
||||
ports:
|
||||
@@ -184,11 +185,15 @@ 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
|
||||
|
||||
34
docs/env.md
34
docs/env.md
@@ -4,7 +4,7 @@ Here we describe all environment variables that can be set for the docs applicat
|
||||
|
||||
## impress-backend container
|
||||
|
||||
These are the environment variables you can set for the `impress-backend` container.
|
||||
These are the environmental variables you can set for the impress-backend container.
|
||||
|
||||
| Option | Description | default |
|
||||
| ----------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
@@ -39,7 +39,7 @@ These are the environment 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 address used as sender | from@example.com |
|
||||
| DJANGO_EMAIL_FROM | email adress 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 | [] |
|
||||
@@ -49,6 +49,9 @@ These are the environment variables you can set for the `impress-backend` contai
|
||||
| COLLABORATION_WS_URL | collaboration websocket url | |
|
||||
| FRONTEND_CSS_URL | To add a external css file to the app | |
|
||||
| FRONTEND_HOMEPAGE_FEATURE_ENABLED | frontend feature flag to display the homepage | false |
|
||||
| FRONTEND_FOOTER_FEATURE_ENABLED | frontend feature flag to display the footer | false |
|
||||
| FRONTEND_FOOTER_VIEW_CACHE_TIMEOUT | Cache duration of the json footer | 86400 |
|
||||
| FRONTEND_URL_JSON_FOOTER | Url with a json to configure the footer | |
|
||||
| FRONTEND_THEME | frontend theme to use | |
|
||||
| POSTHOG_KEY | posthog key for analytics | |
|
||||
| CRISP_WEBSITE_ID | crisp website id for support | |
|
||||
@@ -59,11 +62,11 @@ These are the environment 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 | Authorization endpoint for OIDC | |
|
||||
| OIDC_OP_AUTHORIZATION_ENDPOINT | Autorization 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 parameters | {} |
|
||||
| OIDC_AUTH_REQUEST_EXTRA_PARAMS | OIDC extra auth paramaters | {} |
|
||||
| OIDC_RP_SCOPES | scopes requested for OIDC | openid email |
|
||||
| LOGIN_REDIRECT_URL | login redirect url | |
|
||||
| LOGIN_REDIRECT_URL_FAILURE | login redirect url on failure | |
|
||||
@@ -73,16 +76,15 @@ These are the environment 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 duplicate emails | false |
|
||||
| OIDC_ALLOW_DUPLICATE_EMAILS | Allow dupplicate emails | false |
|
||||
| USER_OIDC_ESSENTIAL_CLAIMS | essential claims in OIDC token | [] |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
@@ -95,19 +97,3 @@ These are the environment variables you can set for the `impress-backend` contai
|
||||
| DJANGO_CSRF_TRUSTED_ORIGINS | CSRF trusted origins | [] |
|
||||
| REDIS_URL | cache url | redis://redis:6379/1 |
|
||||
| CACHES_DEFAULT_TIMEOUT | cache default timeout | 30 |
|
||||
| CACHES_KEY_PREFIX | The prefix used to every cache keys. | docs |
|
||||
| MALWARE_DETECTION_BACKEND | The malware detection backend use from the django-lasuite package | lasuite.malware_detection.backends.dummy.DummyBackend |
|
||||
| MALWARE_DETECTION_PARAMETERS | A dict containing all the parameters to initiate the malware detection backend | {"callback_path": "core.malware_detection.malware_detection_callback",} |
|
||||
| THEME_CUSTOMIZATION_FILE_PATH | full path to the file customizing the theme. An example is provided in src/backend/impress/configuration/theme/default.json | BASE_DIR/impress/configuration/theme/default.json |
|
||||
| THEME_CUSTOMIZATION_CACHE_TIMEOUT | Cache duration for the customization settings | 86400 |
|
||||
|
||||
|
||||
## impress-frontend image
|
||||
|
||||
These are the environment variables you can set to build the `impress-frontend` image.
|
||||
|
||||
| Option | Description | default |
|
||||
| ----------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| API_ORIGIN | backend domain - it uses the current domain if not initialized | |
|
||||
| SW_DEACTIVATED | To not install the service worker | |
|
||||
| PUBLISH_AS_MIT | MIT licence does not include the export feature | true |
|
||||
@@ -33,8 +33,8 @@ backend:
|
||||
OIDC_RP_SIGN_ALGO: RS256
|
||||
OIDC_RP_SCOPES: "openid email"
|
||||
OIDC_VERIFY_SSL: False
|
||||
OIDC_USERINFO_SHORTNAME_FIELD: "given_name"
|
||||
OIDC_USERINFO_FULLNAME_FIELDS: "given_name,usual_name"
|
||||
USER_OIDC_FIELD_TO_SHORTNAME: "given_name"
|
||||
USER_OIDC_FIELDS_TO_FULLNAME: "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
|
||||
|
||||
# Extra volume to manage our local custom CA and avoid to set ssl_verify: false
|
||||
# Exra 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
|
||||
|
||||
# Extra volume to manage our local custom CA and avoid to set ssl_verify: false
|
||||
# Exra volume to manage our local custom CA and avoid to set ssl_verify: false
|
||||
extraVolumes:
|
||||
- name: certs
|
||||
configMap:
|
||||
|
||||
@@ -133,7 +133,7 @@ OIDC_RP_SCOPES: "openid email"
|
||||
|
||||
You can find these values in **examples/keycloak.values.yaml**
|
||||
|
||||
### Find redis server connection values
|
||||
### Find redis server connexion 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 connection values
|
||||
### Find postgresql connexion 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 connection values
|
||||
### Find s3 bucket connexion 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 information 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 informations to the helm chart.
|
||||
|
||||
```
|
||||
$ helm repo add impress https://suitenumerique.github.io/docs/
|
||||
|
||||
@@ -64,3 +64,6 @@ 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
|
||||
|
||||
@@ -4,4 +4,3 @@ BURST_THROTTLE_RATES="200/minute"
|
||||
DJANGO_SERVER_TO_SERVER_API_TOKENS=test-e2e
|
||||
Y_PROVIDER_API_KEY=yprovider-api-key
|
||||
Y_PROVIDER_API_BASE_URL=http://y-provider:4444/api/
|
||||
THEME_CUSTOMIZATION_FILE_PATH="" #force theme_customization to be empty
|
||||
@@ -9,18 +9,6 @@
|
||||
"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",
|
||||
@@ -28,7 +16,6 @@
|
||||
"matchPackageNames": [
|
||||
"eslint",
|
||||
"fetch-mock",
|
||||
"prosemirror-model",
|
||||
"node",
|
||||
"node-fetch",
|
||||
"workbox-webpack-plugin"
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""API filters for Impress' core application."""
|
||||
|
||||
import unicodedata
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import django_filters
|
||||
@@ -9,42 +7,13 @@ import django_filters
|
||||
from core import models
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
A custom CharFilter that filters on the accent-insensitive value searched.
|
||||
"""
|
||||
|
||||
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).
|
||||
Custom filter for filtering documents.
|
||||
"""
|
||||
|
||||
title = AccentInsensitiveCharFilter(
|
||||
field_name="title", lookup_expr="unaccent__icontains", label=_("Title")
|
||||
title = django_filters.CharFilter(
|
||||
field_name="title", lookup_expr="icontains", label=_("Title")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""API endpoints"""
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
from urllib.parse import unquote, urlparse
|
||||
@@ -10,21 +9,21 @@ from django.conf import settings
|
||||
from django.contrib.postgres.aggregates import ArrayAgg
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.contrib.postgres.search import TrigramSimilarity
|
||||
from django.core.cache import cache
|
||||
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
|
||||
from django.utils.text import capfirst, slugify
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.decorators.cache import cache_page
|
||||
|
||||
import requests
|
||||
import rest_framework as drf
|
||||
from botocore.exceptions import ClientError
|
||||
from lasuite.malware_detection import malware_detection
|
||||
from rest_framework import filters, status, viewsets
|
||||
from rest_framework import response as drf_response
|
||||
from rest_framework.permissions import AllowAny
|
||||
@@ -33,6 +32,7 @@ from rest_framework.throttling import UserRateThrottle
|
||||
from core import authentication, enums, models
|
||||
from core.services.ai_services import AIService
|
||||
from core.services.collaboration_services import CollaborationService
|
||||
from core.services.config_services import get_footer_json
|
||||
from core.utils import extract_attachments, filter_descendants
|
||||
|
||||
from . import permissions, serializers, utils
|
||||
@@ -576,7 +576,7 @@ class DocumentViewSet(
|
||||
queryset, filter_data["is_favorite"]
|
||||
)
|
||||
|
||||
# Apply ordering only now that everything is filtered and annotated
|
||||
# Apply ordering only now that everyting is filtered and annotated
|
||||
queryset = filters.OrderingFilter().filter_queryset(
|
||||
self.request, queryset, self
|
||||
)
|
||||
@@ -607,14 +607,6 @@ 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,
|
||||
@@ -674,19 +666,10 @@ 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():
|
||||
@@ -792,12 +775,7 @@ class DocumentViewSet(
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
with transaction.atomic():
|
||||
# "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(
|
||||
child_document = document.add_child(
|
||||
creator=request.user,
|
||||
**serializer.validated_data,
|
||||
)
|
||||
@@ -889,7 +867,7 @@ class DocumentViewSet(
|
||||
)
|
||||
|
||||
# Compute cache for ancestors links to avoid many queries while computing
|
||||
# abilities for his documents in the tree!
|
||||
# abilties for his documents in the tree!
|
||||
ancestors_links.append(
|
||||
{"link_reach": ancestor.link_reach, "link_role": ancestor.link_role}
|
||||
)
|
||||
@@ -1156,10 +1134,7 @@ class DocumentViewSet(
|
||||
|
||||
# Prepare metadata for storage
|
||||
extra_args = {
|
||||
"Metadata": {
|
||||
"owner": str(request.user.id),
|
||||
"status": enums.DocumentAttachmentStatus.PROCESSING,
|
||||
},
|
||||
"Metadata": {"owner": str(request.user.id)},
|
||||
"ContentType": serializer.validated_data["content_type"],
|
||||
}
|
||||
file_unsafe = ""
|
||||
@@ -1191,8 +1166,6 @@ class DocumentViewSet(
|
||||
document.attachments.append(key)
|
||||
document.save()
|
||||
|
||||
malware_detection.analyse_file(key, document_id=document.id)
|
||||
|
||||
return drf.response.Response(
|
||||
{"file": f"{settings.MEDIA_URL:s}{key:s}"},
|
||||
status=drf.status.HTTP_201_CREATED,
|
||||
@@ -1276,19 +1249,6 @@ class DocumentViewSet(
|
||||
logger.debug("User '%s' lacks permission for attachment", user)
|
||||
raise drf.exceptions.PermissionDenied()
|
||||
|
||||
# Check if the attachment is ready
|
||||
s3_client = default_storage.connection.meta.client
|
||||
bucket_name = default_storage.bucket_name
|
||||
head_resp = s3_client.head_object(Bucket=bucket_name, Key=key)
|
||||
metadata = head_resp.get("Metadata", {})
|
||||
# In order to be compatible with existing upload without `status` metadata,
|
||||
# we consider them as ready.
|
||||
if (
|
||||
metadata.get("status", enums.DocumentAttachmentStatus.READY)
|
||||
!= enums.DocumentAttachmentStatus.READY
|
||||
):
|
||||
raise drf.exceptions.PermissionDenied()
|
||||
|
||||
# Generate S3 authorization headers using the extracted URL parameters
|
||||
request = utils.generate_s3_authorization_headers(key)
|
||||
|
||||
@@ -1733,6 +1693,7 @@ class ConfigView(drf.views.APIView):
|
||||
"ENVIRONMENT",
|
||||
"FRONTEND_CSS_URL",
|
||||
"FRONTEND_HOMEPAGE_FEATURE_ENABLED",
|
||||
"FRONTEND_FOOTER_FEATURE_ENABLED",
|
||||
"FRONTEND_THEME",
|
||||
"MEDIA_BASE_URL",
|
||||
"POSTHOG_KEY",
|
||||
@@ -1745,41 +1706,23 @@ class ConfigView(drf.views.APIView):
|
||||
if hasattr(settings, setting):
|
||||
dict_settings[setting] = getattr(settings, setting)
|
||||
|
||||
dict_settings["theme_customization"] = self._load_theme_customization()
|
||||
|
||||
return drf.response.Response(dict_settings)
|
||||
|
||||
def _load_theme_customization(self):
|
||||
if not settings.THEME_CUSTOMIZATION_FILE_PATH:
|
||||
return {}
|
||||
|
||||
cache_key = (
|
||||
f"theme_customization_{slugify(settings.THEME_CUSTOMIZATION_FILE_PATH)}"
|
||||
class FooterView(drf.views.APIView):
|
||||
"""API ViewSet for sharing the footer JSON."""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@method_decorator(cache_page(settings.FRONTEND_FOOTER_VIEW_CACHE_TIMEOUT))
|
||||
def get(self, request):
|
||||
"""
|
||||
GET /api/v1.0/footer/
|
||||
Return the footer JSON.
|
||||
"""
|
||||
json_footer = (
|
||||
get_footer_json(settings.FRONTEND_URL_JSON_FOOTER)
|
||||
if settings.FRONTEND_URL_JSON_FOOTER
|
||||
else {}
|
||||
)
|
||||
theme_customization = cache.get(cache_key, {})
|
||||
if theme_customization:
|
||||
return theme_customization
|
||||
|
||||
try:
|
||||
with open(
|
||||
settings.THEME_CUSTOMIZATION_FILE_PATH, "r", encoding="utf-8"
|
||||
) as f:
|
||||
theme_customization = json.load(f)
|
||||
except FileNotFoundError:
|
||||
logger.error(
|
||||
"Configuration file not found: %s",
|
||||
settings.THEME_CUSTOMIZATION_FILE_PATH,
|
||||
)
|
||||
except json.JSONDecodeError:
|
||||
logger.error(
|
||||
"Configuration file is not a valid JSON: %s",
|
||||
settings.THEME_CUSTOMIZATION_FILE_PATH,
|
||||
)
|
||||
else:
|
||||
cache.set(
|
||||
cache_key,
|
||||
theme_customization,
|
||||
settings.THEME_CUSTOMIZATION_CACHE_TIMEOUT,
|
||||
)
|
||||
|
||||
return theme_customization
|
||||
return drf.response.Response(json_footer)
|
||||
|
||||
@@ -1,59 +1,130 @@
|
||||
"""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 _
|
||||
|
||||
from lasuite.oidc_login.backends import (
|
||||
OIDCAuthenticationBackend as LaSuiteOIDCAuthenticationBackend,
|
||||
import requests
|
||||
from mozilla_django_oidc.auth import (
|
||||
OIDCAuthenticationBackend as MozillaOIDCAuthenticationBackend,
|
||||
)
|
||||
|
||||
from core.models import DuplicateEmailError
|
||||
from core.models import DuplicateEmailError, User
|
||||
|
||||
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."
|
||||
)
|
||||
|
||||
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):
|
||||
class OIDCAuthenticationBackend(MozillaOIDCAuthenticationBackend):
|
||||
"""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_extra_claims(self, user_info):
|
||||
"""
|
||||
Return extra claims from user_info.
|
||||
def get_userinfo(self, access_token, id_token, payload):
|
||||
"""Return user details dictionary.
|
||||
|
||||
Args:
|
||||
user_info (dict): The user information dictionary.
|
||||
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.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary of extra claims.
|
||||
- dict: User details dictionary obtained from the OpenID Connect user endpoint.
|
||||
"""
|
||||
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."""
|
||||
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:
|
||||
return self.UserModel.objects.get_user_by_sub_or_email(sub, email)
|
||||
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,
|
||||
}
|
||||
|
||||
try:
|
||||
user = User.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)
|
||||
|
||||
18
src/backend/core/authentication/urls.py
Normal file
18
src/backend/core/authentication/urls.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""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,
|
||||
]
|
||||
137
src/backend/core/authentication/views.py
Normal file
137
src/backend/core/authentication/views.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""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)
|
||||
@@ -3,7 +3,6 @@ Core application enums declaration
|
||||
"""
|
||||
|
||||
import re
|
||||
from enum import StrEnum
|
||||
|
||||
from django.conf import global_settings, settings
|
||||
from django.db import models
|
||||
@@ -39,10 +38,3 @@ class MoveNodePositionChoices(models.TextChoices):
|
||||
LAST_SIBLING = "last-sibling", _("Last sibling")
|
||||
LEFT = "left", _("Left")
|
||||
RIGHT = "right", _("Right")
|
||||
|
||||
|
||||
class DocumentAttachmentStatus(StrEnum):
|
||||
"""Defines the possible statuses for an attachment."""
|
||||
|
||||
PROCESSING = "processing"
|
||||
READY = "ready"
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
"""Malware detection callbacks"""
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.files.storage import default_storage
|
||||
|
||||
from lasuite.malware_detection.enums import ReportStatus
|
||||
|
||||
from core.enums import DocumentAttachmentStatus
|
||||
from core.models import Document
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
security_logger = logging.getLogger("docs.security")
|
||||
|
||||
|
||||
def malware_detection_callback(file_path, status, error_info, **kwargs):
|
||||
"""Malware detection callback"""
|
||||
|
||||
if status == ReportStatus.SAFE:
|
||||
logger.info("File %s is safe", file_path)
|
||||
# Get existing metadata
|
||||
s3_client = default_storage.connection.meta.client
|
||||
bucket_name = default_storage.bucket_name
|
||||
head_resp = s3_client.head_object(Bucket=bucket_name, Key=file_path)
|
||||
metadata = head_resp.get("Metadata", {})
|
||||
metadata.update({"status": DocumentAttachmentStatus.READY})
|
||||
# Update status in metadata
|
||||
s3_client.copy_object(
|
||||
Bucket=bucket_name,
|
||||
CopySource={"Bucket": bucket_name, "Key": file_path},
|
||||
Key=file_path,
|
||||
ContentType=head_resp.get("ContentType"),
|
||||
Metadata=metadata,
|
||||
MetadataDirective="REPLACE",
|
||||
)
|
||||
return
|
||||
|
||||
document_id = kwargs.get("document_id")
|
||||
security_logger.warning(
|
||||
"File %s for document %s is infected with malware. Error info: %s",
|
||||
file_path,
|
||||
document_id,
|
||||
error_info,
|
||||
)
|
||||
|
||||
# Remove the file from the document and change the status to unsafe
|
||||
document = Document.objects.get(pk=document_id)
|
||||
document.attachments.remove(file_path)
|
||||
document.save(update_fields=["attachments"])
|
||||
|
||||
# Delete the file from the storage
|
||||
default_storage.delete(file_path)
|
||||
@@ -1,10 +0,0 @@
|
||||
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()]
|
||||
@@ -44,7 +44,7 @@ AI_ACTIONS = {
|
||||
}
|
||||
|
||||
AI_TRANSLATE = (
|
||||
"Keep the same html structure and formatting. "
|
||||
"Keep the same html stucture 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."
|
||||
|
||||
@@ -17,7 +17,7 @@ class CollaborationService:
|
||||
def reset_connections(self, room, user_id=None):
|
||||
"""
|
||||
Reset connections of a room in the collaboration server.
|
||||
Resetting a connection means that the user will be disconnected and will
|
||||
Reseting a connection means that the user will be disconnected and will
|
||||
have to reconnect to the collaboration server, with updated rights.
|
||||
"""
|
||||
endpoint = "reset-connections"
|
||||
|
||||
25
src/backend/core/services/config_services.py
Normal file
25
src/backend/core/services/config_services.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""Config services."""
|
||||
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_footer_json(footer_json_url: str) -> dict:
|
||||
"""
|
||||
Fetches the footer JSON from the given URL."
|
||||
"""
|
||||
try:
|
||||
response = requests.get(
|
||||
footer_json_url, timeout=5, headers={"User-Agent": "Docs-Application"}
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
footer_json = response.json()
|
||||
|
||||
return footer_json
|
||||
except (requests.RequestException, ValueError) as e:
|
||||
logger.error("Failed to fetch footer JSON: %s", e)
|
||||
return {}
|
||||
@@ -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(3): # user by sub, user by mail, update sub
|
||||
with django_assert_num_queries(2):
|
||||
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.has_usable_password() is False
|
||||
assert user.password == "!"
|
||||
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.has_usable_password() is False
|
||||
assert user.password == "!"
|
||||
assert models.User.objects.count() == 1
|
||||
|
||||
|
||||
@@ -345,15 +345,11 @@ 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, settings):
|
||||
def test_authentication_get_userinfo_token_response(monkeypatch):
|
||||
"""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,
|
||||
content_type="application/jwt",
|
||||
responses.GET, re.compile(r".*/userinfo"), body="fake.jwt.token", status=200
|
||||
)
|
||||
|
||||
def mock_verify_token(self, token): # pylint: disable=unused-argument
|
||||
@@ -375,25 +371,21 @@ def test_authentication_get_userinfo_token_response(monkeypatch, settings):
|
||||
|
||||
@override_settings(OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo")
|
||||
@responses.activate
|
||||
def test_authentication_get_userinfo_invalid_response(settings):
|
||||
def test_authentication_get_userinfo_invalid_response():
|
||||
"""
|
||||
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,
|
||||
content_type="application/jwt",
|
||||
responses.GET, re.compile(r".*/userinfo"), body="fake.jwt.token", status=200
|
||||
)
|
||||
|
||||
oidc_backend = OIDCAuthenticationBackend()
|
||||
|
||||
with pytest.raises(
|
||||
SuspiciousOperation,
|
||||
match="User info response was not valid JWT",
|
||||
match="Invalid response format or token verification failed",
|
||||
):
|
||||
oidc_backend.get_userinfo("fake_access_token", None, None)
|
||||
|
||||
@@ -458,54 +450,100 @@ def test_authentication_getter_existing_disabled_user_via_email(
|
||||
assert models.User.objects.count() == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_authentication_session_tokens(
|
||||
django_assert_num_queries, monkeypatch, rf, settings
|
||||
# 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,
|
||||
):
|
||||
"""
|
||||
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()
|
||||
"""Ensure SuspiciousOperation is raised if essential claims are missing."""
|
||||
|
||||
klass = OIDCAuthenticationBackend()
|
||||
request = rf.get("/some-url", {"state": "test-state", "code": "test-code"})
|
||||
request.session = {}
|
||||
|
||||
def verify_token_mocked(*args, **kwargs):
|
||||
return {"sub": "123", "email": "test@example.com"}
|
||||
def get_userinfo_mocked(*args):
|
||||
return {
|
||||
"sub": "123",
|
||||
"last_name": "Doe",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(OIDCAuthenticationBackend, "verify_token", verify_token_mocked)
|
||||
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
|
||||
|
||||
responses.add(
|
||||
responses.POST,
|
||||
re.compile(settings.OIDC_OP_TOKEN_ENDPOINT),
|
||||
json={
|
||||
"access_token": "test-access-token",
|
||||
"refresh_token": "test-refresh-token",
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
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.GET,
|
||||
re.compile(settings.OIDC_OP_USER_ENDPOINT),
|
||||
json={"sub": "123", "email": "test@example.com"},
|
||||
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)
|
||||
|
||||
with django_assert_num_queries(6):
|
||||
user = klass.authenticate(
|
||||
request,
|
||||
code="test-code",
|
||||
nonce="test-nonce",
|
||||
code_verifier="test-code-verifier",
|
||||
user = klass.get_or_create_user(
|
||||
access_token="test-token", id_token=None, payload=None
|
||||
)
|
||||
|
||||
assert user is not None
|
||||
assert request.session["oidc_access_token"] == "test-access-token"
|
||||
assert get_oidc_refresh_token(request.session) == "test-refresh-token"
|
||||
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"
|
||||
|
||||
10
src/backend/core/tests/authentication/test_urls.py
Normal file
10
src/backend/core/tests/authentication/test_urls.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""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")
|
||||
231
src/backend/core/tests/authentication/test_views.py
Normal file
231
src/backend/core/tests/authentication/test_views.py
Normal file
@@ -0,0 +1,231 @@
|
||||
"""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"
|
||||
@@ -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()
|
||||
|
||||
@@ -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 existing identity in the db
|
||||
# Build an invitation to the email of an exising identity in the db
|
||||
invitation_values = {
|
||||
"email": existing_user.email,
|
||||
"role": random.choice(models.RoleChoices.values),
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
Authenticated who are not related to a document should be able to request AI transform
|
||||
Autenticated 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()
|
||||
|
||||
@@ -99,7 +99,7 @@ def test_api_documents_ai_translate_anonymous_success(mock_create):
|
||||
{
|
||||
"role": "system",
|
||||
"content": (
|
||||
"Keep the same html structure and formatting. "
|
||||
"Keep the same html stucture 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):
|
||||
"""
|
||||
Authenticated who are not related to a document should be able to request AI translate
|
||||
Autenticated 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 structure and formatting. "
|
||||
"Keep the same html stucture 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 structure and formatting. "
|
||||
"Keep the same html stucture and formatting. "
|
||||
"Translate the content in the html to the "
|
||||
"specified language Colombian Spanish. "
|
||||
"Check the translation for accuracy and make any necessary corrections. "
|
||||
|
||||
@@ -4,7 +4,6 @@ Test file uploads API endpoint for users in impress's core app.
|
||||
|
||||
import re
|
||||
import uuid
|
||||
from unittest import mock
|
||||
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
@@ -13,7 +12,6 @@ import pytest
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core import factories
|
||||
from core.api.viewsets import malware_detection
|
||||
from core.tests.conftest import TEAM, USER, VIA
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
@@ -61,8 +59,7 @@ def test_api_documents_attachment_upload_anonymous_success():
|
||||
file = SimpleUploadedFile(name="test.png", content=PIXEL, content_type="image/png")
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/attachment-upload/"
|
||||
with mock.patch.object(malware_detection, "analyse_file") as mock_analyse_file:
|
||||
response = APIClient().post(url, {"file": file}, format="multipart")
|
||||
response = APIClient().post(url, {"file": file}, format="multipart")
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
@@ -77,13 +74,12 @@ def test_api_documents_attachment_upload_anonymous_success():
|
||||
assert document.attachments == [f"{document.id!s}/attachments/{file_id!s}.png"]
|
||||
|
||||
# Now, check the metadata of the uploaded file
|
||||
key = file_path.replace("/media/", "")
|
||||
mock_analyse_file.assert_called_once_with(key, document_id=document.id)
|
||||
key = file_path.replace("/media", "")
|
||||
file_head = default_storage.connection.meta.client.head_object(
|
||||
Bucket=default_storage.bucket_name, Key=key
|
||||
)
|
||||
|
||||
assert file_head["Metadata"] == {"owner": "None", "status": "processing"}
|
||||
assert file_head["Metadata"] == {"owner": "None"}
|
||||
assert file_head["ContentType"] == "image/png"
|
||||
assert file_head["ContentDisposition"] == 'inline; filename="test.png"'
|
||||
|
||||
@@ -131,7 +127,7 @@ def test_api_documents_attachment_upload_authenticated_forbidden(reach, role):
|
||||
)
|
||||
def test_api_documents_attachment_upload_authenticated_success(reach, role):
|
||||
"""
|
||||
Authenticated users who are not related to a document should be able to upload
|
||||
Autenticated 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()
|
||||
@@ -143,8 +139,7 @@ def test_api_documents_attachment_upload_authenticated_success(reach, role):
|
||||
file = SimpleUploadedFile(name="test.png", content=PIXEL, content_type="image/png")
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/attachment-upload/"
|
||||
with mock.patch.object(malware_detection, "analyse_file") as mock_analyse_file:
|
||||
response = client.post(url, {"file": file}, format="multipart")
|
||||
response = client.post(url, {"file": file}, format="multipart")
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
@@ -152,10 +147,6 @@ def test_api_documents_attachment_upload_authenticated_success(reach, role):
|
||||
match = pattern.search(response.json()["file"])
|
||||
file_id = match.group(1)
|
||||
|
||||
mock_analyse_file.assert_called_once_with(
|
||||
f"{document.id!s}/attachments/{file_id!s}.png", document_id=document.id
|
||||
)
|
||||
|
||||
# Validate that file_id is a valid UUID
|
||||
uuid.UUID(file_id)
|
||||
|
||||
@@ -219,8 +210,7 @@ def test_api_documents_attachment_upload_success(via, role, mock_user_teams):
|
||||
file = SimpleUploadedFile(name="test.png", content=PIXEL, content_type="image/png")
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/attachment-upload/"
|
||||
with mock.patch.object(malware_detection, "analyse_file") as mock_analyse_file:
|
||||
response = client.post(url, {"file": file}, format="multipart")
|
||||
response = client.post(url, {"file": file}, format="multipart")
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
@@ -236,12 +226,11 @@ def test_api_documents_attachment_upload_success(via, role, mock_user_teams):
|
||||
assert document.attachments == [f"{document.id!s}/attachments/{file_id!s}.png"]
|
||||
|
||||
# Now, check the metadata of the uploaded file
|
||||
key = file_path.replace("/media/", "")
|
||||
mock_analyse_file.assert_called_once_with(key, document_id=document.id)
|
||||
key = file_path.replace("/media", "")
|
||||
file_head = default_storage.connection.meta.client.head_object(
|
||||
Bucket=default_storage.bucket_name, Key=key
|
||||
)
|
||||
assert file_head["Metadata"] == {"owner": str(user.id), "status": "processing"}
|
||||
assert file_head["Metadata"] == {"owner": str(user.id)}
|
||||
assert file_head["ContentType"] == "image/png"
|
||||
assert file_head["ContentDisposition"] == 'inline; filename="test.png"'
|
||||
|
||||
@@ -266,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 exceed the maximum size in settings."""
|
||||
"""The uploaded file should not exceeed the maximum size in settings."""
|
||||
settings.DOCUMENT_IMAGE_MAX_SIZE = 1048576 # 1 MB for test
|
||||
|
||||
user = factories.UserFactory()
|
||||
@@ -315,8 +304,7 @@ def test_api_documents_attachment_upload_fix_extension(
|
||||
url = f"/api/v1.0/documents/{document.id!s}/attachment-upload/"
|
||||
|
||||
file = SimpleUploadedFile(name=name, content=content)
|
||||
with mock.patch.object(malware_detection, "analyse_file") as mock_analyse_file:
|
||||
response = client.post(url, {"file": file}, format="multipart")
|
||||
response = client.post(url, {"file": file}, format="multipart")
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
@@ -336,16 +324,11 @@ def test_api_documents_attachment_upload_fix_extension(
|
||||
uuid.UUID(file_id)
|
||||
|
||||
# Now, check the metadata of the uploaded file
|
||||
key = file_path.replace("/media/", "")
|
||||
mock_analyse_file.assert_called_once_with(key, document_id=document.id)
|
||||
key = file_path.replace("/media", "")
|
||||
file_head = default_storage.connection.meta.client.head_object(
|
||||
Bucket=default_storage.bucket_name, Key=key
|
||||
)
|
||||
assert file_head["Metadata"] == {
|
||||
"owner": str(user.id),
|
||||
"is_unsafe": "true",
|
||||
"status": "processing",
|
||||
}
|
||||
assert file_head["Metadata"] == {"owner": str(user.id), "is_unsafe": "true"}
|
||||
assert file_head["ContentType"] == content_type
|
||||
assert file_head["ContentDisposition"] == f'attachment; filename="{name:s}"'
|
||||
|
||||
@@ -381,8 +364,7 @@ def test_api_documents_attachment_upload_unsafe():
|
||||
file = SimpleUploadedFile(
|
||||
name="script.exe", content=b"\x4d\x5a\x90\x00\x03\x00\x00\x00"
|
||||
)
|
||||
with mock.patch.object(malware_detection, "analyse_file") as mock_analyse_file:
|
||||
response = client.post(url, {"file": file}, format="multipart")
|
||||
response = client.post(url, {"file": file}, format="multipart")
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
@@ -399,16 +381,11 @@ def test_api_documents_attachment_upload_unsafe():
|
||||
file_id = file_id.replace("-unsafe", "")
|
||||
uuid.UUID(file_id)
|
||||
|
||||
key = file_path.replace("/media/", "")
|
||||
mock_analyse_file.assert_called_once_with(key, document_id=document.id)
|
||||
# Now, check the metadata of the uploaded file
|
||||
key = file_path.replace("/media", "")
|
||||
file_head = default_storage.connection.meta.client.head_object(
|
||||
Bucket=default_storage.bucket_name, Key=key
|
||||
)
|
||||
assert file_head["Metadata"] == {
|
||||
"owner": str(user.id),
|
||||
"is_unsafe": "true",
|
||||
"status": "processing",
|
||||
}
|
||||
assert file_head["Metadata"] == {"owner": str(user.id), "is_unsafe": "true"}
|
||||
assert file_head["ContentType"] == "application/octet-stream"
|
||||
assert file_head["ContentDisposition"] == 'attachment; filename="script.exe"'
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Tests for Documents API endpoint in impress's core app: children create
|
||||
"""
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
@@ -250,41 +249,3 @@ 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
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Tests for Documents API endpoint in impress's core app: create
|
||||
"""
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
@@ -52,36 +51,6 @@ 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()
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
@@ -279,7 +278,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 useful if an OIDC provider modifies
|
||||
happen in a healthy OIDC federation but can be usefull if an OIDC provider modifies
|
||||
users sub on each login for example...
|
||||
"""
|
||||
user = factories.UserFactory(language="en-us")
|
||||
@@ -426,36 +425,6 @@ 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(
|
||||
|
||||
@@ -7,7 +7,6 @@ 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
|
||||
@@ -50,16 +49,14 @@ def test_api_documents_descendants_filter_unknown_field():
|
||||
[
|
||||
("Project Alpha", 1), # Exact match
|
||||
("project", 2), # Partial match (case-insensitive)
|
||||
("Guide", 2), # Word match within a title
|
||||
("Guide", 1), # Word match within a title
|
||||
("Special", 0), # No match (nonexistent keyword)
|
||||
("2024", 2), # Match by numeric keyword
|
||||
("", 6), # Empty string
|
||||
("velo", 1), # Accent-insensitive match (velo vs vélo)
|
||||
("bêta", 1), # Accent-insensitive match (bêta vs beta)
|
||||
("", 5), # Empty string
|
||||
],
|
||||
)
|
||||
def test_api_documents_descendants_filter_title(query, nb_results):
|
||||
"""Authenticated users should be able to search documents by their unaccented title."""
|
||||
"""Authenticated users should be able to search documents by their title."""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
@@ -73,7 +70,6 @@ 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)
|
||||
@@ -89,7 +85,4 @@ def test_api_documents_descendants_filter_title(query, nb_results):
|
||||
|
||||
# Ensure all results contain the query in their title
|
||||
for result in results:
|
||||
assert (
|
||||
remove_accents(query).lower().strip()
|
||||
in remove_accents(result["title"]).lower()
|
||||
)
|
||||
assert query.lower().strip() in result["title"].lower()
|
||||
|
||||
@@ -15,7 +15,6 @@ import requests
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core import factories, models
|
||||
from core.enums import DocumentAttachmentStatus
|
||||
from core.tests.conftest import TEAM, USER, VIA
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
@@ -46,7 +45,6 @@ def test_api_documents_media_auth_anonymous_public():
|
||||
Key=key,
|
||||
Body=BytesIO(b"my prose"),
|
||||
ContentType="text/plain",
|
||||
Metadata={"status": DocumentAttachmentStatus.READY},
|
||||
)
|
||||
|
||||
factories.DocumentFactory(id=document_id, link_reach="public", attachments=[key])
|
||||
@@ -95,15 +93,7 @@ def test_api_documents_media_auth_extensions():
|
||||
keys = []
|
||||
for ext in extensions:
|
||||
filename = f"{uuid4()!s}.{ext:s}"
|
||||
key = f"{document_id!s}/attachments/{filename:s}"
|
||||
default_storage.connection.meta.client.put_object(
|
||||
Bucket=default_storage.bucket_name,
|
||||
Key=key,
|
||||
Body=BytesIO(b"my prose"),
|
||||
ContentType="text/plain",
|
||||
Metadata={"status": DocumentAttachmentStatus.READY},
|
||||
)
|
||||
keys.append(key)
|
||||
keys.append(f"{document_id!s}/attachments/{filename:s}")
|
||||
|
||||
factories.DocumentFactory(link_reach="public", attachments=keys)
|
||||
|
||||
@@ -152,7 +142,6 @@ def test_api_documents_media_auth_anonymous_attachments():
|
||||
Key=key,
|
||||
Body=BytesIO(b"my prose"),
|
||||
ContentType="text/plain",
|
||||
Metadata={"status": DocumentAttachmentStatus.READY},
|
||||
)
|
||||
|
||||
factories.DocumentFactory(id=document_id, link_reach="restricted")
|
||||
@@ -216,7 +205,6 @@ def test_api_documents_media_auth_authenticated_public_or_authenticated(reach):
|
||||
Key=key,
|
||||
Body=BytesIO(b"my prose"),
|
||||
ContentType="text/plain",
|
||||
Metadata={"status": DocumentAttachmentStatus.READY},
|
||||
)
|
||||
|
||||
factories.DocumentFactory(id=document_id, link_reach=reach, attachments=[key])
|
||||
@@ -295,7 +283,6 @@ def test_api_documents_media_auth_related(via, mock_user_teams):
|
||||
Key=key,
|
||||
Body=BytesIO(b"my prose"),
|
||||
ContentType="text/plain",
|
||||
Metadata={"status": DocumentAttachmentStatus.READY},
|
||||
)
|
||||
|
||||
document = factories.DocumentFactory(
|
||||
@@ -334,70 +321,3 @@ def test_api_documents_media_auth_related(via, mock_user_teams):
|
||||
timeout=1,
|
||||
)
|
||||
assert response.content.decode("utf-8") == "my prose"
|
||||
|
||||
|
||||
def test_api_documents_media_auth_not_ready_status():
|
||||
"""Attachments with status not ready should not be accessible"""
|
||||
document_id = uuid4()
|
||||
filename = f"{uuid4()!s}.jpg"
|
||||
key = f"{document_id!s}/attachments/{filename:s}"
|
||||
default_storage.connection.meta.client.put_object(
|
||||
Bucket=default_storage.bucket_name,
|
||||
Key=key,
|
||||
Body=BytesIO(b"my prose"),
|
||||
ContentType="text/plain",
|
||||
Metadata={"status": DocumentAttachmentStatus.PROCESSING},
|
||||
)
|
||||
|
||||
factories.DocumentFactory(id=document_id, link_reach="public", attachments=[key])
|
||||
|
||||
original_url = f"http://localhost/media/{key:s}"
|
||||
response = APIClient().get(
|
||||
"/api/v1.0/documents/media-auth/", HTTP_X_ORIGINAL_URL=original_url
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_api_documents_media_auth_missing_status_metadata():
|
||||
"""Attachments without status metadata should be considered as ready"""
|
||||
document_id = uuid4()
|
||||
filename = f"{uuid4()!s}.jpg"
|
||||
key = f"{document_id!s}/attachments/{filename:s}"
|
||||
default_storage.connection.meta.client.put_object(
|
||||
Bucket=default_storage.bucket_name,
|
||||
Key=key,
|
||||
Body=BytesIO(b"my prose"),
|
||||
ContentType="text/plain",
|
||||
)
|
||||
|
||||
factories.DocumentFactory(id=document_id, link_reach="public", attachments=[key])
|
||||
|
||||
original_url = f"http://localhost/media/{key:s}"
|
||||
response = APIClient().get(
|
||||
"/api/v1.0/documents/media-auth/", HTTP_X_ORIGINAL_URL=original_url
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
authorization = response["Authorization"]
|
||||
assert "AWS4-HMAC-SHA256 Credential=" in authorization
|
||||
assert (
|
||||
"SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature="
|
||||
in authorization
|
||||
)
|
||||
assert response["X-Amz-Date"] == timezone.now().strftime("%Y%m%dT%H%M%SZ")
|
||||
|
||||
s3_url = urlparse(settings.AWS_S3_ENDPOINT_URL)
|
||||
file_url = f"{settings.AWS_S3_ENDPOINT_URL:s}/impress-media-storage/{key:s}"
|
||||
response = requests.get(
|
||||
file_url,
|
||||
headers={
|
||||
"authorization": authorization,
|
||||
"x-amz-date": response["x-amz-date"],
|
||||
"x-amz-content-sha256": response["x-amz-content-sha256"],
|
||||
"Host": f"{s3_url.hostname:s}:{s3_url.port:d}",
|
||||
},
|
||||
timeout=1,
|
||||
)
|
||||
assert response.content.decode("utf-8") == "my prose"
|
||||
|
||||
@@ -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 rights on its parent.
|
||||
if the user has no rigths on its parent.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
|
||||
@@ -31,7 +31,7 @@ def get_ydoc_with_mages(image_keys):
|
||||
def test_api_documents_update_new_attachment_keys_anonymous(django_assert_num_queries):
|
||||
"""
|
||||
When an anonymous user updates a document, the attachment keys extracted from the
|
||||
updated content should be added to the list of "attachments" to the document if these
|
||||
updated content should be added to the list of "attachments" ot the document if these
|
||||
attachments are already readable by anonymous users.
|
||||
"""
|
||||
image_keys = [f"{uuid4()!s}/attachments/{uuid4()!s}.png" for _ in range(4)]
|
||||
@@ -78,7 +78,7 @@ def test_api_documents_update_new_attachment_keys_authenticated(
|
||||
):
|
||||
"""
|
||||
When an authenticated user updates a document, the attachment keys extracted from the
|
||||
updated content should be added to the list of "attachments" to the document if these
|
||||
updated content should be added to the list of "attachments" ot the document if these
|
||||
attachments are already readable by the editing user.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
Test config API endpoints in the Impress core app.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from django.test import override_settings
|
||||
|
||||
import pytest
|
||||
@@ -18,15 +16,15 @@ pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@override_settings(
|
||||
AI_FEATURE_ENABLED=False,
|
||||
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/",
|
||||
POSTHOG_KEY={"id": "132456", "host": "https://eu.i.posthog-test.com"},
|
||||
SENTRY_DSN="https://sentry.test/123",
|
||||
THEME_CUSTOMIZATION_FILE_PATH="",
|
||||
)
|
||||
@pytest.mark.parametrize("is_authenticated", [False, True])
|
||||
def test_api_config(is_authenticated):
|
||||
@@ -45,111 +43,17 @@ def test_api_config(is_authenticated):
|
||||
"ENVIRONMENT": "test",
|
||||
"FRONTEND_CSS_URL": "http://testcss/",
|
||||
"FRONTEND_HOMEPAGE_FEATURE_ENABLED": True,
|
||||
"FRONTEND_FOOTER_FEATURE_ENABLED": True,
|
||||
"FRONTEND_THEME": "test-theme",
|
||||
"LANGUAGES": [
|
||||
["en-us", "English"],
|
||||
["fr-fr", "Français"],
|
||||
["de-de", "Deutsch"],
|
||||
["nl-nl", "Nederlands"],
|
||||
["es-es", "Español"],
|
||||
],
|
||||
"LANGUAGE_CODE": "en-us",
|
||||
"MEDIA_BASE_URL": "http://testserver/",
|
||||
"POSTHOG_KEY": {"id": "132456", "host": "https://eu.i.posthog-test.com"},
|
||||
"SENTRY_DSN": "https://sentry.test/123",
|
||||
"AI_FEATURE_ENABLED": False,
|
||||
"theme_customization": {},
|
||||
}
|
||||
|
||||
|
||||
@override_settings(
|
||||
THEME_CUSTOMIZATION_FILE_PATH="/not/existing/file.json",
|
||||
)
|
||||
@pytest.mark.parametrize("is_authenticated", [False, True])
|
||||
def test_api_config_with_invalid_theme_customization_file(is_authenticated):
|
||||
"""Anonymous users should be allowed to get the configuration."""
|
||||
client = APIClient()
|
||||
|
||||
if is_authenticated:
|
||||
user = factories.UserFactory()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.get("/api/v1.0/config/")
|
||||
assert response.status_code == HTTP_200_OK
|
||||
content = response.json()
|
||||
assert content["theme_customization"] == {}
|
||||
|
||||
|
||||
@override_settings(
|
||||
THEME_CUSTOMIZATION_FILE_PATH="/configuration/theme/invalid.json",
|
||||
)
|
||||
@pytest.mark.parametrize("is_authenticated", [False, True])
|
||||
def test_api_config_with_invalid_json_theme_customization_file(is_authenticated, fs):
|
||||
"""Anonymous users should be allowed to get the configuration."""
|
||||
fs.create_file(
|
||||
"/configuration/theme/invalid.json",
|
||||
contents="invalid json",
|
||||
)
|
||||
client = APIClient()
|
||||
|
||||
if is_authenticated:
|
||||
user = factories.UserFactory()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.get("/api/v1.0/config/")
|
||||
assert response.status_code == HTTP_200_OK
|
||||
content = response.json()
|
||||
assert content["theme_customization"] == {}
|
||||
|
||||
|
||||
@override_settings(
|
||||
THEME_CUSTOMIZATION_FILE_PATH="/configuration/theme/default.json",
|
||||
)
|
||||
@pytest.mark.parametrize("is_authenticated", [False, True])
|
||||
def test_api_config_with_theme_customization(is_authenticated, fs):
|
||||
"""Anonymous users should be allowed to get the configuration."""
|
||||
fs.create_file(
|
||||
"/configuration/theme/default.json",
|
||||
contents=json.dumps(
|
||||
{
|
||||
"colors": {
|
||||
"primary": "#000000",
|
||||
"secondary": "#000000",
|
||||
},
|
||||
}
|
||||
),
|
||||
)
|
||||
client = APIClient()
|
||||
|
||||
if is_authenticated:
|
||||
user = factories.UserFactory()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.get("/api/v1.0/config/")
|
||||
assert response.status_code == HTTP_200_OK
|
||||
content = response.json()
|
||||
assert content["theme_customization"] == {
|
||||
"colors": {
|
||||
"primary": "#000000",
|
||||
"secondary": "#000000",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_authenticated", [False, True])
|
||||
def test_api_config_with_original_theme_customization(is_authenticated, settings):
|
||||
"""Anonymous users should be allowed to get the configuration."""
|
||||
client = APIClient()
|
||||
|
||||
if is_authenticated:
|
||||
user = factories.UserFactory()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.get("/api/v1.0/config/")
|
||||
assert response.status_code == HTTP_200_OK
|
||||
content = response.json()
|
||||
|
||||
with open(settings.THEME_CUSTOMIZATION_FILE_PATH, "r", encoding="utf-8") as f:
|
||||
theme_customization = json.load(f)
|
||||
|
||||
assert content["theme_customization"] == theme_customization
|
||||
|
||||
81
src/backend/core/tests/test_api_footer.py
Normal file
81
src/backend/core/tests/test_api_footer.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""Test the footer API."""
|
||||
|
||||
import responses
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
|
||||
def test_api_footer_without_settings_configured(settings):
|
||||
"""Test the footer API without settings configured."""
|
||||
settings.FRONTEND_URL_JSON_FOOTER = None
|
||||
client = APIClient()
|
||||
response = client.get("/api/v1.0/footer/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {}
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_footer_with_invalid_request(settings):
|
||||
"""Test the footer API with an invalid request."""
|
||||
settings.FRONTEND_URL_JSON_FOOTER = "https://invalid-request.com"
|
||||
|
||||
footer_response = responses.get(settings.FRONTEND_URL_JSON_FOOTER, status=404)
|
||||
|
||||
client = APIClient()
|
||||
response = client.get("/api/v1.0/footer/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {}
|
||||
assert footer_response.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_footer_with_invalid_json(settings):
|
||||
"""Test the footer API with an invalid JSON response."""
|
||||
settings.FRONTEND_URL_JSON_FOOTER = "https://valid-request.com"
|
||||
|
||||
footer_response = responses.get(
|
||||
settings.FRONTEND_URL_JSON_FOOTER, status=200, body="invalid json"
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
response = client.get("/api/v1.0/footer/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {}
|
||||
assert footer_response.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_footer_with_valid_json(settings):
|
||||
"""Test the footer API with an invalid JSON response."""
|
||||
settings.FRONTEND_URL_JSON_FOOTER = "https://valid-request.com"
|
||||
|
||||
footer_response = responses.get(
|
||||
settings.FRONTEND_URL_JSON_FOOTER, status=200, json={"foo": "bar"}
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
response = client.get("/api/v1.0/footer/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"foo": "bar"}
|
||||
assert footer_response.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_footer_with_valid_json_and_cache(settings):
|
||||
"""Test the footer API with an invalid JSON response."""
|
||||
settings.FRONTEND_URL_JSON_FOOTER = "https://valid-request.com"
|
||||
|
||||
footer_response = responses.get(
|
||||
settings.FRONTEND_URL_JSON_FOOTER, status=200, json={"foo": "bar"}
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
response = client.get("/api/v1.0/footer/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"foo": "bar"}
|
||||
assert footer_response.call_count == 1
|
||||
|
||||
response = client.get("/api/v1.0/footer/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"foo": "bar"}
|
||||
# The cache should have been used
|
||||
assert footer_response.call_count == 1
|
||||
@@ -1,76 +0,0 @@
|
||||
"""Test malware detection callback."""
|
||||
|
||||
import random
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.storage import default_storage
|
||||
|
||||
import pytest
|
||||
from lasuite.malware_detection.enums import ReportStatus
|
||||
|
||||
from core.enums import DocumentAttachmentStatus
|
||||
from core.factories import DocumentFactory
|
||||
from core.malware_detection import malware_detection_callback
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.fixture(name="safe_file")
|
||||
def fixture_safe_file():
|
||||
"""Create a safe file."""
|
||||
file_path = "test.txt"
|
||||
default_storage.save(file_path, ContentFile("test"))
|
||||
yield file_path
|
||||
default_storage.delete(file_path)
|
||||
|
||||
|
||||
@pytest.fixture(name="unsafe_file")
|
||||
def fixture_unsafe_file():
|
||||
"""Create an unsafe file."""
|
||||
file_path = "unsafe.txt"
|
||||
default_storage.save(file_path, ContentFile("test"))
|
||||
yield file_path
|
||||
|
||||
|
||||
def test_malware_detection_callback_safe_status(safe_file):
|
||||
"""Test malware detection callback with safe status."""
|
||||
|
||||
document = DocumentFactory(attachments=[safe_file])
|
||||
|
||||
malware_detection_callback(
|
||||
safe_file,
|
||||
ReportStatus.SAFE,
|
||||
error_info={},
|
||||
document_id=document.id,
|
||||
)
|
||||
|
||||
document.refresh_from_db()
|
||||
|
||||
assert safe_file in document.attachments
|
||||
assert default_storage.exists(safe_file)
|
||||
|
||||
s3_client = default_storage.connection.meta.client
|
||||
bucket_name = default_storage.bucket_name
|
||||
head_resp = s3_client.head_object(Bucket=bucket_name, Key=safe_file)
|
||||
metadata = head_resp.get("Metadata", {})
|
||||
assert metadata["status"] == DocumentAttachmentStatus.READY
|
||||
|
||||
|
||||
def test_malware_detection_callback_unsafe_status(unsafe_file):
|
||||
"""Test malware detection callback with unsafe status."""
|
||||
|
||||
document = DocumentFactory(attachments=[unsafe_file])
|
||||
|
||||
malware_detection_callback(
|
||||
unsafe_file,
|
||||
random.choice(
|
||||
[status.value for status in ReportStatus if status != ReportStatus.SAFE]
|
||||
),
|
||||
error_info={"error": "test", "error_code": 4001},
|
||||
document_id=document.id,
|
||||
)
|
||||
|
||||
document.refresh_from_db()
|
||||
|
||||
assert unsafe_file not in document.attachments
|
||||
assert not default_storage.exists(unsafe_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 "propriétaire" "
|
||||
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
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -56,4 +56,5 @@ urlpatterns = [
|
||||
),
|
||||
),
|
||||
path(f"api/{settings.API_VERSION}/config/", viewsets.ConfigView.as_view()),
|
||||
path(f"api/{settings.API_VERSION}/footer/", viewsets.FooterView.as_view()),
|
||||
]
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
"""Impress package. Import the celery app early to load shared task form dependencies."""
|
||||
|
||||
from .celery_app import app as celery_app
|
||||
|
||||
__all__ = ["celery_app"]
|
||||
|
||||
@@ -11,9 +11,6 @@ os.environ.setdefault("DJANGO_CONFIGURATION", "Development")
|
||||
|
||||
install(check_options=True)
|
||||
|
||||
# Can not be loaded only after install call.
|
||||
from django.conf import settings # pylint: disable=wrong-import-position
|
||||
|
||||
app = Celery("impress")
|
||||
|
||||
# Using a string here means the worker doesn't have to serialize
|
||||
@@ -23,4 +20,4 @@ app = Celery("impress")
|
||||
app.config_from_object("django.conf:settings", namespace="CELERY")
|
||||
|
||||
# Load task modules from all registered Django apps.
|
||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||
app.autodiscover_tasks()
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
{
|
||||
"footer": {
|
||||
"default": {
|
||||
"externalLinks": [
|
||||
{
|
||||
"label": "Github",
|
||||
"href": "https://github.com/suitenumerique/docs/"
|
||||
},
|
||||
{
|
||||
"label": "DINUM",
|
||||
"href": "https://www.numerique.gouv.fr/dinum/"
|
||||
},
|
||||
{
|
||||
"label": "ZenDiS",
|
||||
"href": "https://zendis.de/"
|
||||
},
|
||||
{
|
||||
"label": "BlockNote.js",
|
||||
"href": "https://www.blocknotejs.org/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Unless otherwise stated, all content on this site is under",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Legal Notice",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Personal data and cookies",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Accessibility",
|
||||
"href": "#"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Unless otherwise stated, all content on this site is under",
|
||||
"link": {
|
||||
"label": "licence MIT",
|
||||
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Mentions légales",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Données personnelles et cookies",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Accessibilité",
|
||||
"href": "#"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Sauf mention contraire, tout le contenu de ce site est sous",
|
||||
"link": {
|
||||
"label": "licence MIT",
|
||||
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Impressum",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Personenbezogene Daten und Cookies",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Barrierefreiheit",
|
||||
"href": "#"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Sofern nicht anders angegeben, steht der gesamte Inhalt dieser Website unter",
|
||||
"link": {
|
||||
"label": "licence MIT",
|
||||
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Wettelijke bepalingen",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Persoonlijke gegevens en cookies",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Toegankelijkheid",
|
||||
"href": "#"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Tenzij anders vermeld, is alle inhoud van deze site ondergebracht onder",
|
||||
"link": {
|
||||
"label": "licence MIT",
|
||||
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.getenv("DATA_DIR", os.path.join("/", "data"))
|
||||
DATA_DIR = os.path.join("/", "data")
|
||||
|
||||
|
||||
def get_release():
|
||||
@@ -239,7 +239,6 @@ class Base(Configuration):
|
||||
("fr-fr", "Français"),
|
||||
("de-de", "Deutsch"),
|
||||
("nl-nl", "Nederlands"),
|
||||
("es-es", "Español"),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -317,7 +316,6 @@ class Base(Configuration):
|
||||
"django.contrib.staticfiles",
|
||||
# OIDC third party
|
||||
"mozilla_django_oidc",
|
||||
"lasuite.malware_detection",
|
||||
]
|
||||
|
||||
# Cache
|
||||
@@ -334,12 +332,6 @@ 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,
|
||||
@@ -419,26 +411,27 @@ class Base(Configuration):
|
||||
None, environ_name="FRONTEND_THEME", environ_prefix=None
|
||||
)
|
||||
FRONTEND_HOMEPAGE_FEATURE_ENABLED = values.BooleanValue(
|
||||
default=True,
|
||||
default=False,
|
||||
environ_name="FRONTEND_HOMEPAGE_FEATURE_ENABLED",
|
||||
environ_prefix=None,
|
||||
)
|
||||
FRONTEND_URL_JSON_FOOTER = values.Value(
|
||||
None, environ_name="FRONTEND_URL_JSON_FOOTER", environ_prefix=None
|
||||
)
|
||||
FRONTEND_FOOTER_FEATURE_ENABLED = values.BooleanValue(
|
||||
default=False,
|
||||
environ_name="FRONTEND_FOOTER_FEATURE_ENABLED",
|
||||
environ_prefix=None,
|
||||
)
|
||||
FRONTEND_FOOTER_VIEW_CACHE_TIMEOUT = values.Value(
|
||||
60 * 60 * 24,
|
||||
environ_name="FRONTEND_FOOTER_VIEW_CACHE_TIMEOUT",
|
||||
environ_prefix=None,
|
||||
)
|
||||
FRONTEND_CSS_URL = values.Value(
|
||||
None, environ_name="FRONTEND_CSS_URL", environ_prefix=None
|
||||
)
|
||||
|
||||
THEME_CUSTOMIZATION_FILE_PATH = values.Value(
|
||||
os.path.join(BASE_DIR, "impress/configuration/theme/default.json"),
|
||||
environ_name="THEME_CUSTOMIZATION_FILE_PATH",
|
||||
environ_prefix=None,
|
||||
)
|
||||
|
||||
THEME_CUSTOMIZATION_CACHE_TIMEOUT = values.Value(
|
||||
60 * 60 * 24,
|
||||
environ_name="THEME_CUSTOMIZATION_CACHE_TIMEOUT",
|
||||
environ_prefix=None,
|
||||
)
|
||||
|
||||
# Posthog
|
||||
POSTHOG_KEY = values.DictValue(
|
||||
None, environ_name="POSTHOG_KEY", environ_prefix=None
|
||||
@@ -527,28 +520,6 @@ 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
|
||||
@@ -562,23 +533,14 @@ class Base(Configuration):
|
||||
USER_OIDC_ESSENTIAL_CLAIMS = values.ListValue(
|
||||
default=[], environ_name="USER_OIDC_ESSENTIAL_CLAIMS", environ_prefix=None
|
||||
)
|
||||
|
||||
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",
|
||||
USER_OIDC_FIELDS_TO_FULLNAME = values.ListValue(
|
||||
default=["first_name", "last_name"],
|
||||
environ_name="USER_OIDC_FIELDS_TO_FULLNAME",
|
||||
environ_prefix=None,
|
||||
)
|
||||
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",
|
||||
USER_OIDC_FIELD_TO_SHORTNAME = values.Value(
|
||||
default="first_name",
|
||||
environ_name="USER_OIDC_FIELD_TO_SHORTNAME",
|
||||
environ_prefix=None,
|
||||
)
|
||||
|
||||
@@ -680,21 +642,6 @@ class Base(Configuration):
|
||||
},
|
||||
}
|
||||
|
||||
MALWARE_DETECTION = {
|
||||
"BACKEND": values.Value(
|
||||
"lasuite.malware_detection.backends.dummy.DummyBackend",
|
||||
environ_name="MALWARE_DETECTION_BACKEND",
|
||||
environ_prefix=None,
|
||||
),
|
||||
"PARAMETERS": values.DictValue(
|
||||
default={
|
||||
"callback_path": "core.malware_detection.malware_detection_callback",
|
||||
},
|
||||
environ_name="MALWARE_DETECTION_PARAMETERS",
|
||||
environ_prefix=None,
|
||||
),
|
||||
}
|
||||
|
||||
API_USERS_LIST_LIMIT = values.PositiveIntegerValue(
|
||||
default=5,
|
||||
environ_name="API_USERS_LIST_LIMIT",
|
||||
@@ -913,11 +860,6 @@ class Production(Base):
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
},
|
||||
"KEY_PREFIX": values.Value(
|
||||
"docs",
|
||||
environ_name="CACHES_KEY_PREFIX",
|
||||
environ_prefix=None,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
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 ""
|
||||
|
||||
@@ -1,399 +0,0 @@
|
||||
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 倾力打造。 "
|
||||
@@ -2,8 +2,8 @@ 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"
|
||||
"POT-Creation-Date: 2025-03-13 11:41+0000\n"
|
||||
"PO-Revision-Date: 2025-03-17 13:58\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Language: de_DE\n"
|
||||
@@ -34,199 +34,204 @@ msgstr "Wichtige Daten"
|
||||
msgid "Tree structure"
|
||||
msgstr "Baumstruktur"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:30 core/api/filters.py:30
|
||||
msgid "Creator is me"
|
||||
msgstr "Ersteller bin ich"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:33 core/api/filters.py:33
|
||||
msgid "Favorite"
|
||||
msgstr "Favorit"
|
||||
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
#: build/lib/core/api/serializers.py:354 core/api/serializers.py:354
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Ein neues Dokument wurde in Ihrem Namen erstellt!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
#: build/lib/core/api/serializers.py:358 core/api/serializers.py:358
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Sie sind Besitzer eines neuen Dokuments:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#: build/lib/core/api/serializers.py:473 core/api/serializers.py:473
|
||||
msgid "Body"
|
||||
msgstr "Inhalt"
|
||||
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
#: build/lib/core/api/serializers.py:476 core/api/serializers.py:476
|
||||
msgid "Body type"
|
||||
msgstr "Typ"
|
||||
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
#: build/lib/core/api/serializers.py:482 core/api/serializers.py:482
|
||||
msgid "Format"
|
||||
msgstr "Format"
|
||||
msgstr ""
|
||||
|
||||
#: 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: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/enums.py:35 core/enums.py:35
|
||||
#: 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
|
||||
msgid "First child"
|
||||
msgstr "Erstes Unterelement"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
#: build/lib/core/enums.py:20 core/enums.py:20
|
||||
msgid "Last child"
|
||||
msgstr "Letztes Unterelement"
|
||||
|
||||
#: build/lib/core/enums.py:37 core/enums.py:37
|
||||
#: build/lib/core/enums.py:21 core/enums.py:21
|
||||
msgid "First sibling"
|
||||
msgstr "Erstes Nebenelement"
|
||||
|
||||
#: build/lib/core/enums.py:38 core/enums.py:38
|
||||
#: build/lib/core/enums.py:22 core/enums.py:22
|
||||
msgid "Last sibling"
|
||||
msgstr "Letztes Nebenelement"
|
||||
|
||||
#: build/lib/core/enums.py:39 core/enums.py:39
|
||||
#: build/lib/core/enums.py:23 core/enums.py:23
|
||||
msgid "Left"
|
||||
msgstr "Links"
|
||||
|
||||
#: build/lib/core/enums.py:40 core/enums.py:40
|
||||
#: build/lib/core/enums.py:24 core/enums.py:24
|
||||
msgid "Right"
|
||||
msgstr "Rechts"
|
||||
|
||||
#: 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:55 build/lib/core/models.py:62 core/models.py:55
|
||||
#: core/models.py:62
|
||||
msgid "Reader"
|
||||
msgstr "Lesen"
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Editor"
|
||||
msgstr "Bearbeiten"
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
#: build/lib/core/models.py:64 core/models.py:64
|
||||
msgid "Administrator"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Owner"
|
||||
msgstr "Besitzer"
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
#: build/lib/core/models.py:76 core/models.py:76
|
||||
msgid "Restricted"
|
||||
msgstr "Beschränkt"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "Authenticated"
|
||||
msgstr "Authentifiziert"
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
msgid "Public"
|
||||
msgstr "Öffentlich"
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "primärer Schlüssel für den Datensatz als UUID"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "created on"
|
||||
msgstr "Erstellt"
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
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:167 core/models.py:167
|
||||
#: build/lib/core/models.py:166 core/models.py:166
|
||||
msgid "updated on"
|
||||
msgstr "Aktualisiert"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
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:204 core/models.py:204
|
||||
#: build/lib/core/models.py:203 core/models.py:203
|
||||
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:217 core/models.py:217
|
||||
#: build/lib/core/models.py:216 core/models.py:216
|
||||
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:223 core/models.py:223
|
||||
#: build/lib/core/models.py:222 core/models.py:222
|
||||
msgid "sub"
|
||||
msgstr "unter"
|
||||
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
#: build/lib/core/models.py:224 core/models.py:224
|
||||
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:234 core/models.py:234
|
||||
#: build/lib/core/models.py:233 core/models.py:233
|
||||
msgid "full name"
|
||||
msgstr "Name"
|
||||
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "short name"
|
||||
msgstr "Kurzbezeichnung"
|
||||
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
#: build/lib/core/models.py:236 core/models.py:236
|
||||
msgid "identity email address"
|
||||
msgstr "Identitäts-E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
#: build/lib/core/models.py:241 core/models.py:241
|
||||
msgid "admin email address"
|
||||
msgstr "Admin E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
#: build/lib/core/models.py:248 core/models.py:248
|
||||
msgid "language"
|
||||
msgstr "Sprache"
|
||||
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
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:258 core/models.py:258
|
||||
#: build/lib/core/models.py:257 core/models.py:257
|
||||
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:261 core/models.py:261
|
||||
#: build/lib/core/models.py:260 core/models.py:260
|
||||
msgid "device"
|
||||
msgstr "Gerät"
|
||||
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
#: build/lib/core/models.py:262 core/models.py:262
|
||||
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:266 core/models.py:266
|
||||
#: build/lib/core/models.py:265 core/models.py:265
|
||||
msgid "staff status"
|
||||
msgstr "Status des Teammitgliedes"
|
||||
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
#: build/lib/core/models.py:267 core/models.py:267
|
||||
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:271 core/models.py:271
|
||||
#: build/lib/core/models.py:270 core/models.py:270
|
||||
msgid "active"
|
||||
msgstr "aktiviert"
|
||||
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
#: build/lib/core/models.py:273 core/models.py:273
|
||||
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:286 core/models.py:286
|
||||
#: build/lib/core/models.py:285 core/models.py:285
|
||||
msgid "user"
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "users"
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
|
||||
#: core/models.py:470 core/models.py:1154
|
||||
#: build/lib/core/models.py:470 build/lib/core/models.py:1074
|
||||
#: core/models.py:470 core/models.py:1074
|
||||
msgid "title"
|
||||
msgstr "Titel"
|
||||
|
||||
@@ -234,136 +239,136 @@ msgstr "Titel"
|
||||
msgid "excerpt"
|
||||
msgstr "Auszug"
|
||||
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
#: build/lib/core/models.py:504 core/models.py:504
|
||||
msgid "Document"
|
||||
msgstr "Dokument"
|
||||
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
#: build/lib/core/models.py:505 core/models.py:505
|
||||
msgid "Documents"
|
||||
msgstr "Dokumente"
|
||||
|
||||
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
|
||||
#: core/models.py:872
|
||||
#: build/lib/core/models.py:517 build/lib/core/models.py:826 core/models.py:517
|
||||
#: core/models.py:826
|
||||
msgid "Untitled Document"
|
||||
msgstr "Unbenanntes Dokument"
|
||||
|
||||
#: build/lib/core/models.py:907 core/models.py:907
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} hat ein Dokument mit Ihnen geteilt!"
|
||||
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#, 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:917 core/models.py:917
|
||||
#: build/lib/core/models.py:871 core/models.py:871
|
||||
#, 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:1015 core/models.py:1015
|
||||
#: build/lib/core/models.py:969 core/models.py:969
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Dokument/Benutzer Linkverfolgung"
|
||||
|
||||
#: build/lib/core/models.py:1016 core/models.py:1016
|
||||
#: build/lib/core/models.py:970 core/models.py:970
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Dokument/Benutzer Linkverfolgung"
|
||||
|
||||
#: build/lib/core/models.py:1022 core/models.py:1022
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Für dieses Dokument/ diesen Benutzer ist bereits eine Linkverfolgung vorhanden."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1045 core/models.py:1045
|
||||
#: build/lib/core/models.py:999 core/models.py:999
|
||||
msgid "Document favorite"
|
||||
msgstr "Dokumentenfavorit"
|
||||
|
||||
#: build/lib/core/models.py:1046 core/models.py:1046
|
||||
#: build/lib/core/models.py:1000 core/models.py:1000
|
||||
msgid "Document favorites"
|
||||
msgstr "Dokumentfavoriten"
|
||||
|
||||
#: build/lib/core/models.py:1052 core/models.py:1052
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
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:1074 core/models.py:1074
|
||||
#: build/lib/core/models.py:1028 core/models.py:1028
|
||||
msgid "Document/user relation"
|
||||
msgstr "Dokument/Benutzerbeziehung"
|
||||
|
||||
#: build/lib/core/models.py:1075 core/models.py:1075
|
||||
#: build/lib/core/models.py:1029 core/models.py:1029
|
||||
msgid "Document/user relations"
|
||||
msgstr "Dokument/Benutzerbeziehungen"
|
||||
|
||||
#: build/lib/core/models.py:1081 core/models.py:1081
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Dieser Benutzer befindet sich bereits in diesem Dokument."
|
||||
|
||||
#: build/lib/core/models.py:1087 core/models.py:1087
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Dieses Team befindet sich bereits in diesem Dokument."
|
||||
|
||||
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
|
||||
#: core/models.py:1093 core/models.py:1241
|
||||
#: 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 "Benutzer oder Team müssen gesetzt werden, nicht beides."
|
||||
|
||||
#: build/lib/core/models.py:1155 core/models.py:1155
|
||||
#: build/lib/core/models.py:1075 core/models.py:1075
|
||||
msgid "description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: build/lib/core/models.py:1156 core/models.py:1156
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "code"
|
||||
msgstr "Code"
|
||||
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
#: build/lib/core/models.py:1077 core/models.py:1077
|
||||
msgid "css"
|
||||
msgstr "CSS"
|
||||
|
||||
#: build/lib/core/models.py:1159 core/models.py:1159
|
||||
#: build/lib/core/models.py:1079 core/models.py:1079
|
||||
msgid "public"
|
||||
msgstr "öffentlich"
|
||||
|
||||
#: build/lib/core/models.py:1161 core/models.py:1161
|
||||
#: build/lib/core/models.py:1081 core/models.py:1081
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr "Ob diese Vorlage für jedermann öffentlich ist."
|
||||
|
||||
#: build/lib/core/models.py:1167 core/models.py:1167
|
||||
#: build/lib/core/models.py:1087 core/models.py:1087
|
||||
msgid "Template"
|
||||
msgstr "Vorlage"
|
||||
|
||||
#: build/lib/core/models.py:1168 core/models.py:1168
|
||||
#: build/lib/core/models.py:1088 core/models.py:1088
|
||||
msgid "Templates"
|
||||
msgstr "Vorlagen"
|
||||
|
||||
#: build/lib/core/models.py:1222 core/models.py:1222
|
||||
#: build/lib/core/models.py:1142 core/models.py:1142
|
||||
msgid "Template/user relation"
|
||||
msgstr "Vorlage/Benutzer-Beziehung"
|
||||
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
#: build/lib/core/models.py:1143 core/models.py:1143
|
||||
msgid "Template/user relations"
|
||||
msgstr "Vorlage/Benutzerbeziehungen"
|
||||
|
||||
#: build/lib/core/models.py:1229 core/models.py:1229
|
||||
#: build/lib/core/models.py:1149 core/models.py:1149
|
||||
msgid "This user is already in this template."
|
||||
msgstr "Dieser Benutzer ist bereits in dieser Vorlage."
|
||||
|
||||
#: build/lib/core/models.py:1235 core/models.py:1235
|
||||
#: build/lib/core/models.py:1155 core/models.py:1155
|
||||
msgid "This team is already in this template."
|
||||
msgstr "Dieses Team ist bereits in diesem Template."
|
||||
|
||||
#: build/lib/core/models.py:1258 core/models.py:1258
|
||||
#: build/lib/core/models.py:1178 core/models.py:1178
|
||||
msgid "email address"
|
||||
msgstr "E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:1277 core/models.py:1277
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "Document invitation"
|
||||
msgstr "Einladung zum Dokument"
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1198 core/models.py:1198
|
||||
msgid "Document invitations"
|
||||
msgstr "Dokumenteinladungen"
|
||||
|
||||
#: build/lib/core/models.py:1298 core/models.py:1298
|
||||
#: build/lib/core/models.py:1218 core/models.py:1218
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Diese E-Mail ist bereits einem registrierten Benutzer zugeordnet."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ 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"
|
||||
"POT-Creation-Date: 2025-03-13 11:41+0000\n"
|
||||
"PO-Revision-Date: 2025-03-17 13:58\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: English\n"
|
||||
"Language: en_US\n"
|
||||
@@ -34,199 +34,204 @@ msgstr ""
|
||||
msgid "Tree structure"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:30 core/api/filters.py:30
|
||||
msgid "Creator is me"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: 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
|
||||
#: build/lib/core/api/serializers.py:354 core/api/serializers.py:354
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
#: build/lib/core/api/serializers.py:358 core/api/serializers.py:358
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#: build/lib/core/api/serializers.py:473 core/api/serializers.py:473
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
#: build/lib/core/api/serializers.py:476 core/api/serializers.py:476
|
||||
msgid "Body type"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
#: build/lib/core/api/serializers.py:482 core/api/serializers.py:482
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
|
||||
#, python-brace-format
|
||||
msgid "copy of {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/enums.py:35 core/enums.py:35
|
||||
#: 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
|
||||
msgid "First child"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
#: build/lib/core/enums.py:20 core/enums.py:20
|
||||
msgid "Last child"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:37 core/enums.py:37
|
||||
#: build/lib/core/enums.py:21 core/enums.py:21
|
||||
msgid "First sibling"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:38 core/enums.py:38
|
||||
#: build/lib/core/enums.py:22 core/enums.py:22
|
||||
msgid "Last sibling"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:39 core/enums.py:39
|
||||
#: build/lib/core/enums.py:23 core/enums.py:23
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:40 core/enums.py:40
|
||||
#: build/lib/core/enums.py:24 core/enums.py:24
|
||||
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:65 core/models.py:65
|
||||
#: build/lib/core/models.py:64 core/models.py:64
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
#: build/lib/core/models.py:76 core/models.py:76
|
||||
msgid "Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "Authenticated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:166 core/models.py:166
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
#: build/lib/core/models.py:203 core/models.py:203
|
||||
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
|
||||
#: build/lib/core/models.py:216 core/models.py:216
|
||||
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
|
||||
#: build/lib/core/models.py:222 core/models.py:222
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
#: build/lib/core/models.py:224 core/models.py:224
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
#: build/lib/core/models.py:233 core/models.py:233
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
#: build/lib/core/models.py:236 core/models.py:236
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
#: build/lib/core/models.py:241 core/models.py:241
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
#: build/lib/core/models.py:248 core/models.py:248
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
#: build/lib/core/models.py:257 core/models.py:257
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
#: build/lib/core/models.py:260 core/models.py:260
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
#: build/lib/core/models.py:262 core/models.py:262
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
#: build/lib/core/models.py:265 core/models.py:265
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
#: build/lib/core/models.py:267 core/models.py:267
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
#: build/lib/core/models.py:270 core/models.py:270
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
#: build/lib/core/models.py:273 core/models.py:273
|
||||
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
|
||||
#: build/lib/core/models.py:285 core/models.py:285
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
|
||||
#: core/models.py:470 core/models.py:1154
|
||||
#: build/lib/core/models.py:470 build/lib/core/models.py:1074
|
||||
#: core/models.py:470 core/models.py:1074
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
@@ -234,136 +239,136 @@ msgstr ""
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
#: build/lib/core/models.py:504 core/models.py:504
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
#: build/lib/core/models.py:505 core/models.py:505
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
|
||||
#: core/models.py:872
|
||||
#: build/lib/core/models.py:517 build/lib/core/models.py:826 core/models.py:517
|
||||
#: core/models.py:826
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:907 core/models.py:907
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#, 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
|
||||
#: build/lib/core/models.py:871 core/models.py:871
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1015 core/models.py:1015
|
||||
#: build/lib/core/models.py:969 core/models.py:969
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1016 core/models.py:1016
|
||||
#: build/lib/core/models.py:970 core/models.py:970
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1022 core/models.py:1022
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1045 core/models.py:1045
|
||||
#: build/lib/core/models.py:999 core/models.py:999
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1046 core/models.py:1046
|
||||
#: build/lib/core/models.py:1000 core/models.py:1000
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1052 core/models.py:1052
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
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
|
||||
#: build/lib/core/models.py:1028 core/models.py:1028
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1075 core/models.py:1075
|
||||
#: build/lib/core/models.py:1029 core/models.py:1029
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1081 core/models.py:1081
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1087 core/models.py:1087
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
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
|
||||
#: 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 ""
|
||||
|
||||
#: build/lib/core/models.py:1155 core/models.py:1155
|
||||
#: build/lib/core/models.py:1075 core/models.py:1075
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1156 core/models.py:1156
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "code"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
#: build/lib/core/models.py:1077 core/models.py:1077
|
||||
msgid "css"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1159 core/models.py:1159
|
||||
#: build/lib/core/models.py:1079 core/models.py:1079
|
||||
msgid "public"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1161 core/models.py:1161
|
||||
#: build/lib/core/models.py:1081 core/models.py:1081
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1167 core/models.py:1167
|
||||
#: build/lib/core/models.py:1087 core/models.py:1087
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1168 core/models.py:1168
|
||||
#: build/lib/core/models.py:1088 core/models.py:1088
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1222 core/models.py:1222
|
||||
#: build/lib/core/models.py:1142 core/models.py:1142
|
||||
msgid "Template/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
#: build/lib/core/models.py:1143 core/models.py:1143
|
||||
msgid "Template/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1229 core/models.py:1229
|
||||
#: build/lib/core/models.py:1149 core/models.py:1149
|
||||
msgid "This user is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1235 core/models.py:1235
|
||||
#: build/lib/core/models.py:1155 core/models.py:1155
|
||||
msgid "This team is already in this template."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1258 core/models.py:1258
|
||||
#: build/lib/core/models.py:1178 core/models.py:1178
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1277 core/models.py:1277
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1198 core/models.py:1198
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1298 core/models.py:1298
|
||||
#: build/lib/core/models.py:1218 core/models.py:1218
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
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 "
|
||||
|
||||
@@ -2,8 +2,8 @@ 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 09:05\n"
|
||||
"POT-Creation-Date: 2025-03-13 11:41+0000\n"
|
||||
"PO-Revision-Date: 2025-03-17 13:58\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 "Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:62 core/admin.py:62
|
||||
msgid "Important dates"
|
||||
@@ -32,340 +32,345 @@ msgstr "Dates importantes"
|
||||
|
||||
#: build/lib/core/admin.py:148 core/admin.py:148
|
||||
msgid "Tree structure"
|
||||
msgstr "Arborescence"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:30 core/api/filters.py:30
|
||||
msgid "Creator is me"
|
||||
msgstr "Je suis l'auteur"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:33 core/api/filters.py:33
|
||||
msgid "Favorite"
|
||||
msgstr "Favoris"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
#: build/lib/core/api/serializers.py:354 core/api/serializers.py:354
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Un nouveau document a été créé pour vous !"
|
||||
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
#: build/lib/core/api/serializers.py:358 core/api/serializers.py:358
|
||||
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:586 core/api/serializers.py:586
|
||||
#: build/lib/core/api/serializers.py:473 core/api/serializers.py:473
|
||||
msgid "Body"
|
||||
msgstr "Corps"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
#: build/lib/core/api/serializers.py:476 core/api/serializers.py:476
|
||||
msgid "Body type"
|
||||
msgstr "Type de corps"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
#: build/lib/core/api/serializers.py:482 core/api/serializers.py:482
|
||||
msgid "Format"
|
||||
msgstr "Format"
|
||||
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:61
|
||||
#: core/authentication/backends.py:61
|
||||
msgid "Invalid response format or token verification failed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:35 core/enums.py:35
|
||||
#: 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
|
||||
msgid "First child"
|
||||
msgstr "Premier enfant"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
#: build/lib/core/enums.py:20 core/enums.py:20
|
||||
msgid "Last child"
|
||||
msgstr "Dernier enfant"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:37 core/enums.py:37
|
||||
#: build/lib/core/enums.py:21 core/enums.py:21
|
||||
msgid "First sibling"
|
||||
msgstr "Premier frère ou sœur"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:38 core/enums.py:38
|
||||
#: build/lib/core/enums.py:22 core/enums.py:22
|
||||
msgid "Last sibling"
|
||||
msgstr "Dernière relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:39 core/enums.py:39
|
||||
#: build/lib/core/enums.py:23 core/enums.py:23
|
||||
msgid "Left"
|
||||
msgstr "Gauche"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:40 core/enums.py:40
|
||||
#: build/lib/core/enums.py:24 core/enums.py:24
|
||||
msgid "Right"
|
||||
msgstr "Droite"
|
||||
msgstr ""
|
||||
|
||||
#: 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:55 build/lib/core/models.py:62 core/models.py:55
|
||||
#: core/models.py:62
|
||||
msgid "Reader"
|
||||
msgstr "Lecteur"
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Editor"
|
||||
msgstr "Éditeur"
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
#: build/lib/core/models.py:64 core/models.py:64
|
||||
msgid "Administrator"
|
||||
msgstr "Administrateur"
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Owner"
|
||||
msgstr "Propriétaire"
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
#: build/lib/core/models.py:76 core/models.py:76
|
||||
msgid "Restricted"
|
||||
msgstr "Restreint"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "Authenticated"
|
||||
msgstr "Authentifié"
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
msgid "Public"
|
||||
msgstr "Public"
|
||||
msgstr ""
|
||||
|
||||
#: 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 "identifiant/id"
|
||||
|
||||
#: 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"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "created on"
|
||||
msgstr "créé le"
|
||||
|
||||
#: 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"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:166 core/models.py:166
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: 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 "date et heure de la dernière mise à jour de l'enregistrement"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
#: build/lib/core/models.py:203 core/models.py:203
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr "Nous n'avons pas pu trouver un utilisateur avec ce sous-groupe mais l'e-mail est déjà associé à un utilisateur enregistré."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:217 core/models.py:217
|
||||
#: build/lib/core/models.py:216 core/models.py:216
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
|
||||
msgstr "Saisissez un sous-groupe valide. Cette valeur ne peut contenir que des lettres, des chiffres et les caractères @/./+/-/_/: uniquement."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:223 core/models.py:223
|
||||
#: build/lib/core/models.py:222 core/models.py:222
|
||||
msgid "sub"
|
||||
msgstr "sous-groupe"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
#: build/lib/core/models.py:224 core/models.py:224
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
|
||||
msgstr "Obligatoire. 255 caractères ou moins. Lettres, chiffres et caractères @/./+/-/_/: uniquement."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:233 core/models.py:233
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: 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 "nom court"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
#: build/lib/core/models.py:236 core/models.py:236
|
||||
msgid "identity email address"
|
||||
msgstr "adresse e-mail d'identité"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
#: build/lib/core/models.py:241 core/models.py:241
|
||||
msgid "admin email address"
|
||||
msgstr "adresse e-mail de l'administrateur"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:248 core/models.py:248
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: 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 "La langue dans laquelle l'utilisateur veut voir l'interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
#: build/lib/core/models.py:257 core/models.py:257
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "Le fuseau horaire dans lequel l'utilisateur souhaite voir les heures."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:261 core/models.py:261
|
||||
#: build/lib/core/models.py:260 core/models.py:260
|
||||
msgid "device"
|
||||
msgstr "appareil"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
#: build/lib/core/models.py:262 core/models.py:262
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Si l'utilisateur est un appareil ou un utilisateur réel."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:266 core/models.py:266
|
||||
#: build/lib/core/models.py:265 core/models.py:265
|
||||
msgid "staff status"
|
||||
msgstr "statut d'équipe"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
#: build/lib/core/models.py:267 core/models.py:267
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Si l'utilisateur peut se connecter à ce site d'administration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:271 core/models.py:271
|
||||
#: build/lib/core/models.py:270 core/models.py:270
|
||||
msgid "active"
|
||||
msgstr "actif"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
#: build/lib/core/models.py:273 core/models.py:273
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Si cet utilisateur doit être traité comme actif. Désélectionnez ceci au lieu de supprimer des comptes."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:285 core/models.py:285
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "user"
|
||||
msgstr "utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
msgid "users"
|
||||
msgstr "utilisateurs"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
|
||||
#: core/models.py:470 core/models.py:1154
|
||||
#: build/lib/core/models.py:470 build/lib/core/models.py:1074
|
||||
#: core/models.py:470 core/models.py:1074
|
||||
msgid "title"
|
||||
msgstr "titre"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:471 core/models.py:471
|
||||
msgid "excerpt"
|
||||
msgstr "extrait"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
#: build/lib/core/models.py:504 core/models.py:504
|
||||
msgid "Document"
|
||||
msgstr "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
#: build/lib/core/models.py:505 core/models.py:505
|
||||
msgid "Documents"
|
||||
msgstr "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
|
||||
#: core/models.py:872
|
||||
#: build/lib/core/models.py:517 build/lib/core/models.py:826 core/models.py:517
|
||||
#: core/models.py:826
|
||||
msgid "Untitled Document"
|
||||
msgstr "Document sans titre"
|
||||
|
||||
#: build/lib/core/models.py:907 core/models.py:907
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} a partagé un document avec vous!"
|
||||
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#, 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:917 core/models.py:917
|
||||
#: build/lib/core/models.py:871 core/models.py:871
|
||||
#, 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:1015 core/models.py:1015
|
||||
#: build/lib/core/models.py:969 core/models.py:969
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Trace du lien document/utilisateur"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1016 core/models.py:1016
|
||||
#: build/lib/core/models.py:970 core/models.py:970
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Traces du lien document/utilisateur"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1022 core/models.py:1022
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Une trace de lien existe déjà pour ce document/utilisateur."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1045 core/models.py:1045
|
||||
#: build/lib/core/models.py:999 core/models.py:999
|
||||
msgid "Document favorite"
|
||||
msgstr "Document favori"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1046 core/models.py:1046
|
||||
#: build/lib/core/models.py:1000 core/models.py:1000
|
||||
msgid "Document favorites"
|
||||
msgstr "Documents favoris"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1052 core/models.py:1052
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Ce document est déjà un favori de cet utilisateur."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1074 core/models.py:1074
|
||||
#: build/lib/core/models.py:1028 core/models.py:1028
|
||||
msgid "Document/user relation"
|
||||
msgstr "Relation document/utilisateur"
|
||||
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 ""
|
||||
|
||||
#: build/lib/core/models.py:1075 core/models.py:1075
|
||||
msgid "Document/user relations"
|
||||
msgstr "Relations document/utilisateur"
|
||||
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 ""
|
||||
|
||||
#: build/lib/core/models.py:1081 core/models.py:1081
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Cet utilisateur est déjà dans ce document."
|
||||
msgid "Whether this template is public for anyone to use."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1087 core/models.py:1087
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Cette équipe est déjà dans ce document."
|
||||
msgid "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: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: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 "Cette équipe est déjà modèle."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1258 core/models.py:1258
|
||||
#: build/lib/core/models.py:1178 core/models.py:1178
|
||||
msgid "email address"
|
||||
msgstr "adresse e-mail"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1277 core/models.py:1277
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "Document invitation"
|
||||
msgstr "Invitation à un document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1198 core/models.py:1198
|
||||
msgid "Document invitations"
|
||||
msgstr "Invitations à un document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1298 core/models.py:1298
|
||||
#: build/lib/core/models.py:1218 core/models.py:1218
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Cette adresse email est déjà associée à un utilisateur inscrit."
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/invitation.html:162
|
||||
#: core/templates/mail/text/invitation.txt:3
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
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 l’ultimo 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 ""
|
||||
|
||||
@@ -2,8 +2,8 @@ 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"
|
||||
"POT-Creation-Date: 2025-03-13 11:41+0000\n"
|
||||
"PO-Revision-Date: 2025-03-17 13:58\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Language: nl_NL\n"
|
||||
@@ -34,199 +34,204 @@ msgstr "Belangrijke datums"
|
||||
msgid "Tree structure"
|
||||
msgstr "Document structuur"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:30 core/api/filters.py:30
|
||||
msgid "Creator is me"
|
||||
msgstr "Ik ben Eigenaar"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:33 core/api/filters.py:33
|
||||
msgid "Favorite"
|
||||
msgstr "Favoriete"
|
||||
|
||||
#: build/lib/core/api/serializers.py:446 core/api/serializers.py:446
|
||||
#: build/lib/core/api/serializers.py:354 core/api/serializers.py:354
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Een nieuw document was gecreëerd voor u!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:450 core/api/serializers.py:450
|
||||
#: build/lib/core/api/serializers.py:358 core/api/serializers.py:358
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "U heeft eigenaarschap van een nieuw document:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#: build/lib/core/api/serializers.py:473 core/api/serializers.py:473
|
||||
msgid "Body"
|
||||
msgstr "Text"
|
||||
|
||||
#: build/lib/core/api/serializers.py:589 core/api/serializers.py:589
|
||||
#: build/lib/core/api/serializers.py:476 core/api/serializers.py:476
|
||||
msgid "Body type"
|
||||
msgstr "Text type"
|
||||
|
||||
#: build/lib/core/api/serializers.py:595 core/api/serializers.py:595
|
||||
#: build/lib/core/api/serializers.py:482 core/api/serializers.py:482
|
||||
msgid "Format"
|
||||
msgstr "Formaat"
|
||||
|
||||
#: 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: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/enums.py:35 core/enums.py:35
|
||||
#: 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
|
||||
msgid "First child"
|
||||
msgstr "Eerste node"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
#: build/lib/core/enums.py:20 core/enums.py:20
|
||||
msgid "Last child"
|
||||
msgstr "Laatste node"
|
||||
|
||||
#: build/lib/core/enums.py:37 core/enums.py:37
|
||||
#: build/lib/core/enums.py:21 core/enums.py:21
|
||||
msgid "First sibling"
|
||||
msgstr "Eerste naaste"
|
||||
|
||||
#: build/lib/core/enums.py:38 core/enums.py:38
|
||||
#: build/lib/core/enums.py:22 core/enums.py:22
|
||||
msgid "Last sibling"
|
||||
msgstr "Laatste naaste"
|
||||
|
||||
#: build/lib/core/enums.py:39 core/enums.py:39
|
||||
#: build/lib/core/enums.py:23 core/enums.py:23
|
||||
msgid "Left"
|
||||
msgstr "Links"
|
||||
|
||||
#: build/lib/core/enums.py:40 core/enums.py:40
|
||||
#: build/lib/core/enums.py:24 core/enums.py:24
|
||||
msgid "Right"
|
||||
msgstr "Rechts"
|
||||
|
||||
#: 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:55 build/lib/core/models.py:62 core/models.py:55
|
||||
#: core/models.py:62
|
||||
msgid "Reader"
|
||||
msgstr "Lezer"
|
||||
|
||||
#: build/lib/core/models.py:57 build/lib/core/models.py:64 core/models.py:57
|
||||
#: core/models.py:64
|
||||
#: build/lib/core/models.py:56 build/lib/core/models.py:63 core/models.py:56
|
||||
#: core/models.py:63
|
||||
msgid "Editor"
|
||||
msgstr "Bewerker"
|
||||
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
#: build/lib/core/models.py:64 core/models.py:64
|
||||
msgid "Administrator"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: build/lib/core/models.py:66 core/models.py:66
|
||||
#: build/lib/core/models.py:65 core/models.py:65
|
||||
msgid "Owner"
|
||||
msgstr "Eigenaar"
|
||||
|
||||
#: build/lib/core/models.py:77 core/models.py:77
|
||||
#: build/lib/core/models.py:76 core/models.py:76
|
||||
msgid "Restricted"
|
||||
msgstr "Niet toegestaan"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "Authenticated"
|
||||
msgstr "Geauthenticeerd"
|
||||
|
||||
#: build/lib/core/models.py:83 core/models.py:83
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
msgid "Public"
|
||||
msgstr "Publiek"
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "primaire sleutel voor dossier als UUID"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "created on"
|
||||
msgstr "gemaakt op"
|
||||
|
||||
#: build/lib/core/models.py:162 core/models.py:162
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "datum en tijd wanneer dossier was gecreëerd"
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
#: build/lib/core/models.py:166 core/models.py:166
|
||||
msgid "updated on"
|
||||
msgstr "Laatst gewijzigd op"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
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:204 core/models.py:204
|
||||
#: build/lib/core/models.py:203 core/models.py:203
|
||||
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:217 core/models.py:217
|
||||
#: build/lib/core/models.py:216 core/models.py:216
|
||||
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:223 core/models.py:223
|
||||
#: build/lib/core/models.py:222 core/models.py:222
|
||||
msgid "sub"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:225 core/models.py:225
|
||||
#: build/lib/core/models.py:224 core/models.py:224
|
||||
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:234 core/models.py:234
|
||||
#: build/lib/core/models.py:233 core/models.py:233
|
||||
msgid "full name"
|
||||
msgstr "volledige naam"
|
||||
|
||||
#: build/lib/core/models.py:235 core/models.py:235
|
||||
#: build/lib/core/models.py:234 core/models.py:234
|
||||
msgid "short name"
|
||||
msgstr "gebruikersnaam"
|
||||
|
||||
#: build/lib/core/models.py:237 core/models.py:237
|
||||
#: build/lib/core/models.py:236 core/models.py:236
|
||||
msgid "identity email address"
|
||||
msgstr "identiteit email adres"
|
||||
|
||||
#: build/lib/core/models.py:242 core/models.py:242
|
||||
#: build/lib/core/models.py:241 core/models.py:241
|
||||
msgid "admin email address"
|
||||
msgstr "admin email adres"
|
||||
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
#: build/lib/core/models.py:248 core/models.py:248
|
||||
msgid "language"
|
||||
msgstr "taal"
|
||||
|
||||
#: build/lib/core/models.py:250 core/models.py:250
|
||||
#: build/lib/core/models.py:249 core/models.py:249
|
||||
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:258 core/models.py:258
|
||||
#: build/lib/core/models.py:257 core/models.py:257
|
||||
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:261 core/models.py:261
|
||||
#: build/lib/core/models.py:260 core/models.py:260
|
||||
msgid "device"
|
||||
msgstr "apparaat"
|
||||
|
||||
#: build/lib/core/models.py:263 core/models.py:263
|
||||
#: build/lib/core/models.py:262 core/models.py:262
|
||||
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:266 core/models.py:266
|
||||
#: build/lib/core/models.py:265 core/models.py:265
|
||||
msgid "staff status"
|
||||
msgstr "beheerder status"
|
||||
|
||||
#: build/lib/core/models.py:268 core/models.py:268
|
||||
#: build/lib/core/models.py:267 core/models.py:267
|
||||
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:271 core/models.py:271
|
||||
#: build/lib/core/models.py:270 core/models.py:270
|
||||
msgid "active"
|
||||
msgstr "actief"
|
||||
|
||||
#: build/lib/core/models.py:274 core/models.py:274
|
||||
#: build/lib/core/models.py:273 core/models.py:273
|
||||
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:286 core/models.py:286
|
||||
#: build/lib/core/models.py:285 core/models.py:285
|
||||
msgid "user"
|
||||
msgstr "gebruiker"
|
||||
|
||||
#: build/lib/core/models.py:287 core/models.py:287
|
||||
#: build/lib/core/models.py:286 core/models.py:286
|
||||
msgid "users"
|
||||
msgstr "gebruikers"
|
||||
|
||||
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
|
||||
#: core/models.py:470 core/models.py:1154
|
||||
#: build/lib/core/models.py:470 build/lib/core/models.py:1074
|
||||
#: core/models.py:470 core/models.py:1074
|
||||
msgid "title"
|
||||
msgstr "titel"
|
||||
|
||||
@@ -234,136 +239,136 @@ msgstr "titel"
|
||||
msgid "excerpt"
|
||||
msgstr "uittreksel"
|
||||
|
||||
#: build/lib/core/models.py:519 core/models.py:519
|
||||
#: build/lib/core/models.py:504 core/models.py:504
|
||||
msgid "Document"
|
||||
msgstr "Document"
|
||||
|
||||
#: build/lib/core/models.py:520 core/models.py:520
|
||||
#: build/lib/core/models.py:505 core/models.py:505
|
||||
msgid "Documents"
|
||||
msgstr "Documenten"
|
||||
|
||||
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
|
||||
#: core/models.py:872
|
||||
#: build/lib/core/models.py:517 build/lib/core/models.py:826 core/models.py:517
|
||||
#: core/models.py:826
|
||||
msgid "Untitled Document"
|
||||
msgstr "Naamloos Document"
|
||||
|
||||
#: build/lib/core/models.py:907 core/models.py:907
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} heeft een document met gedeeld!"
|
||||
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
#: build/lib/core/models.py:865 core/models.py:865
|
||||
#, 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:917 core/models.py:917
|
||||
#: build/lib/core/models.py:871 core/models.py:871
|
||||
#, 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:1015 core/models.py:1015
|
||||
#: build/lib/core/models.py:969 core/models.py:969
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Document/gebruiker url"
|
||||
|
||||
#: build/lib/core/models.py:1016 core/models.py:1016
|
||||
#: build/lib/core/models.py:970 core/models.py:970
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Document/gebruiker url"
|
||||
|
||||
#: build/lib/core/models.py:1022 core/models.py:1022
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
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:1045 core/models.py:1045
|
||||
#: build/lib/core/models.py:999 core/models.py:999
|
||||
msgid "Document favorite"
|
||||
msgstr "Document favoriet"
|
||||
|
||||
#: build/lib/core/models.py:1046 core/models.py:1046
|
||||
#: build/lib/core/models.py:1000 core/models.py:1000
|
||||
msgid "Document favorites"
|
||||
msgstr "Document favorieten"
|
||||
|
||||
#: build/lib/core/models.py:1052 core/models.py:1052
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
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:1074 core/models.py:1074
|
||||
#: build/lib/core/models.py:1028 core/models.py:1028
|
||||
msgid "Document/user relation"
|
||||
msgstr "Document/gebruiker relatie"
|
||||
|
||||
#: build/lib/core/models.py:1075 core/models.py:1075
|
||||
#: build/lib/core/models.py:1029 core/models.py:1029
|
||||
msgid "Document/user relations"
|
||||
msgstr "Document/gebruiker relaties"
|
||||
|
||||
#: build/lib/core/models.py:1081 core/models.py:1081
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
msgid "This user is already in this document."
|
||||
msgstr "De gebruiker is al in dit document."
|
||||
|
||||
#: build/lib/core/models.py:1087 core/models.py:1087
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Het team is al in dit document."
|
||||
|
||||
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
|
||||
#: core/models.py:1093 core/models.py:1241
|
||||
#: 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 "Een gebruiker of team moet gekozen worden, maar niet beide."
|
||||
|
||||
#: build/lib/core/models.py:1155 core/models.py:1155
|
||||
#: build/lib/core/models.py:1075 core/models.py:1075
|
||||
msgid "description"
|
||||
msgstr "omschrijving"
|
||||
|
||||
#: build/lib/core/models.py:1156 core/models.py:1156
|
||||
#: build/lib/core/models.py:1076 core/models.py:1076
|
||||
msgid "code"
|
||||
msgstr "code"
|
||||
|
||||
#: build/lib/core/models.py:1157 core/models.py:1157
|
||||
#: build/lib/core/models.py:1077 core/models.py:1077
|
||||
msgid "css"
|
||||
msgstr "css"
|
||||
|
||||
#: build/lib/core/models.py:1159 core/models.py:1159
|
||||
#: build/lib/core/models.py:1079 core/models.py:1079
|
||||
msgid "public"
|
||||
msgstr "publiek"
|
||||
|
||||
#: build/lib/core/models.py:1161 core/models.py:1161
|
||||
#: build/lib/core/models.py:1081 core/models.py:1081
|
||||
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:1167 core/models.py:1167
|
||||
#: build/lib/core/models.py:1087 core/models.py:1087
|
||||
msgid "Template"
|
||||
msgstr "Template"
|
||||
|
||||
#: build/lib/core/models.py:1168 core/models.py:1168
|
||||
#: build/lib/core/models.py:1088 core/models.py:1088
|
||||
msgid "Templates"
|
||||
msgstr "Templates"
|
||||
|
||||
#: build/lib/core/models.py:1222 core/models.py:1222
|
||||
#: build/lib/core/models.py:1142 core/models.py:1142
|
||||
msgid "Template/user relation"
|
||||
msgstr "Template/gebruiker relatie"
|
||||
|
||||
#: build/lib/core/models.py:1223 core/models.py:1223
|
||||
#: build/lib/core/models.py:1143 core/models.py:1143
|
||||
msgid "Template/user relations"
|
||||
msgstr "Template/gebruiker relaties"
|
||||
|
||||
#: build/lib/core/models.py:1229 core/models.py:1229
|
||||
#: build/lib/core/models.py:1149 core/models.py:1149
|
||||
msgid "This user is already in this template."
|
||||
msgstr "De gebruiker bestaat al in dit template."
|
||||
|
||||
#: build/lib/core/models.py:1235 core/models.py:1235
|
||||
#: build/lib/core/models.py:1155 core/models.py:1155
|
||||
msgid "This team is already in this template."
|
||||
msgstr "Het team bestaat al in dit template."
|
||||
|
||||
#: build/lib/core/models.py:1258 core/models.py:1258
|
||||
#: build/lib/core/models.py:1178 core/models.py:1178
|
||||
msgid "email address"
|
||||
msgstr "email adres"
|
||||
|
||||
#: build/lib/core/models.py:1277 core/models.py:1277
|
||||
#: build/lib/core/models.py:1197 core/models.py:1197
|
||||
msgid "Document invitation"
|
||||
msgstr "Document uitnodiging"
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1198 core/models.py:1198
|
||||
msgid "Document invitations"
|
||||
msgstr "Document uitnodigingen"
|
||||
|
||||
#: build/lib/core/models.py:1298 core/models.py:1298
|
||||
#: build/lib/core/models.py:1218 core/models.py:1218
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Deze email is al geassocieerd met een geregistreerde gebruiker."
|
||||
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
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 ""
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
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 "
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
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 ""
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
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 ""
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
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 倾力打造。 "
|
||||
|
||||
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "impress"
|
||||
version = "3.2.1"
|
||||
version = "3.1.0"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -25,20 +25,20 @@ license = { file = "LICENSE" }
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"beautifulsoup4==4.13.4",
|
||||
"boto3==1.38.13",
|
||||
"beautifulsoup4==4.13.3",
|
||||
"boto3==1.37.24",
|
||||
"Brotli==1.1.0",
|
||||
"celery[redis]==5.5.2",
|
||||
"celery[redis]==5.5.0",
|
||||
"django-configurations==2.5.1",
|
||||
"django-cors-headers==4.7.0",
|
||||
"django-countries==7.6.1",
|
||||
"django-filter==25.1",
|
||||
"django-lasuite[all]==0.0.8",
|
||||
"django-parler==2.3",
|
||||
"redis==5.2.1",
|
||||
"django-redis==5.4.0",
|
||||
"django-storages[s3]==1.14.6",
|
||||
"django-storages[s3]==1.14.5",
|
||||
"django-timezone-field>=5.1",
|
||||
"django==5.1.9",
|
||||
"django==5.1.8",
|
||||
"django-treebeard==4.7.1",
|
||||
"djangorestframework==3.16.0",
|
||||
"drf_spectacular==0.28.0",
|
||||
@@ -47,18 +47,17 @@ dependencies = [
|
||||
"factory_boy==3.3.3",
|
||||
"gunicorn==23.0.0",
|
||||
"jsonschema==4.23.0",
|
||||
"lxml==5.4.0",
|
||||
"markdown==3.8",
|
||||
"lxml==5.3.1",
|
||||
"markdown==3.7",
|
||||
"mozilla-django-oidc==4.0.1",
|
||||
"nested-multipart-parser==1.5.0",
|
||||
"openai==1.78.1",
|
||||
"psycopg[binary]==3.2.8",
|
||||
"pycrdt==0.12.15",
|
||||
"openai==1.70.0",
|
||||
"psycopg[binary]==3.2.6",
|
||||
"pycrdt==0.12.10",
|
||||
"PyJWT==2.10.1",
|
||||
"python-magic==0.4.27",
|
||||
"redis<6.0.0",
|
||||
"requests==2.32.3",
|
||||
"sentry-sdk==2.28.0",
|
||||
"sentry-sdk==2.25.0",
|
||||
"whitenoise==6.9.0",
|
||||
]
|
||||
|
||||
@@ -70,22 +69,22 @@ dependencies = [
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"django-extensions==4.1",
|
||||
"django-test-migrations==1.5.0",
|
||||
"drf-spectacular-sidecar==2025.5.1",
|
||||
"django-extensions==3.2.3",
|
||||
"django-test-migrations==1.4.0",
|
||||
"drf-spectacular-sidecar==2025.3.1",
|
||||
"freezegun==1.5.1",
|
||||
"ipdb==0.13.13",
|
||||
"ipython==9.2.0",
|
||||
"ipython==9.0.2",
|
||||
"pyfakefs==5.8.0",
|
||||
"pylint-django==2.6.1",
|
||||
"pylint==3.3.7",
|
||||
"pytest-cov==6.1.1",
|
||||
"pytest-django==4.11.1",
|
||||
"pylint==3.3.6",
|
||||
"pytest-cov==6.0.0",
|
||||
"pytest-django==4.10.0",
|
||||
"pytest==8.3.5",
|
||||
"pytest-icdiff==0.9",
|
||||
"pytest-xdist==3.6.1",
|
||||
"responses==0.25.7",
|
||||
"ruff==0.11.9",
|
||||
"ruff==0.11.2",
|
||||
"types-requests==2.32.0.20250328",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24-alpine AS frontend-deps
|
||||
FROM node:20-alpine AS frontend-deps
|
||||
|
||||
WORKDIR /home/frontend/
|
||||
|
||||
@@ -39,13 +39,10 @@ ENV NEXT_PUBLIC_API_ORIGIN=${API_ORIGIN}
|
||||
ARG SW_DEACTIVATED
|
||||
ENV NEXT_PUBLIC_SW_DEACTIVATED=${SW_DEACTIVATED}
|
||||
|
||||
ARG PUBLISH_AS_MIT
|
||||
ENV NEXT_PUBLIC_PUBLISH_AS_MIT=${PUBLISH_AS_MIT}
|
||||
|
||||
RUN yarn build
|
||||
|
||||
# ---- Front-end image ----
|
||||
FROM nginxinc/nginx-unprivileged:1.27-alpine AS frontend-production
|
||||
FROM nginxinc/nginx-unprivileged:1.26-alpine AS frontend-production
|
||||
|
||||
# Un-privileged user running the application
|
||||
ARG DOCKER_USER
|
||||
|
||||
@@ -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"],
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ export const CONFIG = {
|
||||
ENVIRONMENT: 'development',
|
||||
FRONTEND_CSS_URL: null,
|
||||
FRONTEND_HOMEPAGE_FEATURE_ENABLED: true,
|
||||
FRONTEND_FOOTER_FEATURE_ENABLED: true,
|
||||
FRONTEND_THEME: 'default',
|
||||
MEDIA_BASE_URL: 'http://localhost:8083',
|
||||
LANGUAGES: [
|
||||
@@ -14,12 +15,10 @@ export const CONFIG = {
|
||||
['fr-fr', 'Français'],
|
||||
['de-de', 'Deutsch'],
|
||||
['nl-nl', 'Nederlands'],
|
||||
['es-es', 'Español'],
|
||||
],
|
||||
LANGUAGE_CODE: 'en-us',
|
||||
POSTHOG_KEY: {},
|
||||
SENTRY_DSN: null,
|
||||
theme_customization: {},
|
||||
};
|
||||
|
||||
export const keyCloakSignIn = async (
|
||||
@@ -79,7 +78,6 @@ export const createDoc = async (
|
||||
});
|
||||
|
||||
const input = page.getByLabel('doc title input');
|
||||
await expect(input).toBeVisible();
|
||||
await expect(input).toHaveText('');
|
||||
await input.click();
|
||||
|
||||
|
||||
@@ -79,14 +79,11 @@ test.describe('Config', () => {
|
||||
|
||||
test('it checks that collaboration server is configured from config endpoint', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await page.goto('/');
|
||||
|
||||
void page
|
||||
.getByRole('button', {
|
||||
name: 'New doc',
|
||||
})
|
||||
.click();
|
||||
void createDoc(page, 'doc-collaboration', browserName, 1);
|
||||
|
||||
const webSocket = await page.waitForEvent('websocket', (webSocket) => {
|
||||
return webSocket.url().includes('ws://localhost:4444/collaboration/ws/');
|
||||
|
||||
@@ -452,41 +452,4 @@ test.describe('Doc Editor', () => {
|
||||
const svgBuffer = await cs.toBuffer(await download.createReadStream());
|
||||
expect(svgBuffer.toString()).toContain('Hello svg');
|
||||
});
|
||||
|
||||
test('it checks if callout custom block', async ({ page, browserName }) => {
|
||||
await createDoc(page, 'doc-toolbar', browserName, 1);
|
||||
|
||||
const editor = page.locator('.ProseMirror');
|
||||
await editor.click();
|
||||
await page.locator('.bn-block-outer').last().fill('/');
|
||||
await page.getByText('Add a callout block').click();
|
||||
|
||||
const calloutBlock = page
|
||||
.locator('div[data-content-type="callout"]')
|
||||
.first();
|
||||
|
||||
await expect(calloutBlock).toBeVisible();
|
||||
|
||||
await calloutBlock.locator('.inline-content').fill('example text');
|
||||
|
||||
await expect(page.locator('.bn-block').first()).toHaveAttribute(
|
||||
'data-background-color',
|
||||
'yellow',
|
||||
);
|
||||
|
||||
const emojiButton = calloutBlock.getByRole('button');
|
||||
await expect(emojiButton).toHaveText('💡');
|
||||
await emojiButton.click();
|
||||
await page.locator('button[aria-label="⚠️"]').click();
|
||||
await expect(emojiButton).toHaveText('⚠️');
|
||||
|
||||
await page.locator('.bn-side-menu > button').last().click();
|
||||
await page.locator('.mantine-Menu-dropdown > button').last().click();
|
||||
await page.locator('.bn-color-picker-dropdown > button').last().click();
|
||||
|
||||
await expect(page.locator('.bn-block').first()).toHaveAttribute(
|
||||
'data-background-color',
|
||||
'pink',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,7 +79,7 @@ test.describe('Doc Export', () => {
|
||||
})
|
||||
.click();
|
||||
|
||||
void page
|
||||
await 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();
|
||||
|
||||
void page
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Download',
|
||||
})
|
||||
@@ -141,7 +141,7 @@ test.describe('Doc Export', () => {
|
||||
|
||||
/**
|
||||
* This test tell us that the export to pdf is working with images
|
||||
* but it does not tell us if the images are being displayed correctly
|
||||
* but it does not tell us if the images are beeing displayed correctly
|
||||
* in the pdf.
|
||||
*
|
||||
* TODO: Check if the images are displayed correctly in the pdf
|
||||
@@ -206,7 +206,7 @@ test.describe('Doc Export', () => {
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
void page
|
||||
await 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('Quote or excerpt').click();
|
||||
await page.getByText('Add a quote block').click();
|
||||
|
||||
await expect(
|
||||
editor.locator('.bn-block-content[data-content-type="quote"]'),
|
||||
@@ -254,7 +254,7 @@ test.describe('Doc Export', () => {
|
||||
})
|
||||
.click();
|
||||
|
||||
void page
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Download',
|
||||
})
|
||||
@@ -298,7 +298,7 @@ test.describe('Doc Export', () => {
|
||||
})
|
||||
.click();
|
||||
|
||||
void page
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Download',
|
||||
})
|
||||
|
||||
@@ -152,7 +152,12 @@ 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();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
@@ -103,9 +101,8 @@ test.describe('Doc Routing: Not loggued', () => {
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
const uuid = crypto.randomUUID();
|
||||
await mockedDocument(page, { link_reach: 'public', id: uuid });
|
||||
await page.goto(`/docs/${uuid}/`);
|
||||
await mockedDocument(page, { link_reach: 'public' });
|
||||
await page.goto('/docs/mocked-document-id/');
|
||||
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Login' }).click();
|
||||
await keyCloakSignIn(page, browserName, false);
|
||||
|
||||
@@ -54,11 +54,18 @@ test.describe.serial('Language', () => {
|
||||
}) => {
|
||||
// Helper function to intercept and assert 404 response
|
||||
const check404Response = async (expectedDetail: string) => {
|
||||
const interceptedBackendResponse = await page.request.get(
|
||||
'http://localhost:8071/api/v1.0/documents/non-existent-doc-uuid/',
|
||||
const expectedBackendResponse = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/api') &&
|
||||
response.url().includes('non-existent-doc-uuid') &&
|
||||
response.status() === 404,
|
||||
);
|
||||
|
||||
// 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,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-e2e",
|
||||
"version": "3.2.1",
|
||||
"version": "3.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .ts",
|
||||
@@ -12,9 +12,9 @@
|
||||
"test:ui::chromium": "yarn test:ui --project=chromium"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.52.0",
|
||||
"@playwright/test": "1.50.1",
|
||||
"@types/node": "*",
|
||||
"@types/pdf-parse": "1.1.5",
|
||||
"@types/pdf-parse": "1.1.4",
|
||||
"eslint-config-impress": "*",
|
||||
"typescript": "*"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
NEXT_PUBLIC_API_ORIGIN=
|
||||
NEXT_PUBLIC_SW_DEACTIVATED=
|
||||
NEXT_PUBLIC_PUBLISH_AS_MIT=true
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
NEXT_PUBLIC_API_ORIGIN=http://localhost:8071
|
||||
NEXT_PUBLIC_PUBLISH_AS_MIT=false
|
||||
NEXT_PUBLIC_SW_DEACTIVATED=true
|
||||
|
||||
@@ -9,8 +9,8 @@ server {
|
||||
try_files $uri index.html $uri/ =404;
|
||||
}
|
||||
|
||||
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;
|
||||
location /docs/ {
|
||||
error_page 404 /docs/[id]/;
|
||||
}
|
||||
|
||||
error_page 404 /404.html;
|
||||
|
||||
@@ -48,7 +48,10 @@ const nextConfig = {
|
||||
swDest: '../public/service-worker.js',
|
||||
include: [
|
||||
({ asset }) => {
|
||||
return !!asset.name.match(/.*(static).*/);
|
||||
if (asset.name.match(/.*(static).*/)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-impress",
|
||||
"version": "3.2.1",
|
||||
"version": "3.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -15,74 +15,70 @@
|
||||
"test:watch": "jest --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"@emoji-mart/data": "1.2.1",
|
||||
"@emoji-mart/react": "1.1.1",
|
||||
"@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",
|
||||
"@fontsource/material-icons": "5.2.5",
|
||||
"@gouvfr-lasuite/integration": "1.0.3",
|
||||
"@gouvfr-lasuite/ui-kit": "0.4.1",
|
||||
"@gouvfr-lasuite/integration": "1.0.2",
|
||||
"@gouvfr-lasuite/ui-kit": "0.1.3",
|
||||
"@hocuspocus/provider": "2.15.2",
|
||||
"@openfun/cunningham-react": "3.0.0",
|
||||
"@react-pdf/renderer": "4.3.0",
|
||||
"@sentry/nextjs": "9.15.0",
|
||||
"@tanstack/react-query": "5.75.4",
|
||||
"@react-pdf/renderer": "4.1.6",
|
||||
"@sentry/nextjs": "9.3.0",
|
||||
"@tanstack/react-query": "5.67.1",
|
||||
"canvg": "4.0.3",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
"cmdk": "1.0.4",
|
||||
"crisp-sdk-web": "1.0.25",
|
||||
"docx": "9.4.1",
|
||||
"emoji-mart": "5.6.0",
|
||||
"i18next": "25.1.1",
|
||||
"i18next-browser-languagedetector": "8.1.0",
|
||||
"docx": "9.1.1",
|
||||
"i18next": "24.2.2",
|
||||
"i18next-browser-languagedetector": "8.0.4",
|
||||
"idb": "8.0.2",
|
||||
"lodash": "4.17.21",
|
||||
"luxon": "3.6.1",
|
||||
"next": "15.3.1",
|
||||
"posthog-js": "1.239.1",
|
||||
"luxon": "3.5.0",
|
||||
"next": "15.2.4",
|
||||
"posthog-js": "1.227.0",
|
||||
"react": "*",
|
||||
"react-aria-components": "1.8.0",
|
||||
"react-aria-components": "1.6.0",
|
||||
"react-dom": "*",
|
||||
"react-i18next": "15.5.1",
|
||||
"react-intersection-observer": "9.16.0",
|
||||
"react-i18next": "15.4.1",
|
||||
"react-intersection-observer": "9.15.1",
|
||||
"react-select": "5.10.1",
|
||||
"styled-components": "6.1.17",
|
||||
"styled-components": "6.1.15",
|
||||
"use-debounce": "10.0.4",
|
||||
"y-protocols": "1.0.6",
|
||||
"yjs": "*",
|
||||
"zustand": "5.0.4"
|
||||
"zustand": "5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/webpack": "8.1.0",
|
||||
"@tanstack/react-query-devtools": "5.75.4",
|
||||
"@tanstack/react-query-devtools": "5.67.1",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@testing-library/react": "16.2.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/lodash": "4.17.16",
|
||||
"@types/luxon": "3.6.2",
|
||||
"@types/luxon": "3.4.2",
|
||||
"@types/node": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "16.5.0",
|
||||
"dotenv": "16.4.7",
|
||||
"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.19.1",
|
||||
"stylelint-config-standard": "38.0.0",
|
||||
"stylelint": "16.15.0",
|
||||
"stylelint-config-standard": "37.0.0",
|
||||
"stylelint-prettier": "5.0.3",
|
||||
"typescript": "*",
|
||||
"webpack": "5.99.7",
|
||||
"webpack": "5.98.0",
|
||||
"workbox-webpack-plugin": "7.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
121
src/frontend/apps/impress/public/contents/footer-demo.json
Normal file
121
src/frontend/apps/impress/public/contents/footer-demo.json
Normal file
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"default": {
|
||||
"externalLinks": [
|
||||
{
|
||||
"label": "Github",
|
||||
"href": "https://github.com/suitenumerique/docs/"
|
||||
},
|
||||
{
|
||||
"label": "DINUM",
|
||||
"href": "https://www.numerique.gouv.fr/dinum/"
|
||||
},
|
||||
{
|
||||
"label": "ZenDiS",
|
||||
"href": "https://zendis.de/"
|
||||
},
|
||||
{
|
||||
"label": "BlockNote.js",
|
||||
"href": "https://www.blocknotejs.org/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Unless otherwise stated, all content on this site is under",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Legal Notice",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Personal data and cookies",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Accessibility",
|
||||
"href": "#"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Unless otherwise stated, all content on this site is under",
|
||||
"link": {
|
||||
"label": "licence MIT",
|
||||
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Mentions légales",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Données personnelles et cookies",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Accessibilité",
|
||||
"href": "#"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Sauf mention contraire, tout le contenu de ce site est sous",
|
||||
"link": {
|
||||
"label": "licence MIT",
|
||||
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Impressum",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Personenbezogene Daten und Cookies",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Barrierefreiheit",
|
||||
"href": "#"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Sofern nicht anders angegeben, steht der gesamte Inhalt dieser Website unter",
|
||||
"link": {
|
||||
"label": "licence MIT",
|
||||
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Wettelijke bepalingen",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Persoonlijke gegevens en cookies",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Toegankelijkheid",
|
||||
"href": "#"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Tenzij anders vermeld, is alle inhoud van deze site ondergebracht onder",
|
||||
"link": {
|
||||
"label": "licence MIT",
|
||||
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
135
src/frontend/apps/impress/public/contents/footer-dsfr.json
Normal file
135
src/frontend/apps/impress/public/contents/footer-dsfr.json
Normal file
@@ -0,0 +1,135 @@
|
||||
{
|
||||
"default": {
|
||||
"externalLinks": [
|
||||
{
|
||||
"label": "legifrance.gouv.fr",
|
||||
"href": "https://legifrance.gouv.fr/"
|
||||
},
|
||||
{
|
||||
"label": "info.gouv.fr",
|
||||
"href": "https://info.gouv.fr/"
|
||||
},
|
||||
{
|
||||
"label": "service-public.fr",
|
||||
"href": "https://service-public.fr/"
|
||||
},
|
||||
{
|
||||
"label": "data.gouv.fr",
|
||||
"href": "https://data.gouv.fr/"
|
||||
}
|
||||
],
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Legal Notice",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/4db744e3-5b47-4ed9-b9e7-d4312318bbce/"
|
||||
},
|
||||
{
|
||||
"label": "Personal data and cookies",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/eb863389-a5e5-4d18-879d-149a1122380e/"
|
||||
},
|
||||
{
|
||||
"label": "Accessibility",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/9694e570-1427-4ef7-b0a0-c3e894360e1b/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Unless otherwise stated, all content on this site is under",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Legal Notice",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/4db744e3-5b47-4ed9-b9e7-d4312318bbce/"
|
||||
},
|
||||
{
|
||||
"label": "Personal data and cookies",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/eb863389-a5e5-4d18-879d-149a1122380e/"
|
||||
},
|
||||
{
|
||||
"label": "Accessibility",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/9694e570-1427-4ef7-b0a0-c3e894360e1b/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Unless otherwise stated, all content on this site is under",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Mentions légales",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/4db744e3-5b47-4ed9-b9e7-d4312318bbce/"
|
||||
},
|
||||
{
|
||||
"label": "Données personnelles et cookies",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/eb863389-a5e5-4d18-879d-149a1122380e/"
|
||||
},
|
||||
{
|
||||
"label": "Accessibilité",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/9694e570-1427-4ef7-b0a0-c3e894360e1b/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Sauf mention contraire, tout le contenu de ce site est sous",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Impressum",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/4db744e3-5b47-4ed9-b9e7-d4312318bbce/"
|
||||
},
|
||||
{
|
||||
"label": "Personenbezogene Daten und Cookies",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/eb863389-a5e5-4d18-879d-149a1122380e/"
|
||||
},
|
||||
{
|
||||
"label": "Barrierefreiheit",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/9694e570-1427-4ef7-b0a0-c3e894360e1b/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Sofern nicht anders angegeben, steht der gesamte Inhalt dieser Website unter",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Wettelijke bepalingen",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/4db744e3-5b47-4ed9-b9e7-d4312318bbce/"
|
||||
},
|
||||
{
|
||||
"label": "Persoonlijke gegevens en cookies",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/eb863389-a5e5-4d18-879d-149a1122380e/"
|
||||
},
|
||||
{
|
||||
"label": "Toegankelijkheid",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/9694e570-1427-4ef7-b0a0-c3e894360e1b/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Tenzij anders vermeld, is alle inhoud van deze site ondergebracht onder",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,17 @@
|
||||
/**
|
||||
* Generic interface for representing an API error structure.
|
||||
*
|
||||
* @template T - Optional type of additional data returned with the error.
|
||||
*/
|
||||
interface IAPIError<T = unknown> {
|
||||
/** HTTP status code or API-defined error code */
|
||||
status: number;
|
||||
/** Optional list of error causes (e.g., validation issues) */
|
||||
cause?: string[];
|
||||
/** Optional extra data provided with the error */
|
||||
data?: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom error class for representing API errors.
|
||||
* Extends the native Error object with additional context such as HTTP status,
|
||||
* causes, and extra data returned by the API.
|
||||
*
|
||||
* @template T - Optional type of the `data` field
|
||||
*/
|
||||
export class APIError<T = unknown> extends Error implements IAPIError<T> {
|
||||
public status: IAPIError['status'];
|
||||
public cause?: IAPIError['cause'];
|
||||
public data?: IAPIError<T>['data'];
|
||||
|
||||
/**
|
||||
* Constructs a new APIError instance.
|
||||
*
|
||||
* @param message - The human-readable error message.
|
||||
* @param status - The HTTP status code or equivalent.
|
||||
* @param cause - (Optional) List of strings describing error causes.
|
||||
* @param data - (Optional) Any additional data returned by the API.
|
||||
*/
|
||||
constructor(message: string, { status, cause, data }: IAPIError<T>) {
|
||||
super(message);
|
||||
|
||||
this.name = 'APIError';
|
||||
this.status = status;
|
||||
this.cause = cause;
|
||||
@@ -41,12 +19,6 @@ export class APIError<T = unknown> extends Error implements IAPIError<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for checking if a value is an instance of APIError.
|
||||
*
|
||||
* @param error - The value to check.
|
||||
* @returns True if the value is an instance of APIError.
|
||||
*/
|
||||
export const isAPIError = (error: unknown): error is APIError => {
|
||||
return error instanceof APIError;
|
||||
};
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { APIError, isAPIError } from '@/api';
|
||||
|
||||
describe('APIError', () => {
|
||||
it('should correctly instantiate with required fields', () => {
|
||||
const error = new APIError('Something went wrong', { status: 500 });
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error).toBeInstanceOf(APIError);
|
||||
expect(error.message).toBe('Something went wrong');
|
||||
expect(error.status).toBe(500);
|
||||
expect(error.cause).toBeUndefined();
|
||||
expect(error.data).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should correctly instantiate with all fields', () => {
|
||||
const details = { field: 'email' };
|
||||
const error = new APIError('Validation failed', {
|
||||
status: 400,
|
||||
cause: ['Invalid email format'],
|
||||
data: details,
|
||||
});
|
||||
|
||||
expect(error.name).toBe('APIError');
|
||||
expect(error.status).toBe(400);
|
||||
expect(error.cause).toEqual(['Invalid email format']);
|
||||
expect(error.data).toEqual(details);
|
||||
});
|
||||
|
||||
it('should be detected by isAPIError type guard', () => {
|
||||
const error = new APIError('Unauthorized', { status: 401 });
|
||||
const notAnError = { message: 'Fake error' };
|
||||
|
||||
expect(isAPIError(error)).toBe(true);
|
||||
expect(isAPIError(notAnError)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import { baseApiUrl } from '@/api';
|
||||
|
||||
describe('config', () => {
|
||||
it('constructs URL with default version', () => {
|
||||
expect(baseApiUrl()).toBe('http://test.jest/api/v1.0/');
|
||||
});
|
||||
|
||||
it('constructs URL with custom version', () => {
|
||||
expect(baseApiUrl('2.0')).toBe('http://test.jest/api/v2.0/');
|
||||
});
|
||||
|
||||
it('uses env origin if available', () => {
|
||||
process.env.NEXT_PUBLIC_API_ORIGIN = 'https://env.example.com';
|
||||
expect(baseApiUrl('3.0')).toBe('https://env.example.com/api/v3.0/');
|
||||
});
|
||||
});
|
||||
@@ -36,13 +36,4 @@ describe('fetchAPI', () => {
|
||||
|
||||
expect(fetchMock.lastUrl()).toEqual('http://test.jest/api/v2.0/some/url');
|
||||
});
|
||||
|
||||
it('removes Content-Type header when withoutContentType is true', async () => {
|
||||
fetchMock.mock('http://test.jest/api/v1.0/some/url', 200);
|
||||
|
||||
await fetchAPI('some/url', { withoutContentType: true });
|
||||
|
||||
const options = fetchMock.lastOptions();
|
||||
expect(options?.headers).not.toHaveProperty('Content-Type');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
|
||||
import { useAPIInfiniteQuery } from '@/api';
|
||||
|
||||
interface DummyItem {
|
||||
id: number;
|
||||
}
|
||||
|
||||
interface DummyResponse {
|
||||
results: DummyItem[];
|
||||
next?: string;
|
||||
}
|
||||
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient();
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('helpers', () => {
|
||||
it('fetches and paginates correctly', async () => {
|
||||
const mockAPI = jest
|
||||
.fn<Promise<DummyResponse>, [{ page: number; query: string }]>()
|
||||
.mockResolvedValueOnce({
|
||||
results: [{ id: 1 }],
|
||||
next: 'url?page=2',
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
results: [{ id: 2 }],
|
||||
next: undefined,
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useAPIInfiniteQuery('test-key', mockAPI, { query: 'test' }),
|
||||
{ wrapper: createWrapper() },
|
||||
);
|
||||
|
||||
// Wait for first page
|
||||
await waitFor(() => {
|
||||
expect(result.current.data?.pages[0].results[0].id).toBe(1);
|
||||
});
|
||||
|
||||
// Fetch next page
|
||||
await result.current.fetchNextPage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data?.pages.length).toBe(2);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data?.pages[1].results[0].id).toBe(2);
|
||||
});
|
||||
|
||||
expect(mockAPI).toHaveBeenCalledWith({ query: 'test', page: 1 });
|
||||
expect(mockAPI).toHaveBeenCalledWith({ query: 'test', page: 2 });
|
||||
});
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
import { errorCauses, getCSRFToken } from '@/api';
|
||||
|
||||
describe('utils', () => {
|
||||
describe('errorCauses', () => {
|
||||
const createMockResponse = (jsonData: any, status = 400): Response => {
|
||||
return {
|
||||
status,
|
||||
json: () => jsonData,
|
||||
} as unknown as Response;
|
||||
};
|
||||
|
||||
it('parses multiple string causes from error body', async () => {
|
||||
const mockResponse = createMockResponse(
|
||||
{
|
||||
field: ['error message 1', 'error message 2'],
|
||||
},
|
||||
400,
|
||||
);
|
||||
|
||||
const result = await errorCauses(mockResponse, { context: 'login' });
|
||||
|
||||
expect(result.status).toBe(400);
|
||||
expect(result.cause).toEqual(['error message 1', 'error message 2']);
|
||||
expect(result.data).toEqual({ context: 'login' });
|
||||
});
|
||||
|
||||
it('returns undefined causes if no JSON body', async () => {
|
||||
const mockResponse = createMockResponse(null, 500);
|
||||
|
||||
const result = await errorCauses(mockResponse);
|
||||
|
||||
expect(result.status).toBe(500);
|
||||
expect(result.cause).toBeUndefined();
|
||||
expect(result.data).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCSRFToken', () => {
|
||||
it('extracts csrftoken from document.cookie', () => {
|
||||
Object.defineProperty(document, 'cookie', {
|
||||
writable: true,
|
||||
value: 'sessionid=xyz; csrftoken=abc123; theme=dark',
|
||||
});
|
||||
|
||||
expect(getCSRFToken()).toBe('abc123');
|
||||
});
|
||||
|
||||
it('returns undefined if csrftoken is not present', () => {
|
||||
Object.defineProperty(document, 'cookie', {
|
||||
writable: true,
|
||||
value: 'sessionid=xyz; theme=dark',
|
||||
});
|
||||
|
||||
expect(getCSRFToken()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,22 +1,6 @@
|
||||
/**
|
||||
* Returns the base URL for the backend API.
|
||||
*
|
||||
* Priority:
|
||||
* 1. Uses NEXT_PUBLIC_API_ORIGIN from environment variables if defined.
|
||||
* 2. Falls back to the browser's window.location.origin if in a browser environment.
|
||||
* 3. Defaults to an empty string if executed in a non-browser environment without the env variable.
|
||||
*
|
||||
* @returns The backend base URL as a string.
|
||||
*/
|
||||
export const backendUrl = () =>
|
||||
process.env.NEXT_PUBLIC_API_ORIGIN ||
|
||||
(typeof window !== 'undefined' ? window.location.origin : '');
|
||||
|
||||
/**
|
||||
* Constructs the full base API URL, including the versioned path (e.g., `/api/v1.0/`).
|
||||
*
|
||||
* @param apiVersion - The version of the API (defaults to '1.0').
|
||||
* @returns The full versioned API base URL as a string.
|
||||
*/
|
||||
export const baseApiUrl = (apiVersion: string = '1.0') =>
|
||||
`${backendUrl()}/api/v${apiVersion}/`;
|
||||
|
||||
@@ -23,9 +23,11 @@ export const fetchAPI = async (
|
||||
delete headers?.['Content-Type' as keyof typeof headers];
|
||||
}
|
||||
|
||||
return await fetch(apiUrl, {
|
||||
const response = await fetch(apiUrl, {
|
||||
...init,
|
||||
credentials: 'include',
|
||||
headers,
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
@@ -22,17 +22,9 @@ export type DefinedInitialDataInfiniteOptionsAPI<
|
||||
>;
|
||||
|
||||
/**
|
||||
* Custom React hook that wraps React Query's `useInfiniteQuery` for paginated API requests.
|
||||
*
|
||||
* @template T - Type of the request parameters.
|
||||
* @template Q - Type of the API response, which must include an optional `next` field for pagination.
|
||||
*
|
||||
* @param {string} key - Unique key to identify the query in the cache.
|
||||
* @param {(props: T & { page: number }) => Promise<Q>} api - Function that fetches paginated data from the API. It receives the params merged with a page number.
|
||||
* @param {T} param - Static parameters to send with every API request (excluding the page number).
|
||||
* @param {DefinedInitialDataInfiniteOptionsAPI<Q>} [queryConfig] - Optional configuration passed to `useInfiniteQuery` (e.g., stale time, cache time).
|
||||
*
|
||||
* @returns Return value of `useInfiniteQuery`, including data, loading state, fetchNextPage, etc.
|
||||
* @param param Used for infinite scroll pagination
|
||||
* @param queryConfig
|
||||
* @returns
|
||||
*/
|
||||
export const useAPIInfiniteQuery = <T, Q extends { next?: APIList<Q>['next'] }>(
|
||||
key: string,
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
/**
|
||||
* Generic interface representing a paginated API response.
|
||||
*
|
||||
* Commonly used for endpoints that return list results with pagination metadata.
|
||||
*
|
||||
* @template T - The type of items in the `results` array.
|
||||
*/
|
||||
export interface APIList<T> {
|
||||
/** Total number of items across all pages */
|
||||
count: number;
|
||||
|
||||
/** URL to the next page of results, if available (can be null or undefined) */
|
||||
next?: string | null;
|
||||
|
||||
/** URL to the previous page of results, if available (can be null or undefined) */
|
||||
previous?: string | null;
|
||||
|
||||
/** The list of items for the current page */
|
||||
results: T[];
|
||||
}
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
/**
|
||||
* Extracts error information from an HTTP `Response` object.
|
||||
*
|
||||
* This is typically used to parse structured error responses from an API
|
||||
* and normalize them into a consistent format with `status`, `cause`, and optional `data`.
|
||||
*
|
||||
* @param response - The HTTP response object from `fetch()`.
|
||||
* @param data - Optional custom data to include with the error output.
|
||||
* @returns An object containing:
|
||||
* - `status`: HTTP status code from the response
|
||||
* - `cause`: A flattened list of error messages, or undefined if no body
|
||||
* - `data`: The optional data passed in
|
||||
*/
|
||||
export const errorCauses = async (response: Response, data?: unknown) => {
|
||||
const errorsBody = (await response.json()) as Record<
|
||||
string,
|
||||
@@ -31,11 +18,7 @@ export const errorCauses = async (response: Response, data?: unknown) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the CSRF token from the browser's cookies.
|
||||
*
|
||||
* Assumes the CSRF token is stored as a cookie named "csrftoken".
|
||||
*
|
||||
* @returns The CSRF token string if found, otherwise `undefined`.
|
||||
* Retrieves the CSRF token from the document's cookies.
|
||||
*/
|
||||
export function getCSRFToken() {
|
||||
return document.cookie
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -35,7 +35,9 @@ export const DropdownMenu = ({
|
||||
label,
|
||||
topMessage,
|
||||
}: PropsWithChildren<DropdownMenuProps>) => {
|
||||
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||
const theme = useCunninghamTheme();
|
||||
const spacings = theme.spacingsTokens();
|
||||
const colors = theme.colorsTokens();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const blockButtonRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -118,11 +120,11 @@ export const DropdownMenu = ({
|
||||
key={option.label}
|
||||
$align="center"
|
||||
$justify="space-between"
|
||||
$background={colorsTokens['greyscale-000']}
|
||||
$color={colorsTokens['primary-600']}
|
||||
$background={colors['greyscale-000']}
|
||||
$color={colors['primary-600']}
|
||||
$padding={{ vertical: 'xs', horizontal: 'base' }}
|
||||
$width="100%"
|
||||
$gap={spacingsTokens['base']}
|
||||
$gap={spacings['base']}
|
||||
$css={css`
|
||||
border: none;
|
||||
${index === 0 &&
|
||||
@@ -146,11 +148,7 @@ export const DropdownMenu = ({
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Box
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$gap={spacingsTokens['base']}
|
||||
>
|
||||
<Box $direction="row" $align="center" $gap={spacings['base']}>
|
||||
{option.icon && (
|
||||
<Icon
|
||||
$size="20px"
|
||||
|
||||
@@ -10,7 +10,7 @@ type TextSizes = keyof typeof sizes;
|
||||
|
||||
export interface TextProps extends BoxProps {
|
||||
as?: 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
||||
$ellipsis?: boolean;
|
||||
$elipsis?: boolean;
|
||||
$weight?: CSSProperties['fontWeight'];
|
||||
$textAlign?: CSSProperties['textAlign'];
|
||||
$size?: TextSizes | (string & {});
|
||||
@@ -50,8 +50,8 @@ export const TextStyled = styled(Box)<TextProps>`
|
||||
${({ $theme, $variation }) =>
|
||||
`color: var(--c--theme--colors--${$theme}-${$variation});`}
|
||||
${({ $color }) => $color && `color: ${$color};`}
|
||||
${({ $ellipsis }) =>
|
||||
$ellipsis &&
|
||||
${({ $elipsis }) =>
|
||||
$elipsis &&
|
||||
`white-space: nowrap; overflow: hidden; text-overflow: ellipsis;`}
|
||||
`;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ export const QuickSearchInput = ({
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const spacing = spacingsTokens();
|
||||
|
||||
if (children) {
|
||||
return (
|
||||
@@ -43,7 +44,7 @@ export const QuickSearchInput = ({
|
||||
$direction="row"
|
||||
$align="center"
|
||||
className="quick-search-input"
|
||||
$gap={spacingsTokens['2xs']}
|
||||
$gap={spacing['2xs']}
|
||||
$padding={{ all: 'base' }}
|
||||
>
|
||||
{!loading && <Icon iconName="search" $variation="600" />}
|
||||
|
||||
@@ -17,6 +17,7 @@ export const QuickSearchItemContent = ({
|
||||
right,
|
||||
}: QuickSearchItemContentProps) => {
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const spacings = spacingsTokens();
|
||||
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
|
||||
@@ -31,7 +32,7 @@ export const QuickSearchItemContent = ({
|
||||
<Box
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$gap={spacingsTokens['2xs']}
|
||||
$gap={spacings['2xs']}
|
||||
$width="100%"
|
||||
>
|
||||
{left}
|
||||
|
||||
@@ -26,7 +26,7 @@ export const HorizontalSeparator = ({
|
||||
$background={
|
||||
variant === SeparatorVariant.DARK
|
||||
? '#e5e5e533'
|
||||
: colorsTokens['greyscale-100']
|
||||
: colorsTokens()['greyscale-100']
|
||||
}
|
||||
className="--docs--horizontal-separator"
|
||||
/>
|
||||
|
||||
@@ -13,15 +13,17 @@ export const SeparatedSection = ({
|
||||
showSeparator = true,
|
||||
children,
|
||||
}: PropsWithChildren<Props>) => {
|
||||
const { colorsTokens, spacingsTokens } = useCunninghamTheme();
|
||||
const theme = useCunninghamTheme();
|
||||
const colors = theme.colorsTokens();
|
||||
const spacings = theme.spacingsTokens();
|
||||
return (
|
||||
<Box
|
||||
$css={css`
|
||||
width: 100%;
|
||||
padding: ${spacingsTokens['sm']} 0;
|
||||
padding: ${spacings['sm']} 0;
|
||||
${showSeparator &&
|
||||
css`
|
||||
border-bottom: 1px solid ${colorsTokens['greyscale-200']};
|
||||
border-bottom: 1px solid ${colors?.['greyscale-200']};
|
||||
`}
|
||||
`}
|
||||
>
|
||||
|
||||
@@ -35,7 +35,8 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return initializeResizeListener();
|
||||
const cleanupResizeListener = initializeResizeListener();
|
||||
return cleanupResizeListener;
|
||||
}, [initializeResizeListener]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -19,21 +19,6 @@ 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/`);
|
||||
|
||||
@@ -41,23 +26,15 @@ export const getConfig = async (): Promise<ConfigResponse> => {
|
||||
throw new APIError('Failed to get the doc', await errorCauses(response));
|
||||
}
|
||||
|
||||
const config = response.json() as Promise<ConfigResponse>;
|
||||
setCachedTranslation(await config);
|
||||
|
||||
return config;
|
||||
return response.json() as Promise<ConfigResponse>;
|
||||
};
|
||||
|
||||
export const KEY_CONFIG = 'config';
|
||||
|
||||
export function useConfig() {
|
||||
const cachedData = getCachedTranslation();
|
||||
const oneHour = 1000 * 60 * 60;
|
||||
|
||||
return useQuery<ConfigResponse, APIError, ConfigResponse>({
|
||||
queryKey: [KEY_CONFIG],
|
||||
queryFn: () => getConfig(),
|
||||
initialData: cachedData,
|
||||
staleTime: oneHour,
|
||||
initialDataUpdatedAt: Date.now() - oneHour, // Force initial data to be considered stale
|
||||
staleTime: Infinity,
|
||||
});
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user