Compare commits
90 Commits
add/clean-
...
v4.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
167375231b | ||
|
|
c17fb3e6cc | ||
|
|
1be89180fe | ||
|
|
6a3b33ec32 | ||
|
|
29f2c2ebdf | ||
|
|
9d320092df | ||
|
|
77535b0292 | ||
|
|
770c22b1a6 | ||
|
|
3c980512be | ||
|
|
76cb6d66a4 | ||
|
|
6cef5ff2a0 | ||
|
|
d816234839 | ||
|
|
5dd66f0cdc | ||
|
|
0a4052d023 | ||
|
|
189594c839 | ||
|
|
ca286b6de7 | ||
|
|
6062d0e9c4 | ||
|
|
a51b34a04e | ||
|
|
f294a8e5a3 | ||
|
|
b4591cda10 | ||
|
|
301bf43cb7 | ||
|
|
f155e9217e | ||
|
|
09fb9671e4 | ||
|
|
4c0c1f423e | ||
|
|
83fe903587 | ||
|
|
200b975c6d | ||
|
|
9536227c52 | ||
|
|
fb4c502c75 | ||
|
|
77aee5652a | ||
|
|
7cceffff13 | ||
|
|
a028df54ce | ||
|
|
25cf11c90f | ||
|
|
d1a3519646 | ||
|
|
03ea6b29df | ||
|
|
ea0a1aef10 | ||
|
|
bb7d1353f6 | ||
|
|
1944f6177e | ||
|
|
6ce847d6e1 | ||
|
|
e48080b27e | ||
|
|
73621c91e5 | ||
|
|
ee2462310f | ||
|
|
2d6e34c555 | ||
|
|
3f638b22c4 | ||
|
|
c9f42e7924 | ||
|
|
a30384573e | ||
|
|
54dc72209c | ||
|
|
9cf30a0d5f | ||
|
|
f24b047a7c | ||
|
|
3411df09ae | ||
|
|
2718321fbe | ||
|
|
217af2e2a8 | ||
|
|
53985f77f3 | ||
|
|
a51ceeb409 | ||
|
|
1070b91d2f | ||
|
|
24ec1fa70e | ||
|
|
0ba6f02d1a | ||
|
|
8ce216f6e8 | ||
|
|
050b106a8f | ||
|
|
5011db9bd7 | ||
|
|
e1e0e5ebd8 | ||
|
|
5c8fff01a5 | ||
|
|
1a022450c6 | ||
|
|
09438a8941 | ||
|
|
6f0dac4f48 | ||
|
|
9d6fe5da8f | ||
|
|
1ee313efb1 | ||
|
|
1ac6b42ae3 | ||
|
|
ffae927c93 | ||
|
|
0d335105a1 | ||
|
|
dc23883a9c | ||
|
|
a8ce9eabf8 | ||
|
|
21217be587 | ||
|
|
a8212753aa | ||
|
|
c37dc8dd34 | ||
|
|
e323af2cdb | ||
|
|
9f9f26974c | ||
|
|
c80e7d05bb | ||
|
|
5d5ac0c1c8 | ||
|
|
d0b756550b | ||
|
|
010ed4618a | ||
|
|
c0994d7d1f | ||
|
|
fa0c3847e4 | ||
|
|
49871c45b1 | ||
|
|
2cc0d71b89 | ||
|
|
33785440c6 | ||
|
|
75c7811755 | ||
|
|
f4cb66d6b6 | ||
|
|
57dc56f83e | ||
|
|
de1a0e4a73 | ||
|
|
17cb213ecd |
3
.github/.trivyignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
CVE-2026-26996
|
||||
CVE-2026-27903
|
||||
CVE-2026-27904
|
||||
4
.github/workflows/crowdin_download.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Create empty source files
|
||||
run: |
|
||||
touch src/backend/locale/django.pot
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
CROWDIN_BASE_PATH: "../src/"
|
||||
# frontend i18n
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
|
||||
8
.github/workflows/crowdin_upload.yml
vendored
@@ -20,10 +20,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
# Backend i18n
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.13.3"
|
||||
cache: "pip"
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
run: pip install --user .
|
||||
working-directory: src/backend
|
||||
- name: Restore the mail templates
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: mail-templates
|
||||
with:
|
||||
path: "src/backend/core/templates/mail"
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
DJANGO_CONFIGURATION=Build python manage.py makemessages -a --keep-pot
|
||||
# frontend i18n
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
|
||||
16
.github/workflows/dependencies.yml
vendored
@@ -20,16 +20,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: front-node_modules
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
- name: Setup Node.js
|
||||
if: steps.front-node_modules.outputs.cache-hit != 'true'
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ inputs.node_version }}
|
||||
- name: Install dependencies
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
run: cd src/frontend/ && yarn install --frozen-lockfile
|
||||
- name: Cache install frontend
|
||||
if: steps.front-node_modules.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
@@ -50,10 +50,10 @@ jobs:
|
||||
working-directory: src/mail
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Restore the mail templates
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: mail-templates
|
||||
with:
|
||||
path: "src/backend/core/templates/mail"
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.mail-templates.outputs.cache-hit != 'true'
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ inputs.node_version }}
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
|
||||
- name: Cache mail templates
|
||||
if: steps.mail-templates.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/backend/core/templates/mail"
|
||||
key: mail-templates-${{ hashFiles('src/mail/mjml') }}
|
||||
|
||||
166
.github/workflows/docker-hub.yml
vendored
@@ -5,144 +5,60 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- "main"
|
||||
tags:
|
||||
- 'v*'
|
||||
- "v*"
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'ci/trivy-fails'
|
||||
- "main"
|
||||
|
||||
env:
|
||||
DOCKER_USER: 1001:127
|
||||
SHOULD_PUSH: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview') }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-and-push-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: lasuite/impress-backend
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview')
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
with:
|
||||
docker-build-args: '--target backend-production -f Dockerfile'
|
||||
docker-image-name: 'docker.io/lasuite/impress-backend:${{ github.sha }}'
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
target: backend-production
|
||||
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
|
||||
push: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview') }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
-
|
||||
name: Cleanup Docker after build
|
||||
if: always()
|
||||
run: |
|
||||
docker system prune -af
|
||||
docker volume prune -f
|
||||
uses: ./.github/workflows/docker-publish.yml
|
||||
permissions:
|
||||
contents: read
|
||||
secrets: inherit
|
||||
with:
|
||||
image_name: lasuite/impress-backend
|
||||
context: .
|
||||
file: Dockerfile
|
||||
target: backend-production
|
||||
should_push: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview') }}
|
||||
docker_user: 1001:127
|
||||
|
||||
build-and-push-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: lasuite/impress-frontend
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview')
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
with:
|
||||
docker-build-args: '-f src/frontend/Dockerfile --target frontend-production'
|
||||
docker-image-name: 'docker.io/lasuite/impress-frontend:${{ github.sha }}'
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./src/frontend/Dockerfile
|
||||
target: frontend-production
|
||||
build-args: |
|
||||
DOCKER_USER=${{ env.DOCKER_USER }}:-1000
|
||||
PUBLISH_AS_MIT=false
|
||||
push: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview') }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
-
|
||||
name: Cleanup Docker after build
|
||||
if: always()
|
||||
run: |
|
||||
docker system prune -af
|
||||
docker volume prune -f
|
||||
uses: ./.github/workflows/docker-publish.yml
|
||||
permissions:
|
||||
contents: read
|
||||
secrets: inherit
|
||||
with:
|
||||
image_name: lasuite/impress-frontend
|
||||
context: .
|
||||
file: src/frontend/Dockerfile
|
||||
target: frontend-production
|
||||
arm64_reuse_amd64_build_arg: "FRONTEND_IMAGE"
|
||||
should_push: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview') }}
|
||||
docker_user: 1001:127
|
||||
|
||||
build-and-push-y-provider:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: lasuite/impress-y-provider
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview')
|
||||
run: echo "${{ secrets.DOCKER_HUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_HUB_USER }}" --password-stdin
|
||||
-
|
||||
name: Run trivy scan
|
||||
uses: numerique-gouv/action-trivy-cache@main
|
||||
with:
|
||||
docker-build-args: '-f src/frontend/servers/y-provider/Dockerfile --target y-provider'
|
||||
docker-image-name: 'docker.io/lasuite/impress-y-provider:${{ github.sha }}'
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./src/frontend/servers/y-provider/Dockerfile
|
||||
target: y-provider
|
||||
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
|
||||
push: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview') }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
-
|
||||
name: Cleanup Docker after build
|
||||
if: always()
|
||||
run: |
|
||||
docker system prune -af
|
||||
docker volume prune -f
|
||||
uses: ./.github/workflows/docker-publish.yml
|
||||
permissions:
|
||||
contents: read
|
||||
secrets: inherit
|
||||
with:
|
||||
image_name: lasuite/impress-y-provider
|
||||
context: .
|
||||
file: src/frontend/servers/y-provider/Dockerfile
|
||||
target: y-provider
|
||||
should_push: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'preview') }}
|
||||
docker_user: 1001:127
|
||||
|
||||
notify-argocd:
|
||||
needs:
|
||||
|
||||
142
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
name: Build and Push Container Image
|
||||
description: Build and push a container image based on the input arguments provided
|
||||
|
||||
"on":
|
||||
workflow_call:
|
||||
inputs:
|
||||
image_name:
|
||||
type: string
|
||||
required: true
|
||||
description: The suffix for the image name, without the registry and without the repository path.
|
||||
context:
|
||||
type: string
|
||||
required: true
|
||||
description: The path to the context to start `docker build` into.
|
||||
file:
|
||||
type: string
|
||||
required: true
|
||||
description: The path to the Dockerfile
|
||||
target:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
description: The Dockerfile target stage to build the image for.
|
||||
should_push:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
description: if the image should be pushed on the docker registry
|
||||
docker_user:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
description: The docker_user ARGUMENT to pass to the build step
|
||||
arm64_reuse_amd64_build_arg:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
description: "Build arg name to pass first amd64 tag to arm64 build (skips arch-independent build steps)"
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to DockerHub
|
||||
if: ${{ inputs.should_push }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ inputs.image_name }}
|
||||
- name: Generate platform-specific tags
|
||||
id: platform-tags
|
||||
run: |
|
||||
AMD64_TAGS=$(echo "${{ steps.meta.outputs.tags }}" | sed 's/$/-amd64/')
|
||||
ARM64_TAGS=$(echo "${{ steps.meta.outputs.tags }}" | sed 's/$/-arm64/')
|
||||
FIRST_AMD64_TAG=$(echo "${{ steps.meta.outputs.tags }}" | head -1)-amd64
|
||||
{
|
||||
echo "amd64<<EOF"
|
||||
echo "$AMD64_TAGS"
|
||||
echo "EOF"
|
||||
echo "arm64<<EOF"
|
||||
echo "$ARM64_TAGS"
|
||||
echo "EOF"
|
||||
echo "amd64_first=$FIRST_AMD64_TAG"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
# - name: Run trivy scan
|
||||
# if: ${{ vars.TRIVY_SCAN_ENABLED }} == 'true'
|
||||
# uses: numerique-gouv/action-trivy-cache@main
|
||||
# with:
|
||||
# docker-build-args: "--target ${{ inputs.target }} -f ${{ inputs.file }}"
|
||||
# docker-image-name: "docker.io/${{ inputs.image_name }}:${{ github.sha }}"
|
||||
# trivyignores: ./.github/.trivyignore
|
||||
- name: Build and push (amd64)
|
||||
if: ${{ inputs.should_push }}||${{ vars.TRIVY_SCAN_ENABLED }} != 'true'
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ inputs.context }}
|
||||
file: ${{ inputs.file }}
|
||||
target: ${{ inputs.target }}
|
||||
platforms: linux/amd64
|
||||
build-args: |
|
||||
DOCKER_USER=${{ inputs.docker_user }}
|
||||
PUBLISH_AS_MIT=false
|
||||
push: ${{ inputs.should_push }}
|
||||
provenance: false
|
||||
tags: ${{ steps.platform-tags.outputs.amd64 }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- name: Build and push (arm64)
|
||||
if: ${{ inputs.should_push }}
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ inputs.context }}
|
||||
file: ${{ inputs.file }}
|
||||
target: ${{ inputs.target }}
|
||||
platforms: linux/arm64
|
||||
build-args: |
|
||||
DOCKER_USER=${{ inputs.docker_user }}
|
||||
PUBLISH_AS_MIT=false
|
||||
${{ inputs.arm64_reuse_amd64_build_arg && format('{0}={1}', inputs.arm64_reuse_amd64_build_arg, steps.platform-tags.outputs.amd64_first) || '' }}
|
||||
push: ${{ inputs.should_push }}
|
||||
provenance: false
|
||||
tags: ${{ steps.platform-tags.outputs.arm64 }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- name: Create multi-arch manifests
|
||||
if: ${{ inputs.should_push }}
|
||||
id: create-manifest
|
||||
run: |
|
||||
IMAGE="${{ inputs.image_name }}"
|
||||
readarray -t TAGS <<< "${{ steps.meta.outputs.tags }}"
|
||||
FIRST_TAG=""
|
||||
for tag in "${TAGS[@]}"; do
|
||||
[ -z "$tag" ] && continue
|
||||
docker buildx imagetools create -t "$tag" \
|
||||
"${tag}-amd64" "${tag}-arm64"
|
||||
if [ -z "$FIRST_TAG" ]; then
|
||||
FIRST_TAG="$tag"
|
||||
fi
|
||||
done
|
||||
# Get the digest of the multi-arch manifest for attestation
|
||||
# Note: --format '{{.Manifest.Digest}}' is broken (docker/buildx#1175),
|
||||
# so we compute it from the raw manifest JSON instead.
|
||||
if [ -n "$FIRST_TAG" ]; then
|
||||
DIGEST="sha256:$(docker buildx imagetools inspect "$FIRST_TAG" --raw | sha256sum | awk '{print $1}')"
|
||||
echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- name: Cleanup Docker after build
|
||||
if: always()
|
||||
run: |
|
||||
docker system prune -af
|
||||
docker volume prune -f
|
||||
157
.github/workflows/ghcr.yml
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
name: Build and Push to GHCR
|
||||
run-name: Build and Push to GHCR
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
env:
|
||||
DOCKER_USER: 1001:127
|
||||
REGISTRY: ghcr.io
|
||||
|
||||
jobs:
|
||||
build-and-push-backend:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.repository.fork == true
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ github.repository }}/backend
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
target: backend-production
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: DOCKER_USER=${{ env.DOCKER_USER }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- name: Cleanup Docker after build
|
||||
if: always()
|
||||
run: |
|
||||
docker system prune -af
|
||||
docker volume prune -f
|
||||
|
||||
build-and-push-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.repository.fork == true
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ github.repository }}/frontend
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./src/frontend/Dockerfile
|
||||
target: frontend-production
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
DOCKER_USER=${{ env.DOCKER_USER }}
|
||||
PUBLISH_AS_MIT=false
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- name: Cleanup Docker after build
|
||||
if: always()
|
||||
run: |
|
||||
docker system prune -af
|
||||
docker volume prune -f
|
||||
|
||||
build-and-push-y-provider:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.repository.fork == true
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ github.repository }}/y-provider
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./src/frontend/servers/y-provider/Dockerfile
|
||||
target: y-provider
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- name: Cleanup Docker after build
|
||||
if: always()
|
||||
run: |
|
||||
docker system prune -af
|
||||
docker volume prune -f
|
||||
2
.github/workflows/helmfile-linter.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Helmfile lint
|
||||
shell: bash
|
||||
|
||||
38
.github/workflows/impress-frontend.yml
vendored
@@ -23,15 +23,15 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22.x"
|
||||
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
@@ -47,14 +47,14 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22.x"
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
@@ -69,15 +69,15 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22.x"
|
||||
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
@@ -111,15 +111,15 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22.x"
|
||||
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
@@ -157,11 +157,11 @@ jobs:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Detect relevant changes
|
||||
id: changes
|
||||
uses: dorny/paths-filter@v2
|
||||
uses: dorny/paths-filter@v3
|
||||
with:
|
||||
filters: |
|
||||
lock:
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
- 'src/frontend/apps/impress/**'
|
||||
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.changes.outputs.lock == 'true' || steps.changes.outputs.app == 'true'
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22.x"
|
||||
|
||||
@@ -205,14 +205,14 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22.x"
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
|
||||
18
.github/workflows/impress.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' # Makes sense only for pull requests
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: show
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- name: Check that the CHANGELOG has been modified in the current branch
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Check CHANGELOG max line length
|
||||
run: |
|
||||
max_line_length=$(cat CHANGELOG.md | grep -Ev "^\[.*\]: https://github.com" | wc -L)
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Install codespell
|
||||
run: pip install --user codespell
|
||||
- name: Check for typos
|
||||
@@ -92,9 +92,9 @@ jobs:
|
||||
working-directory: src/backend
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.13.3"
|
||||
cache: "pip"
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Create writable /data
|
||||
run: |
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
sudo mkdir -p /data/static
|
||||
|
||||
- name: Restore the mail templates
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: mail-templates
|
||||
with:
|
||||
path: "src/backend/core/templates/mail"
|
||||
@@ -190,7 +190,7 @@ jobs:
|
||||
mc version enable impress/impress-media-storage"
|
||||
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.13.3"
|
||||
cache: "pip"
|
||||
|
||||
2
.github/workflows/release-helm-chart.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
58
CHANGELOG.md
@@ -6,20 +6,64 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v4.7.0] - 2026-03-09
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(frontend) Can print a doc #1832
|
||||
- ✨(backend) manage reconciliation requests for user accounts #1878
|
||||
- ✨(backend) add management command to reset a Document #1882
|
||||
- ✨(helm) allow all keys in configMap as env var #1872
|
||||
|
||||
### Changed
|
||||
|
||||
- ♿️(frontend) Focus main container after navigation #1854
|
||||
- 🚸(backend) sort user search results by proximity with the active user #1802
|
||||
- 📝(docs) improve README and add documentation hub #1870
|
||||
- ♿️(frontend) restore focus to triggers after closing menus and modals #1863
|
||||
- 🚸(frontend) change position elements toolbar #1957
|
||||
- ♿️(frontend) add focus on open to modals #1948
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(frontend) analytic feature flags problem #1953
|
||||
- 🐛(frontend) fix home collapsing panel #1954
|
||||
- 🐛(frontend) fix disabled color on icon Dropdown #1950
|
||||
- 🐛(frontend) fix zIndex table of content #1949
|
||||
- 🐛(frontend) fix bug when language not supported by BN #1957
|
||||
- 🐛 (backend) prevent privileged users from requesting access #1898
|
||||
|
||||
|
||||
## [v4.6.0] - 2026-03-03
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(frontend) integrate new Blocknote AI feature #1847
|
||||
- 👷(docker) add arm64 platform support for image builds #1901
|
||||
- ✨(tracking) add UTM parameters to shared document links #1896
|
||||
- ✨(frontend) add floating bar with leftpanel collapse button #1876
|
||||
- ✨(frontend) Can print a doc #1832
|
||||
- ✨(backend) manage reconciliation requests for user accounts #1878
|
||||
- 👷(CI) add GHCR workflow for forked repo testing #1851
|
||||
- ✨(frontend) Move doc modal #1886
|
||||
- ⚡️(backend) remove content from Document serializer when asked #1910
|
||||
- ✨(backend) allow the duplication of subpages #1893
|
||||
- ✨(backend) Onboarding docs for new users #1891
|
||||
- 🩺(trivy) add trivyignore file and add minimatch CVE #1915
|
||||
- 🚩 Add feature flags for the AI feature #1922
|
||||
- 🍱(frontend) add icons ui-kit #1943
|
||||
|
||||
### Changed
|
||||
|
||||
- ♿️(frontend) prevent dates from being focusable #1855
|
||||
- ♿️(frontend) Focus main container after navigation #1864
|
||||
- 💄(frontend) align colors and logo with ui-kit v2 #1869
|
||||
- 🚸(backend) sort user search results by proximity with the active user #1802
|
||||
- 🚸(oidc) ignore case when fallback on email #1880
|
||||
- ⚡️(CI) optimize Docker Hub workflow #1919
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(frontend) fix broadcast store sync #1846
|
||||
- 🐛(helm) use celery resources instead of backend resources #1887
|
||||
- 🐛(helm) reverse liveness and readiness for backend deployment #1887
|
||||
- 🐛(y-provider) use CONVERSION_FILE_MAX_SIZE settings #1913
|
||||
- 🐛(frontend) fix callout block spacing for old browsers #1914
|
||||
|
||||
## [v4.5.0] - 2026-01-28
|
||||
|
||||
@@ -1039,7 +1083,9 @@ and this project adheres to
|
||||
- ✨(frontend) Coming Soon page (#67)
|
||||
- 🚀 Impress, project to manage your documents easily and collaboratively.
|
||||
|
||||
[unreleased]: https://github.com/suitenumerique/docs/compare/v4.5.0...main
|
||||
[unreleased]: https://github.com/suitenumerique/docs/compare/v4.7.0...main
|
||||
[v4.7.0]: https://github.com/suitenumerique/docs/releases/v4.7.0
|
||||
[v4.6.0]: https://github.com/suitenumerique/docs/releases/v4.6.0
|
||||
[v4.5.0]: https://github.com/suitenumerique/docs/releases/v4.5.0
|
||||
[v4.4.0]: https://github.com/suitenumerique/docs/releases/v4.4.0
|
||||
[v4.3.0]: https://github.com/suitenumerique/docs/releases/v4.3.0
|
||||
|
||||
26
Dockerfile
@@ -14,13 +14,6 @@ FROM base AS back-builder
|
||||
|
||||
WORKDIR /builder
|
||||
|
||||
# Install Rust and Cargo using Alpine's package manager
|
||||
RUN apk add --no-cache \
|
||||
build-base \
|
||||
libffi-dev \
|
||||
rust \
|
||||
cargo
|
||||
|
||||
# Copy required python dependencies
|
||||
COPY ./src/backend /builder
|
||||
|
||||
@@ -151,7 +144,7 @@ RUN rm -rf /var/cache/apk/*
|
||||
|
||||
ARG IMPRESS_STATIC_ROOT=/data/static
|
||||
|
||||
# Gunicorn
|
||||
# Gunicorn - not used by default but configuration file is provided
|
||||
RUN mkdir -p /usr/local/etc/gunicorn
|
||||
COPY docker/files/usr/local/etc/gunicorn/impress.py /usr/local/etc/gunicorn/impress.py
|
||||
|
||||
@@ -165,5 +158,18 @@ COPY --from=link-collector ${IMPRESS_STATIC_ROOT} ${IMPRESS_STATIC_ROOT}
|
||||
# Copy impress mails
|
||||
COPY --from=mail-builder /mail/backend/core/templates/mail /app/core/templates/mail
|
||||
|
||||
# The default command runs gunicorn WSGI server in impress's main module
|
||||
CMD ["gunicorn", "-c", "/usr/local/etc/gunicorn/impress.py", "impress.wsgi:application"]
|
||||
# The default command runs uvicorn ASGI server in dics's main module
|
||||
# WEB_CONCURRENCY: number of workers to run <=> --workers=4
|
||||
ENV WEB_CONCURRENCY=4
|
||||
CMD [\
|
||||
"uvicorn",\
|
||||
"--app-dir=/app",\
|
||||
"--host=0.0.0.0",\
|
||||
"--timeout-graceful-shutdown=300",\
|
||||
"--limit-max-requests=20000",\
|
||||
"--lifespan=off",\
|
||||
"impress.asgi:application"\
|
||||
]
|
||||
|
||||
# To run using gunicorn WSGI server use this instead:
|
||||
#CMD ["gunicorn", "-c", "/usr/local/etc/gunicorn/conversations.py", "impress.wsgi:application"]
|
||||
|
||||
284
README.md
@@ -3,226 +3,238 @@
|
||||
<img alt="Docs" src="/docs/assets/banner-docs.png" width="100%" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/suitenumerique/docs/stargazers/">
|
||||
<img src="https://img.shields.io/github/stars/suitenumerique/docs" alt="">
|
||||
</a>
|
||||
<a href='https://github.com/suitenumerique/docs/blob/main/CONTRIBUTING.md'><img alt='PRs Welcome' src='https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=shields'/></a>
|
||||
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/suitenumerique/docs"/>
|
||||
<img alt="GitHub closed issues" src="https://img.shields.io/github/issues-closed/suitenumerique/docs"/>
|
||||
<a href="https://github.com/suitenumerique/docs/blob/main/CONTRIBUTING.md">
|
||||
<img alt="PRs Welcome" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg"/>
|
||||
</a>
|
||||
<a href="https://github.com/suitenumerique/docs/blob/main/LICENSE">
|
||||
<img alt="MIT License" src="https://img.shields.io/github/license/suitenumerique/docs"/>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://matrix.to/#/#docs-official:matrix.org">
|
||||
Chat on Matrix
|
||||
</a> - <a href="/docs/">
|
||||
Documentation
|
||||
</a> - <a href="#getting-started-">
|
||||
Getting started
|
||||
</a> - <a href="mailto:docs@numerique.gouv.fr">
|
||||
Reach out
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# La Suite Docs : Collaborative Text Editing
|
||||
Docs, where your notes can become knowledge through live collaboration.
|
||||
<p align="center">
|
||||
<a href="https://matrix.to/#/#docs-official:matrix.org">Chat on Matrix</a> •
|
||||
<a href="/docs/">Documentation</a> •
|
||||
<a href="#try-docs">Try Docs</a> •
|
||||
<a href="mailto:docs@numerique.gouv.fr">Contact us</a>
|
||||
</p>
|
||||
|
||||
<img src="/docs/assets/docs_live_collaboration_light.gif" width="100%" align="center"/>
|
||||
# La Suite Docs: Collaborative Text Editing
|
||||
|
||||
## Why use Docs ❓
|
||||
Docs is a collaborative text editor designed to address common challenges in knowledge building and sharing.
|
||||
**Docs, where your notes can become knowledge through live collaboration.**
|
||||
|
||||
### Write
|
||||
* 😌 Get simple, accessible online editing for your team.
|
||||
* 💅 Create clean documents with beautiful formatting options.
|
||||
* 🖌️ Focus on your content using either the in-line editor, or [the Markdown syntax](https://www.markdownguide.org/basic-syntax/).
|
||||
* 🧱 Quickly design your page thanks to the many block types, accessible from the `/` slash commands, as well as keyboard shortcuts.
|
||||
* 🔌 Write offline! Your edits will be synced once you're back online.
|
||||
* ✨ Save time thanks to our AI actions, such as rephrasing, summarizing, fixing typos, translating, etc. You can even turn your selected text into a prompt!
|
||||
Docs is an open-source collaborative editor that helps teams write, organize, and share knowledge together - in real time.
|
||||
|
||||
### Work together
|
||||
* 🤝 Enjoy live editing! See your team collaborate in real time.
|
||||
* 🔒 Keep your information secure thanks to granular access control. Only share with the right people.
|
||||
* 📑 Export your content in multiple formats (`.odt`, `.docx`, `.pdf`) with customizable templates.
|
||||
* 📚 Turn your team's collaborative work into organized knowledge with Subpages.
|
||||

|
||||
|
||||
### Self-host
|
||||
|
||||
#### 🚀 Docs is easy to install on your own servers
|
||||
We use Kubernetes for our [production instance](https://docs.numerique.gouv.fr/) but also support Docker Compose. The community contributed a couple other methods (Nix, YunoHost etc.) check out the [docs](/docs/installation/README.md) to get detailed instructions and examples.
|
||||
## What is Docs?
|
||||
|
||||
#### 🌍 Known instances
|
||||
We hope to see many more, here is an incomplete list of public Docs instances. Feel free to make a PR to add ones that are not listed below🙏
|
||||
Docs is an open-source alternative to tools like Notion or Google Docs, focused on:
|
||||
|
||||
| Url | Org | Public |
|
||||
| --- | --- | ------- |
|
||||
| [docs.numerique.gouv.fr](https://docs.numerique.gouv.fr/) | DINUM | French public agents working for the central administration and the extended public sphere. ProConnect is required to login in or sign up|
|
||||
| [docs.suite.anct.gouv.fr](https://docs.suite.anct.gouv.fr/) | ANCT | French public agents working for the territorial administration and the extended public sphere. ProConnect is required to login in or sign up|
|
||||
| [notes.demo.opendesk.eu](https://notes.demo.opendesk.eu) | ZenDiS | Demo instance of OpenDesk. Request access to get credentials |
|
||||
| [notes.liiib.re](https://notes.liiib.re/) | lasuite.coop | Free and open demo to all. Content and accounts are reset after one month |
|
||||
| [docs.federated.nexus](https://docs.federated.nexus/) | federated.nexus | Public instance, but you have to [sign up for a Federated Nexus account](https://federated.nexus/register/). |
|
||||
| [docs.demo.mosacloud.eu](https://docs.demo.mosacloud.eu/) | mosa.cloud | Demo instance of mosa.cloud, a dutch company providing services around La Suite apps. |
|
||||
- Real-time collaboration
|
||||
- Clean, structured documents
|
||||
- Knowledge organization
|
||||
- Data ownership & self-hosting
|
||||
|
||||
#### ⚠️ Advanced features
|
||||
For some advanced features (ex: Export as PDF) Docs relies on XL packages from BlockNote. These are licenced under GPL and are not MIT compatible. You can perfectly use Docs without these packages by setting the environment variable `PUBLISH_AS_MIT` to true. That way you'll build an image of the application without the features that are not MIT compatible. Read the [environment variables documentation](/docs/env.md) for more information.
|
||||
***Built for public organizations, companies, and open communities.***
|
||||
|
||||
## Getting started 🔧
|
||||
## Why use Docs?
|
||||
|
||||
### Test it
|
||||
### Writing
|
||||
|
||||
You can test Docs on your browser by visiting this [demo document](https://docs.la-suite.eu/docs/9137bbb5-3e8a-4ff7-8a36-fcc4e8bd57f4/)
|
||||
- Rich-text & Markdown editing
|
||||
- Slash commands & block system
|
||||
- Beautiful formatting
|
||||
- Offline editing
|
||||
- Optional AI writing helpers (rewirite, summarize, translate, fix typos)
|
||||
|
||||
### Run Docs locally
|
||||
### Collaboration
|
||||
|
||||
> ⚠️ The methods described below for running Docs locally is **for testing purposes only**. It is based on building Docs using [Minio](https://min.io/) as an S3-compatible storage solution. Of course you can choose any S3-compatible storage solution.
|
||||
- Live cursors & presence
|
||||
- Comments & sharing
|
||||
- Granular access control
|
||||
|
||||
**Prerequisite**
|
||||
### Knowledge management
|
||||
|
||||
Make sure you have a recent version of Docker and [Docker Compose](https://docs.docker.com/compose/install) installed on your laptop, then type:
|
||||
- Subpages & hierarchy
|
||||
- Searchable content
|
||||
|
||||
```shellscript
|
||||
$ docker -v
|
||||
### Export/Import & interoperability
|
||||
|
||||
Docker version 20.10.2, build 2291f61
|
||||
- Import to `.docx` and `.md`
|
||||
- Export to `.docx`, `.odt`, `.pdf`
|
||||
|
||||
$ docker compose version
|
||||
## Try Docs
|
||||
|
||||
Docker Compose version v2.32.4
|
||||
Experience Docs instantly - no installation required.
|
||||
|
||||
- 🔗 [Open a live demo document][demo]
|
||||
- 🌍 [Browse public instances][instances]
|
||||
|
||||
[demo]: https://docs.la-suite.eu/docs/9137bbb5-3e8a-4ff7-8a36-fcc4e8bd57f4/
|
||||
[instances]: /docs/instances.md
|
||||
|
||||
## Self-hosting
|
||||
|
||||
Docs supports Kubernetes, Docker Compose, and community-provided methods such as Nix and YunoHost.
|
||||
|
||||
Get started with self-hosting: [Installation guide](/docs/installation/README.md)
|
||||
|
||||
> [!WARNING]
|
||||
> Some advanced features (for example: `Export as PDF`) rely on XL packages from Blocknote.
|
||||
> These packages are licensed under GPL and are **not MIT-compatible**
|
||||
>
|
||||
> You can run Docs **without these packages** by building with:
|
||||
>
|
||||
> ```bash
|
||||
> PUBLISH_AS_MIT=true
|
||||
> ```
|
||||
>
|
||||
> This builds an image of Docs without non-MIT features.
|
||||
>
|
||||
> More details can be found in [environment variables](/docs/env.md)
|
||||
|
||||
## Local Development (for contributors)
|
||||
|
||||
Run Docs locally for development and testing.
|
||||
|
||||
> [!WARNING]
|
||||
> This setup is intended **for development and testing only**.
|
||||
> It uses Minio as an S3-compatible storage backend, but any S3-compatible service can be used.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker
|
||||
- Docker Compose
|
||||
- GNU Make
|
||||
|
||||
Verify installation:
|
||||
|
||||
```bash
|
||||
docker -v
|
||||
docker compose version
|
||||
```
|
||||
|
||||
> ⚠️ You may need to run the following commands with `sudo`, but this can be avoided by adding your user to the local `docker` group.
|
||||
> If you encounounter permission errors, you may need to use `sudo`, or add your user to the `docker` group.
|
||||
|
||||
**Project bootstrap**
|
||||
### Bootstrap the project
|
||||
|
||||
The easiest way to start working on the project is to use [GNU Make](https://www.gnu.org/software/make/):
|
||||
The easiest way to start is using GNU Make:
|
||||
|
||||
```shellscript
|
||||
$ make bootstrap FLUSH_ARGS='--no-input'
|
||||
```bash
|
||||
make bootstrap FLUSH_ARGS='--no-input'
|
||||
```
|
||||
|
||||
This command builds the `app-dev` and `frontend-dev` containers, installs dependencies, performs database migrations and compiles translations. It's a good idea to use this command each time you are pulling code from the project repository to avoid dependency-related or migration-related issues.
|
||||
This builds the `app-dev` and `fronted-dev` containers, installs dependencies, runs database migrations, and compiles translations.
|
||||
|
||||
Your Docker services should now be up and running 🎉
|
||||
It is recommend to run this command after pulling new code.
|
||||
|
||||
You can access the project by going to <http://localhost:3000>.
|
||||
|
||||
You will be prompted to log in. The default credentials are:
|
||||
Start services:
|
||||
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
|
||||
Open <https://localhost:3000>
|
||||
|
||||
Default credentials (development only):
|
||||
|
||||
```md
|
||||
username: impress
|
||||
password: impress
|
||||
```
|
||||
|
||||
📝 Note that if you need to run them afterwards, you can use the eponymous Make rule:
|
||||
### Frontend development mode
|
||||
|
||||
```shellscript
|
||||
$ make run
|
||||
For frontend work, running outside Docker is often more convenient:
|
||||
|
||||
```bash
|
||||
make frontend-development-install
|
||||
make run-frontend-development
|
||||
```
|
||||
|
||||
⚠️ For the frontend developer, it is often better to run the frontend in development mode locally.
|
||||
### Backend only
|
||||
|
||||
To do so, install the frontend dependencies with the following command:
|
||||
Starting all services except the frontend container:
|
||||
|
||||
```shellscript
|
||||
$ make frontend-development-install
|
||||
```bash
|
||||
make run-backend
|
||||
```
|
||||
|
||||
And run the frontend locally in development mode with the following command:
|
||||
### Tests & Linting
|
||||
|
||||
```shellscript
|
||||
$ make run-frontend-development
|
||||
```bash
|
||||
make frontend-test
|
||||
make frontend-lint
|
||||
```
|
||||
|
||||
To start all the services, except the frontend container, you can use the following command:
|
||||
### Demo content
|
||||
|
||||
```shellscript
|
||||
$ make run-backend
|
||||
Create a basic demo site:
|
||||
|
||||
```bash
|
||||
make demo
|
||||
```
|
||||
|
||||
To execute frontend tests & linting only
|
||||
```shellscript
|
||||
$ make frontend-test
|
||||
$ make frontend-lint
|
||||
### More Make targets
|
||||
|
||||
To check all available Make rules:
|
||||
|
||||
```bash
|
||||
make help
|
||||
```
|
||||
|
||||
**Adding content**
|
||||
### Django admin
|
||||
|
||||
You can create a basic demo site by running this command:
|
||||
Create a superuser:
|
||||
|
||||
```shellscript
|
||||
$ make demo
|
||||
```bash
|
||||
make superuser
|
||||
```
|
||||
|
||||
Finally, you can check all available Make rules using this command:
|
||||
Admin UI: <http://localhost:8071/admin>
|
||||
|
||||
```shellscript
|
||||
$ make help
|
||||
```
|
||||
## Contributing
|
||||
|
||||
**Django admin**
|
||||
This project is community-driven and PRs are welcome.
|
||||
|
||||
You can access the Django admin site at:
|
||||
- [Contribution guide](CONTRIBUTING.md)
|
||||
- [Translations](https://crowdin.com/project/lasuite-docs)
|
||||
- [Chat with us!](https://matrix.to/#/#docs-official:matrix.org)
|
||||
|
||||
<http://localhost:8071/admin>.
|
||||
## Roadmap
|
||||
|
||||
You first need to create a superuser account:
|
||||
Curious where Docs is headed?
|
||||
|
||||
```shellscript
|
||||
$ make superuser
|
||||
```
|
||||
|
||||
## Feedback 🙋♂️🙋♀️
|
||||
|
||||
We'd love to hear your thoughts, and hear about your experiments, so come and say hi on [Matrix](https://matrix.to/#/#docs-official:matrix.org).
|
||||
|
||||
## Roadmap 💡
|
||||
|
||||
Want to know where the project is headed? [🗺️ Checkout our roadmap](https://github.com/orgs/numerique-gouv/projects/13/views/11)
|
||||
Explore upcoming features, priorities and long-term direction on our [public roadmap](https://docs.numerique.gouv.fr/docs/d1d3788e-c619-41ff-abe8-2d079da2f084/).
|
||||
|
||||
## License 📝
|
||||
|
||||
This work is released under the MIT License (see [LICENSE](https://github.com/suitenumerique/docs/blob/main/LICENSE)).
|
||||
|
||||
While Docs is a public-driven initiative, our license choice is an invitation for private sector actors to use, sell and contribute to the project.
|
||||
|
||||
## Contributing 🙌
|
||||
|
||||
This project is intended to be community-driven, so please, do not hesitate to [get in touch](https://matrix.to/#/#docs-official:matrix.org) if you have any question related to our implementation or design decisions.
|
||||
|
||||
You can help us with translations on [Crowdin](https://crowdin.com/project/lasuite-docs).
|
||||
|
||||
If you intend to make pull requests, see [CONTRIBUTING](https://github.com/suitenumerique/docs/blob/main/CONTRIBUTING.md) for guidelines.
|
||||
|
||||
## Directory structure:
|
||||
|
||||
```markdown
|
||||
docs
|
||||
├── bin - executable scripts or binaries that are used for various tasks, such as setup scripts, utility scripts, or custom commands.
|
||||
├── crowdin - for crowdin translations, a tool or service that helps manage translations for the project.
|
||||
├── docker - Dockerfiles and related configuration files used to build Docker images for the project. These images can be used for development, testing, or production environments.
|
||||
├── docs - documentation for the project, including user guides, API documentation, and other helpful resources.
|
||||
├── env.d/development - environment-specific configuration files for the development environment. These files might include environment variables, configuration settings, or other setup files needed for development.
|
||||
├── gitlint - configuration files for `gitlint`, a tool that enforces commit message guidelines to ensure consistency and quality in commit messages.
|
||||
├── playground - experimental or temporary code, where developers can test new features or ideas without affecting the main codebase.
|
||||
└── src - main source code directory, containing the core application code, libraries, and modules of the project.
|
||||
```
|
||||
While Docs is a public-driven initiative, our license choice is an invitation for private sector actors to use, sell and contribute to the project.
|
||||
|
||||
## Credits ❤️
|
||||
|
||||
### Stack
|
||||
|
||||
Docs is built on top of [Django Rest Framework](https://www.django-rest-framework.org/), [Next.js](https://nextjs.org/), [BlockNote.js](https://www.blocknotejs.org/), [HocusPocus](https://tiptap.dev/docs/hocuspocus/introduction) and [Yjs](https://yjs.dev/). We thank the contributors of all these projects for their awesome work!
|
||||
Docs is built on top of [Django Rest Framework](https://www.django-rest-framework.org/), [Next.js](https://nextjs.org/), [ProseMirror](https://prosemirror.net/), [BlockNote.js](https://www.blocknotejs.org/), [HocusPocus](https://tiptap.dev/docs/hocuspocus/introduction), and [Yjs](https://yjs.dev/). We thank the contributors of all these projects for their awesome work!
|
||||
|
||||
We are proud sponsors of [BlockNotejs](https://www.blocknotejs.org/) and [Yjs](https://yjs.dev/).
|
||||
We are proud sponsors of [BlockNotejs](https://www.blocknotejs.org/) and [Yjs](https://yjs.dev/).
|
||||
|
||||
---
|
||||
|
||||
### Gov ❤️ open source
|
||||
Docs is the result of a joint effort led by the French 🇫🇷🥖 ([DINUM](https://www.numerique.gouv.fr/dinum/)) and German 🇩🇪🥨 governments ([ZenDiS](https://zendis.de/)).
|
||||
|
||||
We are always looking for new public partners (we are currently onboarding the Netherlands 🇳🇱🧀), feel free to [reach out](mailto:docs@numerique.gouv.fr) if you are interested in using or contributing to Docs.
|
||||
Docs is the result of a joint initiative led by the French 🇫🇷 ([DINUM](https://www.numerique.gouv.fr/dinum/)) Government and German 🇩🇪 government ([ZenDiS](https://zendis.de/)).
|
||||
|
||||
We are always looking for new public partners (we are currently onboarding the Netherlands 🇳🇱), feel free to [contact us](mailto:docs@numerique.gouv.fr) if you are interested in using or contributing to Docs.
|
||||
|
||||
<p align="center">
|
||||
<img src="/docs/assets/europe_opensource.png" width="50%"/>
|
||||
<img src="/docs/assets/europe_opensource.png" width="50%"/ alt="Europe Opensource">
|
||||
</p>
|
||||
|
||||
@@ -16,6 +16,12 @@ the following command inside your docker container:
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [4.6.0] - 2026-02-27
|
||||
|
||||
- ⚠️ Some setup have changed to offer a bigger flexibility and consistency, overriding the favicon and logo are now from the theme configuration.
|
||||
https://github.com/suitenumerique/docs/blob/f24b047a7cc146411412bf759b5b5248a45c3d99/src/backend/impress/configuration/theme/default.json#L129-L161
|
||||
|
||||
|
||||
## [4.0.0] - 2025-11-26
|
||||
|
||||
- ⚠️ We updated `@gouvfr-lasuite/ui-kit` to `0.18.0`, so if you are customizing Docs with a css layer or with a custom template, you need to update your customization to follow the new design system structure.
|
||||
|
||||
39
docs/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Docs Documentation
|
||||
|
||||
Welcome to the official documentation for Docs.
|
||||
|
||||
This documentation is organized by topic and audience.
|
||||
Use the section below to quickly find what you are looking for.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- Getting started
|
||||
- [System requirements](system-requirements.md)
|
||||
- [Installation overview](installation/README.md)
|
||||
- [Docker Compose deployment](installation/compose.md)
|
||||
- [Docker Compose examples](examples/compose/)
|
||||
- [Kubernetes deployment](installation/kubernetes.md)
|
||||
- [Helm values examples](examples/helm/)
|
||||
|
||||
- Configuration
|
||||
- [Environment variables](env.md)
|
||||
- [Customization](customization.md)
|
||||
- [Language configuration](languages-configuration.md)
|
||||
- [Search configuration](search.md)
|
||||
|
||||
- Architecture & design
|
||||
- [Architecture overview](architecture.md)
|
||||
- [Architectural Decision Records (ADR)](adr/)
|
||||
|
||||
- Usage & operations
|
||||
- [Public instances](instances.md)
|
||||
- [Releases & upgrades](release.md)
|
||||
- [Troubleshooting](troubleshoot.md)
|
||||
|
||||
- Project & product
|
||||
- [Roadmap](roadmap.md)
|
||||
|
||||
- Assets
|
||||
- [Branding & visuals](assets/)
|
||||
45
docs/env.md
@@ -7,17 +7,21 @@ Here we describe all environment variables that can be set for the docs applicat
|
||||
These are the environment variables you can set for the `impress-backend` container.
|
||||
|
||||
| Option | Description | default |
|
||||
|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
|
||||
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| AI_ALLOW_REACH_FROM | Users that can use AI must be this level. options are "public", "authenticated", "restricted" | authenticated |
|
||||
| AI_API_KEY | AI key to be used for AI Base url | |
|
||||
| AI_BASE_URL | OpenAI compatible AI base url | |
|
||||
| AI_BOT | Information to give to the frontend about the AI bot | { "name": "Docs AI", "color": "#8bc6ff" }
|
||||
| AI_FEATURE_ENABLED | Enable AI options | false |
|
||||
| AI_FEATURE_BLOCKNOTE_ENABLED | Enable Blocknote AI options | false |
|
||||
| AI_FEATURE_LEGACY_ENABLED | Enable legacyAI options | true |
|
||||
| AI_MODEL | AI Model to use | |
|
||||
| AI_VERCEL_SDK_VERSION | The vercel AI SDK version used | 6 |
|
||||
| ALLOW_LOGOUT_GET_METHOD | Allow get logout method | true |
|
||||
| API_USERS_LIST_LIMIT | Limit on API users | 5 |
|
||||
| API_USERS_LIST_THROTTLE_RATE_BURST | Throttle rate for api on burst | 30/minute |
|
||||
| API_USERS_LIST_THROTTLE_RATE_SUSTAINED | Throttle rate for api | 180/hour |
|
||||
| API_USERS_SEARCH_QUERY_MIN_LENGTH | Minimum characters to insert to search a user | 3 |
|
||||
| API_USERS_SEARCH_QUERY_MIN_LENGTH | Minimum characters to insert to search a user | 3 |
|
||||
| AWS_S3_ACCESS_KEY_ID | Access id for s3 endpoint | |
|
||||
| AWS_S3_ENDPOINT_URL | S3 endpoint | |
|
||||
| AWS_S3_REGION_NAME | Region name for s3 endpoint | |
|
||||
@@ -35,7 +39,7 @@ These are the environment variables you can set for the `impress-backend` contai
|
||||
| CONVERSION_API_SECURE | Require secure conversion api | false |
|
||||
| CONVERSION_API_TIMEOUT | Conversion api timeout | 30 |
|
||||
| CONVERSION_FILE_MAX_SIZE | The file max size allowed when uploaded to convert it | 20971520 (20MB) |
|
||||
| CONVERSION_FILE_EXTENSIONS_ALLOWED | Extension list managed by the conversion service | [".docx", ".md"]
|
||||
| CONVERSION_FILE_EXTENSIONS_ALLOWED | Extension list managed by the conversion service | [".docx", ".md"] |
|
||||
| CRISP_WEBSITE_ID | Crisp website id for support | |
|
||||
| DB_ENGINE | Engine to use for database connections | django.db.backends.postgresql_psycopg2 |
|
||||
| DB_HOST | Host of the database | localhost |
|
||||
@@ -58,22 +62,22 @@ These are the environment variables you can set for the `impress-backend` contai
|
||||
| DJANGO_EMAIL_HOST_USER | User to authenticate with on the email host | |
|
||||
| DJANGO_EMAIL_LOGO_IMG | Logo for the email | |
|
||||
| DJANGO_EMAIL_PORT | Port used to connect to email host | |
|
||||
| DJANGO_EMAIL_URL_APP | Url used in the email to go to the app | |
|
||||
| DJANGO_EMAIL_URL_APP | Url used in the email to go to the app | |
|
||||
| DJANGO_EMAIL_USE_SSL | Use ssl for email host connection | false |
|
||||
| DJANGO_EMAIL_USE_TLS | Use tls for email host connection | false |
|
||||
| DJANGO_SECRET_KEY | Secret key | |
|
||||
| DJANGO_SERVER_TO_SERVER_API_TOKENS | | [] |
|
||||
| DOCSPEC_API_URL | URL to endpoint of DocSpec conversion API | |
|
||||
| DOCSPEC_API_URL | URL to endpoint of DocSpec conversion API | |
|
||||
| DOCUMENT_IMAGE_MAX_SIZE | Maximum size of document in bytes | 10485760 |
|
||||
| FRONTEND_CSS_URL | To add a external css file to the app | |
|
||||
| FRONTEND_JS_URL | To add a external js file to the app | |
|
||||
| FRONTEND_JS_URL | To add a external js file to the app | |
|
||||
| FRONTEND_HOMEPAGE_FEATURE_ENABLED | Frontend feature flag to display the homepage | false |
|
||||
| FRONTEND_THEME | Frontend theme to use | |
|
||||
| LANGUAGE_CODE | Default language | en-us |
|
||||
| LANGFUSE_SECRET_KEY | The Langfuse secret key used by the sdk | None |
|
||||
| LANGFUSE_PUBLIC_KEY | The Langfuse public key used by the sdk | None |
|
||||
| LANGFUSE_BASE_URL | The Langfuse base url used by the sdk | None |
|
||||
| LASUITE_MARKETING_BACKEND | Backend used when SIGNUP_NEW_USER_TO_MARKETING_EMAIL is True. See https://github.com/suitenumerique/django-lasuite/blob/main/documentation/how-to-use-marketing-backend.md | lasuite.marketing.backends.dummy.DummyBackend |
|
||||
| LASUITE_MARKETING_BACKEND | Backend used when SIGNUP_NEW_USER_TO_MARKETING_EMAIL is True. See https://github.com/suitenumerique/django-lasuite/blob/main/documentation/how-to-use-marketing-backend.md | lasuite.marketing.backends.dummy.DummyBackend |
|
||||
| LASUITE_MARKETING_PARAMETERS | The parameters to configure LASUITE_MARKETING_BACKEND. See https://github.com/suitenumerique/django-lasuite/blob/main/documentation/how-to-use-marketing-backend.md | {} |
|
||||
| LOGGING_LEVEL_LOGGERS_APP | Application logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO |
|
||||
| LOGGING_LEVEL_LOGGERS_ROOT | Default logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO |
|
||||
@@ -120,11 +124,12 @@ These are the environment variables you can set for the `impress-backend` contai
|
||||
| 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 |
|
||||
| TRASHBIN_CUTOFF_DAYS | Trashbin cutoff | 30 |
|
||||
| USER_OIDC_ESSENTIAL_CLAIMS | Essential claims in OIDC token | [] |
|
||||
| USER_ONBOARDING_DOCUMENTS | A list of documents IDs for which a read-only access will be created for new s | [] |
|
||||
| USER_ONBOARDING_SANDBOX_DOCUMENT | ID of a template sandbox document that will be duplicated for new users | |
|
||||
| USER_RECONCILIATION_FORM_URL | URL of a third-party form for user reconciliation requests | |
|
||||
| Y_PROVIDER_API_BASE_URL | Y Provider url | |
|
||||
| Y_PROVIDER_API_KEY | Y provider API key | |
|
||||
|
||||
|
||||
## impress-frontend image
|
||||
|
||||
These are the environment variables you can set to build the `impress-frontend` image.
|
||||
@@ -135,31 +140,31 @@ If you want to build the Docker image, this variable is used as an argument in t
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
```bash
|
||||
docker build -f src/frontend/Dockerfile --target frontend-production --build-arg PUBLISH_AS_MIT=false docs-frontend:latest
|
||||
```
|
||||
```
|
||||
|
||||
If you want to build the front-end application using the yarn build command, you can edit the file `src/frontend/apps/impress/.env` with the `NODE_ENV=production` environment variable and modify it. Alternatively, you can use the listed environment variables with the prefix `NEXT_PUBLIC_` (for example, `NEXT_PUBLIC_PUBLISH_AS_MIT=false`).
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
```bash
|
||||
cd src/frontend/apps/impress
|
||||
NODE_ENV=production NEXT_PUBLIC_PUBLISH_AS_MIT=false yarn build
|
||||
```
|
||||
|
||||
| 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 | Removes packages whose licences are incompatible with the MIT licence (see below) | true |
|
||||
| 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 | Removes packages whose licences are incompatible with the MIT licence (see below) | true |
|
||||
|
||||
Packages with licences incompatible with the MIT licence:
|
||||
* `xl-docx-exporter`: [GPL](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-docx-exporter/LICENSE),
|
||||
* `xl-pdf-exporter`: [GPL](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-pdf-exporter/LICENSE),
|
||||
* `xl-multi-column`: [GPL](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-multi-column/LICENSE).
|
||||
|
||||
* `xl-docx-exporter`: [GPL](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-docx-exporter/LICENSE),
|
||||
* `xl-pdf-exporter`: [GPL](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-pdf-exporter/LICENSE),
|
||||
* `xl-multi-column`: [GPL](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-multi-column/LICENSE).
|
||||
|
||||
In `.env.development`, `PUBLISH_AS_MIT` is set to `false`, allowing developers to test Docs with all its features.
|
||||
|
||||
⚠️ If you run Docs in production with `PUBLISH_AS_MIT` set to `false` make sure you fulfill your BlockNote licensing or [subscription](https://www.blocknotejs.org/about#partner-with-us) obligations.
|
||||
|
||||
|
||||
@@ -138,6 +138,8 @@ AI is disabled by default. To enable it, the following environment variables mus
|
||||
|
||||
```env
|
||||
AI_FEATURE_ENABLED=true # is false by default
|
||||
AI_FEATURE_BLOCKNOTE_ENABLED=true # is false by default
|
||||
AI_FEATURE_LEGACY_ENABLED=true # is true by default, AI_FEATURE_ENABLED must be set to true to enable it
|
||||
AI_BASE_URL=https://openaiendpoint.com
|
||||
AI_API_KEY=<API key>
|
||||
AI_MODEL=<model used> e.g. llama
|
||||
|
||||
77
docs/instances.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# 🌍 Public Docs Instances
|
||||
|
||||
This page lists known public instances of **Docs**.
|
||||
|
||||
These instances are operated by different organizations and may have different access policies.
|
||||
If you run a public instance and would like it listed here, feel free to open a pull request.
|
||||
|
||||
---
|
||||
|
||||
## 🏛️ Public Organizations
|
||||
|
||||
### docs.numerique.gouv.fr
|
||||
|
||||
**Organization:** DINUM
|
||||
**Audience:** French public agents working for central administration and extended public sphere
|
||||
**Access:** ProConnect account required
|
||||
<https://docs.numerique.gouv.fr/>
|
||||
|
||||
### docs.suite.anct.gouv.fr
|
||||
|
||||
**Organization:** ANCT
|
||||
**Audience:** French public agents working for territorial administration and extended public sphere
|
||||
**Access:** ProConnect account required
|
||||
<https://docs.suite.anct.gouv.fr/>
|
||||
|
||||
### notes.demo.opendesk.eu
|
||||
|
||||
**Organization:** ZenDiS
|
||||
**Type:** OpenDesk demo instance
|
||||
**Access:** Request credentials
|
||||
<https://notes.demo.opendesk.eu/>
|
||||
|
||||
---
|
||||
|
||||
## 🏢 Private Sector
|
||||
|
||||
### docs.demo.mosacloud.eu
|
||||
|
||||
**Organization:** mosa.cloud
|
||||
**Type:** Demo instance
|
||||
<https://docs.demo.mosacloud.eu/>
|
||||
|
||||
### notes.liiib.re
|
||||
|
||||
**Organization:** lasuite.coop
|
||||
**Access:** Public demo
|
||||
**Notes:** Content and accounts reset monthly
|
||||
<https://notes.liiib.re/>
|
||||
|
||||
### notes.lasuite.coop
|
||||
|
||||
**Organization:** lasuite.coop
|
||||
**Access:** Public
|
||||
<https://notes.lasuite.coop/>
|
||||
|
||||
---
|
||||
|
||||
## 🤝 NGOs
|
||||
|
||||
### docs.federated.nexus
|
||||
|
||||
**Organization:** federated.nexus
|
||||
**Access:** Public with account registration
|
||||
<https://docs.federated.nexus/>
|
||||
|
||||
---
|
||||
|
||||
## ➕ Add your instance
|
||||
|
||||
To add your instance:
|
||||
|
||||
1. Fork the repository
|
||||
2. Edit `docs/instances.md`
|
||||
3. Add your instance following the existing format
|
||||
4. Open a pull request
|
||||
|
||||
Thank you for helping grow the Docs ecosystem ❤️
|
||||
@@ -64,6 +64,8 @@ USER_RECONCILIATION_FORM_URL=http://localhost:3000
|
||||
|
||||
# AI
|
||||
AI_FEATURE_ENABLED=true
|
||||
AI_FEATURE_BLOCKNOTE_ENABLED=true
|
||||
AI_FEATURE_LEGACY_ENABLED=true
|
||||
AI_BASE_URL=https://openaiendpoint.com
|
||||
AI_API_KEY=password
|
||||
AI_MODEL=llama
|
||||
|
||||
@@ -58,6 +58,8 @@ OIDC_REDIRECT_ALLOWED_HOSTS=["https://${DOCS_HOST}"]
|
||||
|
||||
# AI
|
||||
#AI_FEATURE_ENABLED=true # is false by default
|
||||
#AI_FEATURE_BLOCKNOTE_ENABLED=true # is false by default
|
||||
#AI_FEATURE_LEGACY_ENABLED=true # is true by default, AI_FEATURE_ENABLED must be set to true to enable it
|
||||
#AI_BASE_URL=https://openaiendpoint.com
|
||||
#AI_API_KEY=<API key>
|
||||
#AI_MODEL=<model used> e.g. llama
|
||||
|
||||
@@ -37,6 +37,12 @@
|
||||
"matchPackageNames": ["celery"],
|
||||
"allowedVersions": "<5.6.0"
|
||||
},
|
||||
{
|
||||
"groupName": "allowed pydantic-ai-slim versions",
|
||||
"matchManagers": ["pep621"],
|
||||
"matchPackageNames": ["pydantic-ai-slim"],
|
||||
"allowedVersions": "<1.59.0"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"groupName": "ignored js dependencies",
|
||||
|
||||
@@ -225,8 +225,16 @@ class DocumentSerializer(ListDocumentSerializer):
|
||||
fields = super().get_fields()
|
||||
|
||||
request = self.context.get("request")
|
||||
if request and request.method == "POST":
|
||||
fields["id"].read_only = False
|
||||
if request:
|
||||
if request.method == "POST":
|
||||
fields["id"].read_only = False
|
||||
if (
|
||||
serializers.BooleanField().to_internal_value(
|
||||
request.query_params.get("without_content", False)
|
||||
)
|
||||
is True
|
||||
):
|
||||
del fields["content"]
|
||||
|
||||
return fields
|
||||
|
||||
@@ -591,10 +599,13 @@ class LinkDocumentSerializer(serializers.ModelSerializer):
|
||||
class DocumentDuplicationSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for duplicating a document.
|
||||
Allows specifying whether to keep access permissions.
|
||||
Allows specifying whether to keep access permissions,
|
||||
and whether to duplicate descendant documents as well
|
||||
(deep copy) or not (shallow copy).
|
||||
"""
|
||||
|
||||
with_accesses = serializers.BooleanField(default=False)
|
||||
with_descendants = serializers.BooleanField(default=False)
|
||||
|
||||
def create(self, validated_data):
|
||||
"""
|
||||
|
||||
@@ -27,6 +27,7 @@ from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.http import content_disposition_header
|
||||
from django.utils.text import capfirst, slugify
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -38,6 +39,7 @@ from csp.decorators import csp_update
|
||||
from lasuite.malware_detection import malware_detection
|
||||
from lasuite.oidc_login.decorators import refresh_oidc_access_token
|
||||
from lasuite.tools.email import get_domain_from_email
|
||||
from pydantic import ValidationError as PydanticValidationError
|
||||
from rest_framework import filters, status, viewsets
|
||||
from rest_framework import response as drf_response
|
||||
from rest_framework.permissions import AllowAny
|
||||
@@ -475,6 +477,9 @@ class DocumentViewSet(
|
||||
Returns: JSON response with the translated text.
|
||||
Throttled by: AIDocumentRateThrottle, AIUserRateThrottle.
|
||||
|
||||
12. **AI Proxy**: Proxy an AI request to an external AI service.
|
||||
Example: POST /api/v1.0/documents/<resource_id>/ai-proxy
|
||||
|
||||
### Ordering: created_at, updated_at, is_favorite, title
|
||||
|
||||
Example:
|
||||
@@ -1208,11 +1213,7 @@ class DocumentViewSet(
|
||||
@transaction.atomic
|
||||
def duplicate(self, request, *args, **kwargs):
|
||||
"""
|
||||
Duplicate a document and store the links to attached files in the duplicated
|
||||
document to allow cross-access.
|
||||
|
||||
Optionally duplicates accesses if `with_accesses` is set to true
|
||||
in the payload.
|
||||
Duplicate a document, alongside its descendants if requested.
|
||||
"""
|
||||
# Get document while checking permissions
|
||||
document_to_duplicate = self.get_object()
|
||||
@@ -1221,8 +1222,43 @@ class DocumentViewSet(
|
||||
data=request.data, partial=True
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
user = request.user
|
||||
|
||||
duplicated_document = self._duplicate_document(
|
||||
document_to_duplicate=document_to_duplicate,
|
||||
serializer=serializer,
|
||||
user=user,
|
||||
)
|
||||
|
||||
return drf_response.Response(
|
||||
{"id": str(duplicated_document.id)}, status=status.HTTP_201_CREATED
|
||||
)
|
||||
|
||||
def _duplicate_document(
|
||||
self,
|
||||
document_to_duplicate,
|
||||
serializer,
|
||||
user,
|
||||
new_parent=None,
|
||||
):
|
||||
"""
|
||||
Duplicate a document and store the links to attached files in the duplicated
|
||||
document to allow cross-access.
|
||||
|
||||
Optionally duplicates accesses if `with_accesses` is set to true
|
||||
in the payload.
|
||||
|
||||
Optionally duplicates sub-documents if `with_descendants` is set to true in
|
||||
the payload. In this case, the whole subtree of the document will be duplicated,
|
||||
and the links to attached files will be stored in all duplicated documents.
|
||||
|
||||
The `with_accesses` option will also be applied to all duplicated documents
|
||||
if `with_descendants` is set to true.
|
||||
"""
|
||||
with_accesses = serializer.validated_data.get("with_accesses", False)
|
||||
user_role = document_to_duplicate.get_role(request.user)
|
||||
with_descendants = serializer.validated_data.get("with_descendants", False)
|
||||
|
||||
user_role = document_to_duplicate.get_role(user)
|
||||
is_owner_or_admin = user_role in models.PRIVILEGED_ROLES
|
||||
|
||||
base64_yjs_content = document_to_duplicate.content
|
||||
@@ -1241,11 +1277,41 @@ class DocumentViewSet(
|
||||
extracted_attachments & set(document_to_duplicate.attachments)
|
||||
)
|
||||
title = capfirst(_("copy of {title}").format(title=document_to_duplicate.title))
|
||||
if not document_to_duplicate.is_root() and choices.RoleChoices.get_priority(
|
||||
# If parent_duplicate is provided we must add the duplicated document as a child
|
||||
if new_parent is not None:
|
||||
duplicated_document = new_parent.add_child(
|
||||
title=title,
|
||||
content=base64_yjs_content,
|
||||
attachments=attachments,
|
||||
duplicated_from=document_to_duplicate,
|
||||
creator=user,
|
||||
**link_kwargs,
|
||||
)
|
||||
|
||||
# Handle access duplication for this child
|
||||
if with_accesses and is_owner_or_admin:
|
||||
original_accesses = models.DocumentAccess.objects.filter(
|
||||
document=document_to_duplicate
|
||||
).exclude(user=user)
|
||||
|
||||
accesses_to_create = [
|
||||
models.DocumentAccess(
|
||||
document=duplicated_document,
|
||||
user_id=access.user_id,
|
||||
team=access.team,
|
||||
role=access.role,
|
||||
)
|
||||
for access in original_accesses
|
||||
]
|
||||
|
||||
if accesses_to_create:
|
||||
models.DocumentAccess.objects.bulk_create(accesses_to_create)
|
||||
|
||||
elif not document_to_duplicate.is_root() and choices.RoleChoices.get_priority(
|
||||
user_role
|
||||
) < choices.RoleChoices.get_priority(models.RoleChoices.EDITOR):
|
||||
duplicated_document = models.Document.add_root(
|
||||
creator=self.request.user,
|
||||
creator=user,
|
||||
title=title,
|
||||
content=base64_yjs_content,
|
||||
attachments=attachments,
|
||||
@@ -1254,55 +1320,63 @@ class DocumentViewSet(
|
||||
)
|
||||
models.DocumentAccess.objects.create(
|
||||
document=duplicated_document,
|
||||
user=self.request.user,
|
||||
user=user,
|
||||
role=models.RoleChoices.OWNER,
|
||||
)
|
||||
return drf_response.Response(
|
||||
{"id": str(duplicated_document.id)}, status=status.HTTP_201_CREATED
|
||||
else:
|
||||
duplicated_document = document_to_duplicate.add_sibling(
|
||||
"right",
|
||||
title=title,
|
||||
content=base64_yjs_content,
|
||||
attachments=attachments,
|
||||
duplicated_from=document_to_duplicate,
|
||||
creator=user,
|
||||
**link_kwargs,
|
||||
)
|
||||
|
||||
duplicated_document = document_to_duplicate.add_sibling(
|
||||
"right",
|
||||
title=title,
|
||||
content=base64_yjs_content,
|
||||
attachments=attachments,
|
||||
duplicated_from=document_to_duplicate,
|
||||
creator=request.user,
|
||||
**link_kwargs,
|
||||
)
|
||||
|
||||
# Always add the logged-in user as OWNER for root documents
|
||||
if document_to_duplicate.is_root():
|
||||
accesses_to_create = [
|
||||
models.DocumentAccess(
|
||||
document=duplicated_document,
|
||||
user=request.user,
|
||||
role=models.RoleChoices.OWNER,
|
||||
)
|
||||
]
|
||||
|
||||
# If accesses should be duplicated, add other users' accesses as per original document
|
||||
if with_accesses and is_owner_or_admin:
|
||||
original_accesses = models.DocumentAccess.objects.filter(
|
||||
document=document_to_duplicate
|
||||
).exclude(user=request.user)
|
||||
|
||||
accesses_to_create.extend(
|
||||
# Always add the logged-in user as OWNER for root documents
|
||||
if document_to_duplicate.is_root():
|
||||
accesses_to_create = [
|
||||
models.DocumentAccess(
|
||||
document=duplicated_document,
|
||||
user_id=access.user_id,
|
||||
team=access.team,
|
||||
role=access.role,
|
||||
user=user,
|
||||
role=models.RoleChoices.OWNER,
|
||||
)
|
||||
for access in original_accesses
|
||||
]
|
||||
|
||||
# If accesses should be duplicated,
|
||||
# add other users' accesses as per original document
|
||||
if with_accesses and is_owner_or_admin:
|
||||
original_accesses = models.DocumentAccess.objects.filter(
|
||||
document=document_to_duplicate
|
||||
).exclude(user=user)
|
||||
|
||||
accesses_to_create.extend(
|
||||
models.DocumentAccess(
|
||||
document=duplicated_document,
|
||||
user_id=access.user_id,
|
||||
team=access.team,
|
||||
role=access.role,
|
||||
)
|
||||
for access in original_accesses
|
||||
)
|
||||
|
||||
# Bulk create all the duplicated accesses
|
||||
models.DocumentAccess.objects.bulk_create(accesses_to_create)
|
||||
|
||||
if with_descendants:
|
||||
for child in document_to_duplicate.get_children().filter(
|
||||
ancestors_deleted_at__isnull=True
|
||||
):
|
||||
# When duplicating descendants, attach duplicates under the duplicated_document
|
||||
self._duplicate_document(
|
||||
document_to_duplicate=child,
|
||||
serializer=serializer,
|
||||
user=user,
|
||||
new_parent=duplicated_document,
|
||||
)
|
||||
|
||||
# Bulk create all the duplicated accesses
|
||||
models.DocumentAccess.objects.bulk_create(accesses_to_create)
|
||||
|
||||
return drf_response.Response(
|
||||
{"id": str(duplicated_document.id)}, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return duplicated_document
|
||||
|
||||
def _search_simple(self, request, text):
|
||||
"""
|
||||
@@ -1588,11 +1662,19 @@ class DocumentViewSet(
|
||||
or serializer.validated_data["is_unsafe"]
|
||||
):
|
||||
extra_args.update(
|
||||
{"ContentDisposition": f'attachment; filename="{file_name:s}"'}
|
||||
{
|
||||
"ContentDisposition": content_disposition_header(
|
||||
as_attachment=True, filename=file_name
|
||||
)
|
||||
}
|
||||
)
|
||||
else:
|
||||
extra_args.update(
|
||||
{"ContentDisposition": f'inline; filename="{file_name:s}"'}
|
||||
{
|
||||
"ContentDisposition": content_disposition_header(
|
||||
as_attachment=False, filename=file_name
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
file = serializer.validated_data["file"]
|
||||
@@ -1763,6 +1845,45 @@ class DocumentViewSet(
|
||||
|
||||
return drf.response.Response(body, status=drf.status.HTTP_200_OK)
|
||||
|
||||
@drf.decorators.action(
|
||||
detail=True,
|
||||
methods=["post"],
|
||||
name="Proxy AI requests to the AI provider",
|
||||
url_path="ai-proxy",
|
||||
throttle_classes=[utils.AIDocumentRateThrottle, utils.AIUserRateThrottle],
|
||||
)
|
||||
def ai_proxy(self, request, *args, **kwargs):
|
||||
"""
|
||||
POST /api/v1.0/documents/<resource_id>/ai-proxy
|
||||
Proxy AI requests to the configured AI provider.
|
||||
This endpoint forwards requests to the AI provider and returns the complete response.
|
||||
"""
|
||||
# Check permissions first
|
||||
self.get_object()
|
||||
|
||||
if not settings.AI_FEATURE_ENABLED or not settings.AI_FEATURE_BLOCKNOTE_ENABLED:
|
||||
raise ValidationError("AI feature is not enabled.")
|
||||
|
||||
ai_service = AIService()
|
||||
|
||||
try:
|
||||
stream = ai_service.stream(request)
|
||||
except PydanticValidationError as err:
|
||||
logger.info("pydantic validation error: %s", err)
|
||||
return drf.response.Response(
|
||||
{"detail": "Invalid submitted payload"},
|
||||
status=drf.status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
return StreamingHttpResponse(
|
||||
stream,
|
||||
content_type="text/event-stream",
|
||||
headers={
|
||||
"x-vercel-ai-data-stream": "v1", # This header is used for Vercel AI streaming,
|
||||
"X-Accel-Buffering": "no", # Prevent nginx buffering
|
||||
},
|
||||
)
|
||||
|
||||
@drf.decorators.action(
|
||||
detail=True,
|
||||
methods=["post"],
|
||||
@@ -2402,6 +2523,12 @@ class DocumentAskForAccessViewSet(
|
||||
"""Create a document ask for access resource."""
|
||||
document = self.get_document_or_404()
|
||||
|
||||
if document.get_role(request.user) in models.PRIVILEGED_ROLES:
|
||||
return drf.response.Response(
|
||||
{"detail": "You already have privileged access to this document."},
|
||||
status=drf.status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = serializers.DocumentAskForAccessCreateSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
@@ -2458,7 +2585,10 @@ class ConfigView(drf.views.APIView):
|
||||
Return a dictionary of public settings.
|
||||
"""
|
||||
array_settings = [
|
||||
"AI_BOT",
|
||||
"AI_FEATURE_ENABLED",
|
||||
"AI_FEATURE_BLOCKNOTE_ENABLED",
|
||||
"AI_FEATURE_LEGACY_ENABLED",
|
||||
"API_USERS_SEARCH_QUERY_MIN_LENGTH",
|
||||
"COLLABORATION_WS_URL",
|
||||
"COLLABORATION_WS_NOT_CONNECTED_READY_ONLY",
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
"""Clean a document by resetting it (keeping its title) and deleting all descendants."""
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
from core.choices import LinkReachChoices, LinkRoleChoices, RoleChoices
|
||||
from core.models import Document, DocumentAccess, Invitation, Thread
|
||||
|
||||
logger = logging.getLogger("impress.commands.clean_document")
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Reset a document (keeping its title) and delete all its descendants."""
|
||||
|
||||
help = __doc__
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""Define command arguments."""
|
||||
parser.add_argument(
|
||||
"document_id",
|
||||
type=str,
|
||||
help="UUID of the document to clean",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Force command execution despite DEBUG is set to False",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--title",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Update the document title to this value",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--link_reach",
|
||||
type=str,
|
||||
default=LinkReachChoices.RESTRICTED,
|
||||
choices=LinkReachChoices,
|
||||
help="Update the link_reach to this value",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--link_role",
|
||||
type=str,
|
||||
default=LinkRoleChoices.READER,
|
||||
choices=LinkRoleChoices,
|
||||
help="update the link_role to this value",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""Execute the clean_document command."""
|
||||
if not settings.DEBUG and not options["force"]:
|
||||
raise CommandError(
|
||||
"This command is not meant to be used in production environment "
|
||||
"except you know what you are doing, if so use --force parameter"
|
||||
)
|
||||
|
||||
document_id = options["document_id"]
|
||||
|
||||
try:
|
||||
document = Document.objects.get(pk=document_id)
|
||||
except (Document.DoesNotExist, ValueError) as err:
|
||||
raise CommandError(f"Document {document_id} does not exist.") from err
|
||||
|
||||
descendants = list(document.get_descendants())
|
||||
descendant_ids = [doc.id for doc in descendants]
|
||||
all_documents = [document, *descendants]
|
||||
|
||||
# Collect all attachment keys before the transaction clears them
|
||||
all_attachment_keys = []
|
||||
for doc in all_documents:
|
||||
all_attachment_keys.extend(doc.attachments)
|
||||
|
||||
self.stdout.write(
|
||||
f"Cleaning document {document_id} and deleting "
|
||||
f"{len(descendants)} descendant(s)..."
|
||||
)
|
||||
|
||||
with transaction.atomic():
|
||||
# Clean accesses and invitations on the root document
|
||||
access_count, _ = DocumentAccess.objects.filter(
|
||||
Q(document_id=document.id) & ~Q(role=RoleChoices.OWNER)
|
||||
).delete()
|
||||
self.stdout.write(f"Deleted {access_count} access(es) on root document.")
|
||||
|
||||
invitation_count, _ = Invitation.objects.filter(
|
||||
document_id=document.id
|
||||
).delete()
|
||||
self.stdout.write(
|
||||
f"Deleted {invitation_count} invitation(s) on root document."
|
||||
)
|
||||
|
||||
thread_count, _ = Thread.objects.filter(document_id=document.id).delete()
|
||||
self.stdout.write(f"Deleted {thread_count} thread(s) on root document.")
|
||||
|
||||
# Reset root document fields
|
||||
update_fields = {
|
||||
"excerpt": None,
|
||||
"link_reach": options["link_reach"],
|
||||
"link_role": options["link_role"],
|
||||
"attachments": [],
|
||||
}
|
||||
if options["title"] is not None:
|
||||
update_fields["title"] = options["title"]
|
||||
Document.objects.filter(id=document.id).update(**update_fields)
|
||||
|
||||
if options["title"] is not None:
|
||||
self.stdout.write(
|
||||
f'Reset fields on root document (title set to "{options["title"]}").'
|
||||
)
|
||||
else:
|
||||
self.stdout.write("Reset fields on root document (title kept).")
|
||||
|
||||
# Delete all descendants (cascades accesses and invitations)
|
||||
if descendants:
|
||||
deleted_count, _ = Document.objects.filter(
|
||||
id__in=descendant_ids
|
||||
).delete()
|
||||
self.stdout.write(f"Deleted {deleted_count} descendant(s).")
|
||||
|
||||
# Delete S3 content outside the transaction (S3 is not transactional)
|
||||
s3_client = default_storage.connection.meta.client
|
||||
bucket = default_storage.bucket_name
|
||||
|
||||
for doc in all_documents:
|
||||
try:
|
||||
s3_client.delete_object(Bucket=bucket, Key=doc.file_key)
|
||||
except ClientError:
|
||||
logger.warning("Failed to delete S3 file for document %s", doc.id)
|
||||
|
||||
self.stdout.write(f"Deleted S3 content for {len(all_documents)} document(s).")
|
||||
|
||||
for key in all_attachment_keys:
|
||||
try:
|
||||
s3_client.delete_object(Bucket=bucket, Key=key)
|
||||
except ClientError:
|
||||
logger.warning("Failed to delete S3 attachment %s", key)
|
||||
|
||||
self.stdout.write(f"Deleted {len(all_attachment_keys)} attachment(s) from S3.")
|
||||
self.stdout.write("Done.")
|
||||
@@ -19,3 +19,21 @@ class ForceSessionMiddleware:
|
||||
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
|
||||
|
||||
class SaveRawBodyMiddleware:
|
||||
"""
|
||||
Save the raw request body to use it later.
|
||||
"""
|
||||
|
||||
def __init__(self, get_response):
|
||||
"""Initialize the middleware."""
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
"""Save the raw request body in the request to use it later."""
|
||||
if request.path.endswith(("/ai-proxy/", "/ai-proxy")):
|
||||
request.raw_body = request.body
|
||||
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
|
||||
@@ -22,7 +22,7 @@ def set_path_on_existing_documents(apps, schema_editor):
|
||||
|
||||
# Iterate over all existing documents and make them root nodes
|
||||
documents = Document.objects.order_by("created_at").values_list("id", flat=True)
|
||||
numconv = NumConv(len(ALPHABET), ALPHABET)
|
||||
numconv = NumConv(ALPHABET)
|
||||
|
||||
updates = []
|
||||
for i, pk in enumerate(documents):
|
||||
|
||||
@@ -118,11 +118,11 @@ class UserManager(auth_models.UserManager):
|
||||
|
||||
if settings.OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION:
|
||||
try:
|
||||
return self.get(email=email)
|
||||
return self.get(email__iexact=email)
|
||||
except self.model.DoesNotExist:
|
||||
pass
|
||||
elif (
|
||||
self.filter(email=email).exists()
|
||||
self.filter(email__iexact=email).exists()
|
||||
and not settings.OIDC_ALLOW_DUPLICATE_EMAILS
|
||||
):
|
||||
raise DuplicateEmailError(
|
||||
@@ -209,14 +209,77 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
If it's a new user, give its user access to the documents to which s.he was invited.
|
||||
If it's a new user, give its user access to the documents they were invited to.
|
||||
"""
|
||||
is_adding = self._state.adding
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
if is_adding:
|
||||
self._handle_onboarding_documents_access()
|
||||
self._duplicate_onboarding_sandbox_document()
|
||||
self._convert_valid_invitations()
|
||||
|
||||
def _handle_onboarding_documents_access(self):
|
||||
"""
|
||||
If the user is new and there are documents configured to be given to new users,
|
||||
give access to these documents and pin them as favorites for the user.
|
||||
"""
|
||||
if settings.USER_ONBOARDING_DOCUMENTS:
|
||||
onboarding_document_ids = set(settings.USER_ONBOARDING_DOCUMENTS)
|
||||
onboarding_accesses = []
|
||||
favorite_documents = []
|
||||
for document_id in onboarding_document_ids:
|
||||
try:
|
||||
document = Document.objects.get(id=document_id)
|
||||
except Document.DoesNotExist:
|
||||
logger.warning(
|
||||
"Onboarding document with id %s does not exist. Skipping.",
|
||||
document_id,
|
||||
)
|
||||
continue
|
||||
|
||||
onboarding_accesses.append(
|
||||
DocumentAccess(
|
||||
user=self, document=document, role=RoleChoices.READER
|
||||
)
|
||||
)
|
||||
favorite_documents.append(
|
||||
DocumentFavorite(user=self, document_id=document_id)
|
||||
)
|
||||
|
||||
DocumentAccess.objects.bulk_create(onboarding_accesses)
|
||||
DocumentFavorite.objects.bulk_create(favorite_documents)
|
||||
|
||||
def _duplicate_onboarding_sandbox_document(self):
|
||||
"""
|
||||
If the user is new and there is a sandbox document configured,
|
||||
duplicate the sandbox document for the user
|
||||
"""
|
||||
if settings.USER_ONBOARDING_SANDBOX_DOCUMENT:
|
||||
sandbox_id = settings.USER_ONBOARDING_SANDBOX_DOCUMENT
|
||||
try:
|
||||
template_document = Document.objects.get(id=sandbox_id)
|
||||
|
||||
except Document.DoesNotExist:
|
||||
logger.warning(
|
||||
"Onboarding sandbox document with id %s does not exist. Skipping.",
|
||||
sandbox_id,
|
||||
)
|
||||
return
|
||||
|
||||
sandbox_document = template_document.add_sibling(
|
||||
"right",
|
||||
title=template_document.title,
|
||||
content=template_document.content,
|
||||
attachments=template_document.attachments,
|
||||
duplicated_from=template_document,
|
||||
creator=self,
|
||||
)
|
||||
|
||||
DocumentAccess.objects.create(
|
||||
user=self, document=sandbox_document, role=RoleChoices.OWNER
|
||||
)
|
||||
|
||||
def _convert_valid_invitations(self):
|
||||
"""
|
||||
Convert valid invitations to document accesses.
|
||||
@@ -1219,6 +1282,7 @@ class Document(MP_Node, BaseModel):
|
||||
return {
|
||||
"accesses_manage": is_owner_or_admin,
|
||||
"accesses_view": has_access_role,
|
||||
"ai_proxy": ai_access,
|
||||
"ai_transform": ai_access,
|
||||
"ai_translate": ai_access,
|
||||
"attachment_upload": can_update,
|
||||
@@ -1260,7 +1324,7 @@ class Document(MP_Node, BaseModel):
|
||||
"brandname": settings.EMAIL_BRAND_NAME,
|
||||
"document": self,
|
||||
"domain": domain,
|
||||
"link": f"{domain}/docs/{self.id}/",
|
||||
"link": f"{domain}/docs/{self.id}/?utm_source=docssharelink&utm_campaign={self.id}",
|
||||
"link_label": self.title or str(_("Untitled Document")),
|
||||
"button_label": _("Open"),
|
||||
"logo_img": settings.EMAIL_LOGO_IMG,
|
||||
@@ -1905,7 +1969,7 @@ class Invitation(BaseModel):
|
||||
|
||||
# Check if an identity already exists for the provided email
|
||||
if (
|
||||
User.objects.filter(email=self.email).exists()
|
||||
User.objects.filter(email__iexact=self.email).exists()
|
||||
and not settings.OIDC_ALLOW_DUPLICATE_EMAILS
|
||||
):
|
||||
raise ValidationError(
|
||||
|
||||
@@ -1,15 +1,68 @@
|
||||
"""AI services."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import queue
|
||||
import threading
|
||||
from collections.abc import AsyncIterator, Iterator
|
||||
from typing import Any, Dict, Union
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from langfuse import get_client
|
||||
from langfuse.openai import OpenAI as OpenAI_Langfuse
|
||||
from pydantic_ai import Agent, DeferredToolRequests
|
||||
from pydantic_ai.models.openai import OpenAIChatModel
|
||||
from pydantic_ai.providers.openai import OpenAIProvider
|
||||
from pydantic_ai.tools import ToolDefinition
|
||||
from pydantic_ai.toolsets.external import ExternalToolset
|
||||
from pydantic_ai.ui import SSE_CONTENT_TYPE
|
||||
from pydantic_ai.ui.vercel_ai import VercelAIAdapter
|
||||
from pydantic_ai.ui.vercel_ai.request_types import RequestData, TextUIPart, UIMessage
|
||||
from rest_framework.request import Request
|
||||
|
||||
from core import enums
|
||||
|
||||
if settings.LANGFUSE_PUBLIC_KEY:
|
||||
from langfuse.openai import OpenAI
|
||||
OpenAI = OpenAI_Langfuse
|
||||
else:
|
||||
from openai import OpenAI
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
BLOCKNOTE_TOOL_STRICT_PROMPT = """
|
||||
You are editing a BlockNote document via the tool applyDocumentOperations.
|
||||
|
||||
You MUST respond ONLY by calling applyDocumentOperations.
|
||||
The tool input MUST be valid JSON:
|
||||
{ "operations": [ ... ] }
|
||||
|
||||
Each operation MUST include "type" and it MUST be one of:
|
||||
- "update" (requires: id, block)
|
||||
- "add" (requires: referenceId, position, blocks)
|
||||
- "delete" (requires: id)
|
||||
|
||||
VALID SHAPES (FOLLOW EXACTLY):
|
||||
|
||||
Update:
|
||||
{ "type":"update", "id":"<id$>", "block":"<p>...</p>" }
|
||||
IMPORTANT: "block" MUST be a STRING containing a SINGLE valid HTML element.
|
||||
|
||||
Add:
|
||||
{ "type":"add", "referenceId":"<id$>", "position":"before|after", "blocks":["<p>...</p>"] }
|
||||
IMPORTANT: "blocks" MUST be an ARRAY OF STRINGS.
|
||||
Each item MUST be a STRING containing a SINGLE valid HTML element.
|
||||
|
||||
Delete:
|
||||
{ "type":"delete", "id":"<id$>" }
|
||||
|
||||
IDs ALWAYS end with "$". Use ids EXACTLY as provided.
|
||||
|
||||
Return ONLY the JSON tool input. No prose, no markdown.
|
||||
"""
|
||||
|
||||
AI_ACTIONS = {
|
||||
"prompt": (
|
||||
@@ -56,6 +109,40 @@ AI_TRANSLATE = (
|
||||
)
|
||||
|
||||
|
||||
def convert_async_generator_to_sync(async_gen: AsyncIterator[str]) -> Iterator[str]:
|
||||
"""Convert an async generator to a sync generator."""
|
||||
q: queue.Queue[str | object] = queue.Queue()
|
||||
sentinel = object()
|
||||
exc_sentinel = object()
|
||||
|
||||
async def run_async_gen():
|
||||
try:
|
||||
async for async_item in async_gen:
|
||||
q.put(async_item)
|
||||
except Exception as exc: # pylint: disable=broad-except #noqa: BLE001
|
||||
q.put((exc_sentinel, exc))
|
||||
finally:
|
||||
q.put(sentinel)
|
||||
|
||||
def start_async_loop():
|
||||
asyncio.run(run_async_gen())
|
||||
|
||||
thread = threading.Thread(target=start_async_loop, daemon=True)
|
||||
thread.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
item = q.get()
|
||||
if item is sentinel:
|
||||
break
|
||||
if isinstance(item, tuple) and item[0] is exc_sentinel:
|
||||
# re-raise the exception in the sync context
|
||||
raise item[1]
|
||||
yield item
|
||||
finally:
|
||||
thread.join()
|
||||
|
||||
|
||||
class AIService:
|
||||
"""Service class for AI-related operations."""
|
||||
|
||||
@@ -96,3 +183,198 @@ class AIService:
|
||||
language_display = enums.ALL_LANGUAGES.get(language, language)
|
||||
system_content = AI_TRANSLATE.format(language=language_display)
|
||||
return self.call_ai_api(system_content, text)
|
||||
|
||||
@staticmethod
|
||||
def inject_document_state_messages(
|
||||
messages: list[UIMessage],
|
||||
) -> list[UIMessage]:
|
||||
"""Inject document state context before user messages.
|
||||
|
||||
Port of BlockNote's injectDocumentStateMessages.
|
||||
For each user message carrying documentState metadata, an assistant
|
||||
message describing the current document/selection state is prepended
|
||||
so the LLM sees it as context.
|
||||
"""
|
||||
result: list[UIMessage] = []
|
||||
for message in messages:
|
||||
if (
|
||||
message.role == "user"
|
||||
and isinstance(message.metadata, dict)
|
||||
and "documentState" in message.metadata
|
||||
):
|
||||
doc_state = message.metadata["documentState"]
|
||||
selection = doc_state.get("selection")
|
||||
blocks = doc_state.get("blocks")
|
||||
|
||||
if selection:
|
||||
parts = [
|
||||
TextUIPart(
|
||||
text=(
|
||||
"This is the latest state of the selection "
|
||||
"(ignore previous selections, you MUST issue "
|
||||
"operations against this latest version of "
|
||||
"the selection):"
|
||||
),
|
||||
),
|
||||
TextUIPart(
|
||||
text=json.dumps(doc_state.get("selectedBlocks")),
|
||||
),
|
||||
TextUIPart(
|
||||
text=(
|
||||
"This is the latest state of the entire "
|
||||
"document (INCLUDING the selected text), you "
|
||||
"can use this to find the selected text to "
|
||||
"understand the context (but you MUST NOT "
|
||||
"issue operations against this document, you "
|
||||
"MUST issue operations against the selection):"
|
||||
),
|
||||
),
|
||||
TextUIPart(text=json.dumps(blocks)),
|
||||
]
|
||||
else:
|
||||
text = (
|
||||
"There is no active selection. This is the latest "
|
||||
"state of the document (ignore previous documents, "
|
||||
"you MUST issue operations against this latest "
|
||||
"version of the document). The cursor is BETWEEN "
|
||||
"two blocks as indicated by cursor: true."
|
||||
)
|
||||
if doc_state.get("isEmptyDocument"):
|
||||
text += (
|
||||
"Because the document is empty, YOU MUST first "
|
||||
"update the empty block before adding new blocks."
|
||||
)
|
||||
else:
|
||||
text += (
|
||||
"Prefer updating existing blocks over removing "
|
||||
"and adding (but this also depends on the "
|
||||
"user's question)."
|
||||
)
|
||||
parts = [
|
||||
TextUIPart(text=text),
|
||||
TextUIPart(text=json.dumps(blocks)),
|
||||
]
|
||||
|
||||
result.append(
|
||||
UIMessage(
|
||||
role="assistant",
|
||||
id=f"assistant-document-state-{message.id}",
|
||||
parts=parts,
|
||||
)
|
||||
)
|
||||
|
||||
result.append(message)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def tool_definitions_to_toolset(
|
||||
tool_definitions: Dict[str, Any],
|
||||
) -> ExternalToolset:
|
||||
"""Convert serialized tool definitions to a pydantic-ai ExternalToolset.
|
||||
|
||||
Port of BlockNote's toolDefinitionsToToolSet.
|
||||
Builds ToolDefinition objects from the JSON-Schema-based definitions
|
||||
sent by the frontend and wraps them in an ExternalToolset so that
|
||||
pydantic-ai advertises them to the LLM without trying to execute them
|
||||
server-side (execution is deferred to the frontend).
|
||||
"""
|
||||
tool_defs = [
|
||||
ToolDefinition(
|
||||
name=name,
|
||||
description=defn.get("description", ""),
|
||||
parameters_json_schema=defn.get("inputSchema", {}),
|
||||
kind="external",
|
||||
metadata={
|
||||
"output_schema": defn.get("outputSchema"),
|
||||
},
|
||||
)
|
||||
for name, defn in tool_definitions.items()
|
||||
]
|
||||
return ExternalToolset(tool_defs)
|
||||
|
||||
def _harden_messages(
|
||||
self, run_input: RequestData, tool_definitions: Dict[str, Any]
|
||||
):
|
||||
"""
|
||||
Harden messages if applyDocumentOperations tool is used.
|
||||
We would like the system_prompt property in the Agent initialization
|
||||
but for UI adapter, like vercel, the agent is ignoring it
|
||||
see https://github.com/pydantic/pydantic-ai/issues/3315
|
||||
|
||||
We have to inject it in the run_input.messages if needed.
|
||||
"""
|
||||
for name, _defn in tool_definitions.items():
|
||||
if name == "applyDocumentOperations":
|
||||
run_input.messages.insert(
|
||||
0,
|
||||
UIMessage(
|
||||
id="system-force-tool-usage",
|
||||
role="system",
|
||||
parts=[TextUIPart(text=BLOCKNOTE_TOOL_STRICT_PROMPT)],
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
def _build_async_stream(self, request: Request) -> AsyncIterator[str]:
|
||||
"""Build the async stream from the AI provider."""
|
||||
instrument_enabled = settings.LANGFUSE_PUBLIC_KEY is not None
|
||||
|
||||
if instrument_enabled:
|
||||
langfuse = get_client()
|
||||
langfuse.auth_check()
|
||||
Agent.instrument_all()
|
||||
|
||||
model = OpenAIChatModel(
|
||||
settings.AI_MODEL,
|
||||
provider=OpenAIProvider(
|
||||
base_url=settings.AI_BASE_URL, api_key=settings.AI_API_KEY
|
||||
),
|
||||
)
|
||||
agent = Agent(model, instrument=instrument_enabled)
|
||||
|
||||
accept = request.META.get("HTTP_ACCEPT", SSE_CONTENT_TYPE)
|
||||
|
||||
run_input = VercelAIAdapter.build_run_input(request.raw_body)
|
||||
|
||||
# Inject document state context into the conversation
|
||||
run_input.messages = self.inject_document_state_messages(run_input.messages)
|
||||
|
||||
# Build an ExternalToolset from frontend-supplied tool definitions
|
||||
raw_tool_defs = (
|
||||
run_input.model_extra.get("toolDefinitions")
|
||||
if run_input.model_extra
|
||||
else None
|
||||
)
|
||||
toolset = (
|
||||
self.tool_definitions_to_toolset(raw_tool_defs) if raw_tool_defs else None
|
||||
)
|
||||
|
||||
if raw_tool_defs:
|
||||
self._harden_messages(run_input, raw_tool_defs)
|
||||
|
||||
adapter = VercelAIAdapter(
|
||||
agent=agent,
|
||||
run_input=run_input,
|
||||
accept=accept,
|
||||
sdk_version=settings.AI_VERCEL_SDK_VERSION,
|
||||
)
|
||||
|
||||
event_stream = adapter.run_stream(
|
||||
output_type=[str, DeferredToolRequests] if toolset else None,
|
||||
toolsets=[toolset] if toolset else None,
|
||||
)
|
||||
|
||||
return adapter.encode_stream(event_stream)
|
||||
|
||||
def stream(self, request: Request) -> Union[AsyncIterator[str], Iterator[str]]:
|
||||
"""Stream AI API requests to the configured AI provider.
|
||||
|
||||
Returns an async iterator when running in async mode (ASGI)
|
||||
or a sync iterator when running in sync mode (WSGI).
|
||||
"""
|
||||
async_stream = self._build_async_stream(request)
|
||||
|
||||
if os.environ.get("PYTHON_SERVER_MODE", "sync") == "async":
|
||||
return async_stream
|
||||
|
||||
return convert_async_generator_to_sync(async_stream)
|
||||
|
||||
@@ -68,6 +68,30 @@ def test_authentication_getter_existing_user_via_email(
|
||||
assert user == db_user
|
||||
|
||||
|
||||
def test_authentication_getter_existing_user_via_email_case_insensitive(
|
||||
django_assert_num_queries, monkeypatch
|
||||
):
|
||||
"""
|
||||
If an existing user doesn't match the sub but matches the email with different case,
|
||||
the user should be returned (case-insensitive email matching).
|
||||
"""
|
||||
|
||||
klass = OIDCAuthenticationBackend()
|
||||
db_user = UserFactory(email="john.doe@example.com")
|
||||
|
||||
def get_userinfo_mocked(*args):
|
||||
return {"sub": "123", "email": "JOHN.DOE@EXAMPLE.COM"}
|
||||
|
||||
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
|
||||
|
||||
with django_assert_num_queries(4): # user by sub, user by mail, update sub
|
||||
user = klass.get_or_create_user(
|
||||
access_token="test-token", id_token=None, payload=None
|
||||
)
|
||||
|
||||
assert user == db_user
|
||||
|
||||
|
||||
def test_authentication_getter_email_none(monkeypatch):
|
||||
"""
|
||||
If no user is found with the sub and no email is provided, a new user should be created.
|
||||
@@ -157,6 +181,39 @@ def test_authentication_getter_existing_user_no_fallback_to_email_no_duplicate(
|
||||
assert models.User.objects.count() == 1
|
||||
|
||||
|
||||
def test_authentication_getter_existing_user_no_fallback_to_email_no_duplicate_case_insensitive(
|
||||
settings, monkeypatch
|
||||
):
|
||||
"""
|
||||
When the "OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION" setting is set to False,
|
||||
the system should detect duplicate emails even with different case.
|
||||
"""
|
||||
|
||||
klass = OIDCAuthenticationBackend()
|
||||
_db_user = UserFactory(email="john.doe@example.com")
|
||||
|
||||
# Set the setting to False
|
||||
settings.OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION = False
|
||||
settings.OIDC_ALLOW_DUPLICATE_EMAILS = False
|
||||
|
||||
def get_userinfo_mocked(*args):
|
||||
return {"sub": "123", "email": "JOHN.DOE@EXAMPLE.COM"}
|
||||
|
||||
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
|
||||
|
||||
with pytest.raises(
|
||||
SuspiciousOperation,
|
||||
match=(
|
||||
"We couldn't find a user with this sub but the email is already associated "
|
||||
"with a registered user."
|
||||
),
|
||||
):
|
||||
klass.get_or_create_user(access_token="test-token", id_token=None, payload=None)
|
||||
|
||||
# Since the sub doesn't match, it should not create a new user
|
||||
assert models.User.objects.count() == 1
|
||||
|
||||
|
||||
def test_authentication_getter_existing_user_with_email(
|
||||
django_assert_num_queries, monkeypatch
|
||||
):
|
||||
|
||||
@@ -1,313 +0,0 @@
|
||||
"""Unit tests for the `clean_document` management command."""
|
||||
|
||||
import random
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from django.core.management import CommandError, call_command
|
||||
|
||||
import pytest
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
from core import choices, factories, models
|
||||
from core.choices import LinkReachChoices, LinkRoleChoices
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_clean_document_with_descendants(settings):
|
||||
"""The command should reset the root (keeping title) and delete descendants."""
|
||||
settings.DEBUG = True
|
||||
|
||||
# Create a root document with subdocuments
|
||||
root = factories.DocumentFactory(
|
||||
title="Root",
|
||||
link_reach=LinkReachChoices.PUBLIC,
|
||||
link_role=LinkRoleChoices.EDITOR,
|
||||
)
|
||||
child = factories.DocumentFactory(
|
||||
parent=root,
|
||||
title="Child",
|
||||
link_reach=LinkReachChoices.AUTHENTICATED,
|
||||
link_role=LinkRoleChoices.EDITOR,
|
||||
)
|
||||
grandchild = factories.DocumentFactory(
|
||||
parent=child,
|
||||
title="Grandchild",
|
||||
)
|
||||
|
||||
# Create accesses and invitations
|
||||
factories.UserDocumentAccessFactory.create_batch(
|
||||
5,
|
||||
document=root,
|
||||
role=random.choice(
|
||||
[
|
||||
role
|
||||
for role in choices.RoleChoices
|
||||
if role not in choices.PRIVILEGED_ROLES
|
||||
],
|
||||
),
|
||||
)
|
||||
# One owner role
|
||||
factories.UserDocumentAccessFactory(document=root, role=choices.RoleChoices.OWNER)
|
||||
factories.UserDocumentAccessFactory(document=child)
|
||||
factories.InvitationFactory(document=root)
|
||||
factories.InvitationFactory(document=child)
|
||||
factories.ThreadFactory.create_batch(5, document=root)
|
||||
|
||||
assert models.Invitation.objects.filter(document=root).exists()
|
||||
assert models.Thread.objects.filter(document=root).exists()
|
||||
assert models.DocumentAccess.objects.filter(document=root).exists()
|
||||
|
||||
with mock.patch(
|
||||
"core.management.commands.clean_document.default_storage"
|
||||
) as mock_storage:
|
||||
call_command("clean_document", str(root.id), "--force")
|
||||
|
||||
# Root document should still exist with title kept and other fields reset
|
||||
root.refresh_from_db()
|
||||
assert root.title == "Root"
|
||||
assert root.excerpt is None
|
||||
assert root.link_reach == LinkReachChoices.RESTRICTED
|
||||
assert root.link_role == LinkRoleChoices.READER
|
||||
assert root.attachments == []
|
||||
|
||||
# Accesses and invitations on root should be deleted. Only owner should be kept
|
||||
keeping_accesses = list(models.DocumentAccess.objects.filter(document=root))
|
||||
assert len(keeping_accesses) == 1
|
||||
assert keeping_accesses[0].role == models.RoleChoices.OWNER
|
||||
assert not models.Invitation.objects.filter(document=root).exists()
|
||||
assert not models.Thread.objects.filter(document=root).exists()
|
||||
|
||||
# Descendants should be deleted entirely
|
||||
assert not models.Document.objects.filter(id__in=[child.id, grandchild.id]).exists()
|
||||
|
||||
# Root should have no descendants
|
||||
root.refresh_from_db()
|
||||
assert root.get_descendants().count() == 0
|
||||
|
||||
# S3 delete should have been called for document files + attachments
|
||||
delete_calls = mock_storage.connection.meta.client.delete_object.call_args_list
|
||||
assert len(delete_calls) == 3
|
||||
|
||||
|
||||
def test_clean_document_invalid_uuid(settings):
|
||||
"""The command should raise an error for a non-existent document."""
|
||||
settings.DEBUG = True
|
||||
|
||||
fake_id = str(uuid4())
|
||||
with pytest.raises(CommandError, match=f"Document {fake_id} does not exist."):
|
||||
call_command("clean_document", fake_id, "--force")
|
||||
|
||||
|
||||
def test_clean_document_no_force_in_production(settings):
|
||||
"""The command should require --force when DEBUG is False."""
|
||||
settings.DEBUG = False
|
||||
|
||||
doc = factories.DocumentFactory()
|
||||
with pytest.raises(CommandError, match="not meant to be used in production"):
|
||||
call_command("clean_document", str(doc.id))
|
||||
|
||||
|
||||
def test_clean_document_single_document(settings):
|
||||
"""The command should work on a single document without children."""
|
||||
settings.DEBUG = True
|
||||
|
||||
doc = factories.DocumentFactory(
|
||||
title="Single",
|
||||
link_reach=LinkReachChoices.PUBLIC,
|
||||
link_role=LinkRoleChoices.EDITOR,
|
||||
)
|
||||
factories.UserDocumentAccessFactory.create_batch(
|
||||
5,
|
||||
document=doc,
|
||||
role=random.choice(
|
||||
[
|
||||
role
|
||||
for role in choices.RoleChoices
|
||||
if role not in choices.PRIVILEGED_ROLES
|
||||
],
|
||||
),
|
||||
)
|
||||
# One owner role
|
||||
factories.UserDocumentAccessFactory(document=doc, role=choices.RoleChoices.OWNER)
|
||||
factories.ThreadFactory.create_batch(5, document=doc)
|
||||
factories.InvitationFactory(document=doc)
|
||||
|
||||
with mock.patch(
|
||||
"core.management.commands.clean_document.default_storage"
|
||||
) as mock_storage:
|
||||
call_command("clean_document", str(doc.id), "--force")
|
||||
|
||||
# Accesses and invitations on root should be deleted. Only owner should be kept
|
||||
keeping_accesses = list(models.DocumentAccess.objects.filter(document=doc))
|
||||
assert len(keeping_accesses) == 1
|
||||
assert keeping_accesses[0].role == models.RoleChoices.OWNER
|
||||
assert not models.Invitation.objects.filter(document=doc).exists()
|
||||
assert not models.Thread.objects.filter(document=doc).exists()
|
||||
|
||||
doc.refresh_from_db()
|
||||
assert doc.title == "Single"
|
||||
assert doc.excerpt is None
|
||||
assert doc.link_reach == LinkReachChoices.RESTRICTED
|
||||
assert doc.link_role == LinkRoleChoices.READER
|
||||
assert doc.attachments == []
|
||||
|
||||
mock_storage.connection.meta.client.delete_object.assert_called_once()
|
||||
|
||||
|
||||
def test_clean_document_with_title_option(settings):
|
||||
"""The --title option should update the document title."""
|
||||
settings.DEBUG = True
|
||||
|
||||
doc = factories.DocumentFactory(
|
||||
title="Old Title",
|
||||
link_reach=LinkReachChoices.PUBLIC,
|
||||
link_role=LinkRoleChoices.EDITOR,
|
||||
)
|
||||
|
||||
with mock.patch("core.management.commands.clean_document.default_storage"):
|
||||
call_command("clean_document", str(doc.id), "--force", "--title", "New Title")
|
||||
|
||||
doc.refresh_from_db()
|
||||
assert doc.title == "New Title"
|
||||
assert doc.excerpt is None
|
||||
assert doc.link_reach == LinkReachChoices.RESTRICTED
|
||||
assert doc.link_role == LinkRoleChoices.READER
|
||||
assert doc.attachments == []
|
||||
|
||||
|
||||
def test_clean_document_deletes_attachments_from_s3(settings):
|
||||
"""The command should delete attachment files from S3."""
|
||||
settings.DEBUG = True
|
||||
|
||||
root = factories.DocumentFactory(
|
||||
attachments=["root-id/attachments/file1.png", "root-id/attachments/file2.pdf"],
|
||||
)
|
||||
child = factories.DocumentFactory(
|
||||
parent=root,
|
||||
attachments=["child-id/attachments/file3.png"],
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
"core.management.commands.clean_document.default_storage"
|
||||
) as mock_storage:
|
||||
call_command("clean_document", str(root.id), "--force")
|
||||
|
||||
delete_calls = mock_storage.connection.meta.client.delete_object.call_args_list
|
||||
deleted_keys = [call.kwargs["Key"] for call in delete_calls]
|
||||
|
||||
# Document files (root + child)
|
||||
assert root.file_key in deleted_keys
|
||||
assert child.file_key in deleted_keys
|
||||
|
||||
# Attachment files
|
||||
assert "root-id/attachments/file1.png" in deleted_keys
|
||||
assert "root-id/attachments/file2.pdf" in deleted_keys
|
||||
assert "child-id/attachments/file3.png" in deleted_keys
|
||||
|
||||
assert len(delete_calls) == 5
|
||||
|
||||
|
||||
def test_clean_document_s3_errors_do_not_stop_command(settings):
|
||||
"""S3 deletion errors should be logged but not stop the command."""
|
||||
settings.DEBUG = True
|
||||
|
||||
doc = factories.DocumentFactory(
|
||||
attachments=["doc-id/attachments/file1.png"],
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
"core.management.commands.clean_document.default_storage"
|
||||
) as mock_storage:
|
||||
mock_storage.connection.meta.client.delete_object.side_effect = ClientError(
|
||||
{"Error": {"Code": "500", "Message": "Internal Error"}},
|
||||
"DeleteObject",
|
||||
)
|
||||
# Command should complete without raising
|
||||
call_command("clean_document", str(doc.id), "--force")
|
||||
|
||||
|
||||
def test_clean_document_with_options(settings):
|
||||
"""Run the command using optional argument link_reach and link_role."""
|
||||
|
||||
settings.DEBUG = True
|
||||
|
||||
# Create a root document with subdocuments
|
||||
root = factories.DocumentFactory(
|
||||
title="Root",
|
||||
link_reach=LinkReachChoices.PUBLIC,
|
||||
link_role=LinkRoleChoices.READER,
|
||||
)
|
||||
child = factories.DocumentFactory(
|
||||
parent=root,
|
||||
title="Child",
|
||||
link_reach=LinkReachChoices.AUTHENTICATED,
|
||||
link_role=LinkRoleChoices.EDITOR,
|
||||
)
|
||||
grandchild = factories.DocumentFactory(
|
||||
parent=child,
|
||||
title="Grandchild",
|
||||
)
|
||||
|
||||
# Create accesses and invitations
|
||||
factories.UserDocumentAccessFactory.create_batch(
|
||||
5,
|
||||
document=root,
|
||||
role=random.choice(
|
||||
[
|
||||
role
|
||||
for role in choices.RoleChoices
|
||||
if role not in choices.PRIVILEGED_ROLES
|
||||
],
|
||||
),
|
||||
)
|
||||
# One owner role
|
||||
factories.UserDocumentAccessFactory(document=root, role=choices.RoleChoices.OWNER)
|
||||
factories.UserDocumentAccessFactory(document=child)
|
||||
factories.InvitationFactory(document=root)
|
||||
factories.InvitationFactory(document=child)
|
||||
factories.ThreadFactory.create_batch(5, document=root)
|
||||
|
||||
assert models.Invitation.objects.filter(document=root).exists()
|
||||
assert models.Thread.objects.filter(document=root).exists()
|
||||
assert models.DocumentAccess.objects.filter(document=root).exists()
|
||||
|
||||
with mock.patch(
|
||||
"core.management.commands.clean_document.default_storage"
|
||||
) as mock_storage:
|
||||
call_command(
|
||||
"clean_document",
|
||||
str(root.id),
|
||||
"--force",
|
||||
"--link_reach",
|
||||
"public",
|
||||
"--link_role",
|
||||
"editor",
|
||||
)
|
||||
|
||||
# Root document should still exist with title kept and other fields reset
|
||||
root.refresh_from_db()
|
||||
assert root.title == "Root"
|
||||
assert root.excerpt is None
|
||||
assert root.link_reach == LinkReachChoices.PUBLIC
|
||||
assert root.link_role == LinkRoleChoices.EDITOR
|
||||
assert root.attachments == []
|
||||
|
||||
# Accesses and invitations on root should be deleted. Only owner should be kept
|
||||
keeping_accesses = list(models.DocumentAccess.objects.filter(document=root))
|
||||
assert len(keeping_accesses) == 1
|
||||
assert keeping_accesses[0].role == models.RoleChoices.OWNER
|
||||
assert not models.Invitation.objects.filter(document=root).exists()
|
||||
assert not models.Thread.objects.filter(document=root).exists()
|
||||
|
||||
# Descendants should be deleted entirely
|
||||
assert not models.Document.objects.filter(id__in=[child.id, grandchild.id]).exists()
|
||||
|
||||
# Root should have no descendants
|
||||
root.refresh_from_db()
|
||||
assert root.get_descendants().count() == 0
|
||||
|
||||
# S3 delete should have been called for document files + attachments
|
||||
delete_calls = mock_storage.connection.meta.client.delete_object.call_args_list
|
||||
assert len(delete_calls) == 3
|
||||
@@ -596,6 +596,38 @@ def test_api_document_invitations_create_cannot_invite_existing_users():
|
||||
}
|
||||
|
||||
|
||||
def test_api_item_invitations_create_cannot_invite_existing_users_case_insensitive():
|
||||
"""
|
||||
It should not be possible to invite already existing users, even with different email case.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
document = factories.DocumentFactory(users=[(user, "owner")])
|
||||
existing_user = factories.UserFactory()
|
||||
|
||||
# Build an invitation to the email of an existing identity with different case
|
||||
invitation_values = {
|
||||
"email": existing_user.email.upper(),
|
||||
"role": random.choice(models.RoleChoices.values),
|
||||
}
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{document.id!s}/invitations/",
|
||||
invitation_values,
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {
|
||||
"email": ["This email is already associated to a registered user."]
|
||||
}
|
||||
|
||||
|
||||
def test_api_document_invitations_create_lower_email():
|
||||
"""
|
||||
No matter the case, the email should be converted to lowercase.
|
||||
|
||||
387
src/backend/core/tests/documents/test_api_documents_ai_proxy.py
Normal file
@@ -0,0 +1,387 @@
|
||||
"""
|
||||
Test AI proxy API endpoint for users in impress's core app.
|
||||
"""
|
||||
|
||||
import random
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import override_settings
|
||||
|
||||
import pytest
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core import factories
|
||||
from core.tests.conftest import TEAM, USER, VIA
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def ai_settings(settings):
|
||||
"""Fixture to set AI settings."""
|
||||
settings.AI_MODEL = "llama"
|
||||
settings.AI_BASE_URL = "http://localhost-ai:12345/"
|
||||
settings.AI_API_KEY = "test-key"
|
||||
settings.AI_FEATURE_ENABLED = True
|
||||
settings.AI_FEATURE_BLOCKNOTE_ENABLED = True
|
||||
settings.AI_FEATURE_LEGACY_ENABLED = True
|
||||
settings.LANGFUSE_PUBLIC_KEY = None
|
||||
settings.AI_VERCEL_SDK_VERSION = 6
|
||||
|
||||
|
||||
@override_settings(
|
||||
AI_ALLOW_REACH_FROM=random.choice(["public", "authenticated", "restricted"])
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"reach, role",
|
||||
[
|
||||
("restricted", "reader"),
|
||||
("restricted", "editor"),
|
||||
("authenticated", "reader"),
|
||||
("authenticated", "editor"),
|
||||
("public", "reader"),
|
||||
],
|
||||
)
|
||||
def test_api_documents_ai_proxy_anonymous_forbidden(reach, role):
|
||||
"""
|
||||
Anonymous users should not be able to request AI proxy if the link reach
|
||||
and role don't allow it.
|
||||
"""
|
||||
document = factories.DocumentFactory(link_reach=reach, link_role=role)
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/ai-proxy/"
|
||||
response = APIClient().post(
|
||||
url,
|
||||
{
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
assert response.json() == {
|
||||
"detail": "Authentication credentials were not provided."
|
||||
}
|
||||
|
||||
|
||||
@override_settings(AI_ALLOW_REACH_FROM="public")
|
||||
@patch("core.services.ai_services.AIService.stream")
|
||||
def test_api_documents_ai_proxy_anonymous_success(mock_stream):
|
||||
"""
|
||||
Anonymous users should be able to request AI proxy to a document
|
||||
if the link reach and role permit it.
|
||||
"""
|
||||
document = factories.DocumentFactory(link_reach="public", link_role="editor")
|
||||
|
||||
mock_stream.return_value = iter(["data: chunk1\n", "data: chunk2\n"])
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/ai-proxy/"
|
||||
response = APIClient().post(
|
||||
url,
|
||||
b"{}",
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response["Content-Type"] == "text/event-stream"
|
||||
assert response["x-vercel-ai-data-stream"] == "v1"
|
||||
assert response["X-Accel-Buffering"] == "no"
|
||||
|
||||
content = b"".join(response.streaming_content).decode()
|
||||
assert "chunk1" in content
|
||||
assert "chunk2" in content
|
||||
mock_stream.assert_called_once()
|
||||
|
||||
|
||||
@override_settings(AI_ALLOW_REACH_FROM=random.choice(["authenticated", "restricted"]))
|
||||
def test_api_documents_ai_proxy_anonymous_limited_by_setting():
|
||||
"""
|
||||
Anonymous users should not be able to request AI proxy to a document
|
||||
if AI_ALLOW_REACH_FROM setting restricts it.
|
||||
"""
|
||||
document = factories.DocumentFactory(link_reach="public", link_role="editor")
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/ai-proxy/"
|
||||
response = APIClient().post(
|
||||
url,
|
||||
b"{}",
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reach, role",
|
||||
[
|
||||
("restricted", "reader"),
|
||||
("restricted", "editor"),
|
||||
("authenticated", "reader"),
|
||||
("public", "reader"),
|
||||
],
|
||||
)
|
||||
def test_api_documents_ai_proxy_authenticated_forbidden(reach, role):
|
||||
"""
|
||||
Users who are not related to a document can't request AI proxy if the
|
||||
link reach and role don't allow it.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach=reach, link_role=role)
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/ai-proxy/"
|
||||
response = client.post(
|
||||
url,
|
||||
b"{}",
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reach, role",
|
||||
[
|
||||
("authenticated", "editor"),
|
||||
("public", "editor"),
|
||||
],
|
||||
)
|
||||
@patch("core.services.ai_services.AIService.stream")
|
||||
def test_api_documents_ai_proxy_authenticated_success(mock_stream, reach, role):
|
||||
"""
|
||||
Authenticated users should be able to request AI proxy to a document
|
||||
if the link reach and role permit it.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach=reach, link_role=role)
|
||||
|
||||
mock_stream.return_value = iter(["data: response\n"])
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/ai-proxy/"
|
||||
response = client.post(
|
||||
url,
|
||||
b"{}",
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response["Content-Type"] == "text/event-stream"
|
||||
mock_stream.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_documents_ai_proxy_reader(via, mock_user_teams):
|
||||
"""Users with reader access should not be able to request AI proxy."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach="restricted")
|
||||
if via == USER:
|
||||
factories.UserDocumentAccessFactory(document=document, user=user, role="reader")
|
||||
elif via == TEAM:
|
||||
mock_user_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamDocumentAccessFactory(
|
||||
document=document, team="lasuite", role="reader"
|
||||
)
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/ai-proxy/"
|
||||
response = client.post(
|
||||
url,
|
||||
b"{}",
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", ["editor", "administrator", "owner"])
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
@patch("core.services.ai_services.AIService.stream")
|
||||
def test_api_documents_ai_proxy_success(mock_stream, via, role, mock_user_teams):
|
||||
"""Users with sufficient permissions should be able to request AI proxy."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach="restricted")
|
||||
if via == USER:
|
||||
factories.UserDocumentAccessFactory(document=document, user=user, role=role)
|
||||
elif via == TEAM:
|
||||
mock_user_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamDocumentAccessFactory(
|
||||
document=document, team="lasuite", role=role
|
||||
)
|
||||
|
||||
mock_stream.return_value = iter(["data: success\n"])
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/ai-proxy/"
|
||||
response = client.post(
|
||||
url,
|
||||
b"{}",
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response["Content-Type"] == "text/event-stream"
|
||||
assert response["x-vercel-ai-data-stream"] == "v1"
|
||||
assert response["X-Accel-Buffering"] == "no"
|
||||
|
||||
content = b"".join(response.streaming_content).decode()
|
||||
assert "success" in content
|
||||
mock_stream.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"setting_to_disable", ["AI_FEATURE_ENABLED", "AI_FEATURE_BLOCKNOTE_ENABLED"]
|
||||
)
|
||||
def test_api_documents_ai_proxy_ai_feature_disabled(settings, setting_to_disable):
|
||||
"""When AI_FEATURE_ENABLED is False, the endpoint returns 400."""
|
||||
setattr(settings, setting_to_disable, False)
|
||||
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach="public", link_role="editor")
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{document.id!s}/ai-proxy/",
|
||||
b"{}",
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json() == ["AI feature is not enabled."]
|
||||
|
||||
|
||||
@override_settings(AI_DOCUMENT_RATE_THROTTLE_RATES={"minute": 3, "hour": 6, "day": 10})
|
||||
@patch("core.services.ai_services.AIService.stream")
|
||||
def test_api_documents_ai_proxy_throttling_document(mock_stream):
|
||||
"""
|
||||
Throttling per document should be triggered on the AI proxy endpoint.
|
||||
For full throttle class test see: `test_api_utils_ai_document_rate_throttles`
|
||||
"""
|
||||
client = APIClient()
|
||||
document = factories.DocumentFactory(link_reach="public", link_role="editor")
|
||||
|
||||
mock_stream.return_value = iter(["data: ok\n"])
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/ai-proxy/"
|
||||
for _ in range(3):
|
||||
mock_stream.return_value = iter(["data: ok\n"])
|
||||
user = factories.UserFactory()
|
||||
client.force_login(user)
|
||||
response = client.post(
|
||||
url,
|
||||
b"{}",
|
||||
content_type="application/json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
user = factories.UserFactory()
|
||||
client.force_login(user)
|
||||
response = client.post(
|
||||
url,
|
||||
b"{}",
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 429
|
||||
assert response.json() == {
|
||||
"detail": "Request was throttled. Expected available in 60 seconds."
|
||||
}
|
||||
|
||||
|
||||
@override_settings(AI_USER_RATE_THROTTLE_RATES={"minute": 3, "hour": 6, "day": 10})
|
||||
@patch("core.services.ai_services.AIService.stream")
|
||||
def test_api_documents_ai_proxy_throttling_user(mock_stream):
|
||||
"""
|
||||
Throttling per user should be triggered on the AI proxy endpoint.
|
||||
For full throttle class test see: `test_api_utils_ai_user_rate_throttles`
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
for _ in range(3):
|
||||
mock_stream.return_value = iter(["data: ok\n"])
|
||||
document = factories.DocumentFactory(link_reach="public", link_role="editor")
|
||||
url = f"/api/v1.0/documents/{document.id!s}/ai-proxy/"
|
||||
response = client.post(
|
||||
url,
|
||||
b"{}",
|
||||
content_type="application/json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
document = factories.DocumentFactory(link_reach="public", link_role="editor")
|
||||
url = f"/api/v1.0/documents/{document.id!s}/ai-proxy/"
|
||||
response = client.post(
|
||||
url,
|
||||
b"{}",
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 429
|
||||
assert response.json() == {
|
||||
"detail": "Request was throttled. Expected available in 60 seconds."
|
||||
}
|
||||
|
||||
|
||||
@patch("core.services.ai_services.AIService.stream")
|
||||
def test_api_documents_ai_proxy_returns_streaming_response(mock_stream):
|
||||
"""AI proxy should return a StreamingHttpResponse with correct headers."""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(link_reach="public", link_role="editor")
|
||||
|
||||
mock_stream.return_value = iter(["data: part1\n", "data: part2\n", "data: part3\n"])
|
||||
|
||||
url = f"/api/v1.0/documents/{document.id!s}/ai-proxy/"
|
||||
response = client.post(
|
||||
url,
|
||||
b"{}",
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response["Content-Type"] == "text/event-stream"
|
||||
assert response["x-vercel-ai-data-stream"] == "v1"
|
||||
assert response["X-Accel-Buffering"] == "no"
|
||||
|
||||
chunks = list(response.streaming_content)
|
||||
assert len(chunks) == 3
|
||||
|
||||
|
||||
def test_api_documents_ai_proxy_invalid_payload():
|
||||
"""AI Proxy should return a 400 if the payload is invalid."""
|
||||
|
||||
user = factories.UserFactory()
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "owner")])
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{document.id!s}/ai-proxy/",
|
||||
b'{"foo": "bar", "trigger": "submit-message"}',
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {"detail": "Invalid submitted payload"}
|
||||
@@ -9,6 +9,7 @@ import pytest
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core.api.serializers import UserSerializer
|
||||
from core.choices import PRIVILEGED_ROLES
|
||||
from core.factories import (
|
||||
DocumentAskForAccessFactory,
|
||||
DocumentFactory,
|
||||
@@ -199,6 +200,27 @@ def test_api_documents_ask_for_access_create_authenticated_already_has_ask_for_a
|
||||
assert response.json() == {"detail": "You already ask to access to this document."}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", PRIVILEGED_ROLES)
|
||||
def test_api_documents_ask_for_access_create_authenticated_already_has_privileged_access(
|
||||
role,
|
||||
):
|
||||
"""
|
||||
Authenticated users with privileged access (owner or admin) should not be able to
|
||||
create a document ask for access.
|
||||
"""
|
||||
user = UserFactory()
|
||||
document = DocumentFactory(users=[(user, role)])
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.post(f"/api/v1.0/documents/{document.id}/ask-for-access/")
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {
|
||||
"detail": "You already have privileged access to this document."
|
||||
}
|
||||
|
||||
|
||||
## List
|
||||
|
||||
|
||||
|
||||
@@ -318,3 +318,424 @@ def test_api_documents_duplicate_reader_non_root_document():
|
||||
assert duplicated_document.is_root()
|
||||
assert duplicated_document.accesses.count() == 1
|
||||
assert duplicated_document.accesses.get(user=user).role == "owner"
|
||||
|
||||
|
||||
def test_api_documents_duplicate_with_descendants_simple():
|
||||
"""
|
||||
Duplicating a document with descendants flag should recursively duplicate all children.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
# Create document tree
|
||||
root = factories.DocumentFactory(
|
||||
users=[(user, "owner")],
|
||||
title="Root Document",
|
||||
)
|
||||
child1 = factories.DocumentFactory(
|
||||
parent=root,
|
||||
title="Child 1",
|
||||
)
|
||||
child2 = factories.DocumentFactory(
|
||||
parent=root,
|
||||
title="Child 2",
|
||||
)
|
||||
|
||||
initial_count = models.Document.objects.count()
|
||||
assert initial_count == 3
|
||||
|
||||
# Duplicate with descendants
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{root.id!s}/duplicate/",
|
||||
{"with_descendants": True},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
duplicated_root = models.Document.objects.get(id=response.json()["id"])
|
||||
|
||||
# Check that all documents were duplicated (6 total: 3 original + 3 duplicated)
|
||||
assert models.Document.objects.count() == 6
|
||||
|
||||
# Check root duplication
|
||||
assert duplicated_root.title == "Copy of Root Document"
|
||||
assert duplicated_root.creator == user
|
||||
assert duplicated_root.duplicated_from == root
|
||||
assert duplicated_root.get_children().count() == 2
|
||||
|
||||
# Check children duplication
|
||||
duplicated_children = duplicated_root.get_children().order_by("title")
|
||||
assert duplicated_children.count() == 2
|
||||
|
||||
duplicated_child1 = duplicated_children.first()
|
||||
assert duplicated_child1.title == "Copy of Child 1"
|
||||
assert duplicated_child1.creator == user
|
||||
assert duplicated_child1.duplicated_from == child1
|
||||
assert duplicated_child1.get_parent() == duplicated_root
|
||||
|
||||
duplicated_child2 = duplicated_children.last()
|
||||
assert duplicated_child2.title == "Copy of Child 2"
|
||||
assert duplicated_child2.creator == user
|
||||
assert duplicated_child2.duplicated_from == child2
|
||||
assert duplicated_child2.get_parent() == duplicated_root
|
||||
|
||||
|
||||
def test_api_documents_duplicate_with_descendants_multi_level():
|
||||
"""
|
||||
Duplicating should recursively handle multiple levels of nesting.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
root = factories.DocumentFactory(
|
||||
users=[(user, "owner")],
|
||||
title="Level 0",
|
||||
)
|
||||
child = factories.DocumentFactory(
|
||||
parent=root,
|
||||
title="Level 1",
|
||||
)
|
||||
grandchild = factories.DocumentFactory(
|
||||
parent=child,
|
||||
title="Level 2",
|
||||
)
|
||||
great_grandchild = factories.DocumentFactory(
|
||||
parent=grandchild,
|
||||
title="Level 3",
|
||||
)
|
||||
|
||||
initial_count = models.Document.objects.count()
|
||||
assert initial_count == 4
|
||||
|
||||
# Duplicate with descendants
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{root.id!s}/duplicate/",
|
||||
{"with_descendants": True},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
duplicated_root = models.Document.objects.get(id=response.json()["id"])
|
||||
|
||||
# Check that all documents were duplicated
|
||||
assert models.Document.objects.count() == 8
|
||||
|
||||
# Verify the tree structure
|
||||
assert duplicated_root.depth == root.depth
|
||||
dup_children = duplicated_root.get_children()
|
||||
assert dup_children.count() == 1
|
||||
|
||||
dup_child = dup_children.first()
|
||||
assert dup_child.title == "Copy of Level 1"
|
||||
assert dup_child.duplicated_from == child
|
||||
dup_grandchildren = dup_child.get_children()
|
||||
assert dup_grandchildren.count() == 1
|
||||
|
||||
dup_grandchild = dup_grandchildren.first()
|
||||
assert dup_grandchild.title == "Copy of Level 2"
|
||||
assert dup_grandchild.duplicated_from == grandchild
|
||||
dup_great_grandchildren = dup_grandchild.get_children()
|
||||
assert dup_great_grandchildren.count() == 1
|
||||
|
||||
dup_great_grandchild = dup_great_grandchildren.first()
|
||||
assert dup_great_grandchild.title == "Copy of Level 3"
|
||||
assert dup_great_grandchild.duplicated_from == great_grandchild
|
||||
|
||||
|
||||
def test_api_documents_duplicate_with_descendants_and_attachments():
|
||||
"""
|
||||
Duplicating with descendants should properly handle attachments in all children.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
# Create documents with attachments
|
||||
root_id = uuid.uuid4()
|
||||
child_id = uuid.uuid4()
|
||||
image_key_root, image_url_root = get_image_refs(root_id)
|
||||
image_key_child, image_url_child = get_image_refs(child_id)
|
||||
|
||||
# Create root document with attachment
|
||||
ydoc = pycrdt.Doc()
|
||||
fragment = pycrdt.XmlFragment(
|
||||
[
|
||||
pycrdt.XmlElement("img", {"src": image_url_root}),
|
||||
]
|
||||
)
|
||||
ydoc["document-store"] = fragment
|
||||
update = ydoc.get_update()
|
||||
root_content = base64.b64encode(update).decode("utf-8")
|
||||
|
||||
root = factories.DocumentFactory(
|
||||
id=root_id,
|
||||
users=[(user, "owner")],
|
||||
title="Root with Image",
|
||||
content=root_content,
|
||||
attachments=[image_key_root],
|
||||
)
|
||||
|
||||
# Create child with different attachment
|
||||
ydoc_child = pycrdt.Doc()
|
||||
fragment_child = pycrdt.XmlFragment(
|
||||
[
|
||||
pycrdt.XmlElement("img", {"src": image_url_child}),
|
||||
]
|
||||
)
|
||||
ydoc_child["document-store"] = fragment_child
|
||||
update_child = ydoc_child.get_update()
|
||||
child_content = base64.b64encode(update_child).decode("utf-8")
|
||||
|
||||
child = factories.DocumentFactory(
|
||||
id=child_id,
|
||||
parent=root,
|
||||
title="Child with Image",
|
||||
content=child_content,
|
||||
attachments=[image_key_child],
|
||||
)
|
||||
|
||||
# Duplicate with descendants
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{root.id!s}/duplicate/",
|
||||
{"with_descendants": True},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
duplicated_root = models.Document.objects.get(id=response.json()["id"])
|
||||
|
||||
# Check root attachments
|
||||
assert duplicated_root.attachments == [image_key_root]
|
||||
assert duplicated_root.content == root_content
|
||||
|
||||
# Check child attachments
|
||||
dup_children = duplicated_root.get_children()
|
||||
assert dup_children.count() == 1
|
||||
dup_child = dup_children.first()
|
||||
assert dup_child.attachments == [image_key_child]
|
||||
assert dup_child.content == child_content
|
||||
|
||||
|
||||
def test_api_documents_duplicate_with_descendants_and_accesses():
|
||||
"""
|
||||
Duplicating with descendants and accesses should propagate accesses to all children.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
other_user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
# Create document tree with accesses
|
||||
root = factories.DocumentFactory(
|
||||
users=[(user, "owner"), (other_user, "editor")],
|
||||
title="Root",
|
||||
)
|
||||
child = factories.DocumentFactory(
|
||||
parent=root,
|
||||
title="Child",
|
||||
)
|
||||
factories.UserDocumentAccessFactory(document=child, user=other_user, role="reader")
|
||||
|
||||
# Duplicate with descendants and accesses
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{root.id!s}/duplicate/",
|
||||
{"with_descendants": True, "with_accesses": True},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
duplicated_root = models.Document.objects.get(id=response.json()["id"])
|
||||
|
||||
# Check root accesses (should be duplicated)
|
||||
root_accesses = duplicated_root.accesses.order_by("user_id")
|
||||
assert root_accesses.count() == 2
|
||||
assert root_accesses.get(user=user).role == "owner"
|
||||
assert root_accesses.get(user=other_user).role == "editor"
|
||||
|
||||
# Check child accesses (should be duplicated)
|
||||
dup_children = duplicated_root.get_children()
|
||||
dup_child = dup_children.first()
|
||||
child_accesses = dup_child.accesses.order_by("user_id")
|
||||
assert child_accesses.count() == 1
|
||||
assert child_accesses.get(user=other_user).role == "reader"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("role", ["editor", "reader"])
|
||||
def test_api_documents_duplicate_with_descendants_non_root_document_becomes_root(role):
|
||||
"""
|
||||
When duplicating a non-root document with descendants as a reader/editor,
|
||||
it should become a root document and still duplicate its children.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
parent = factories.DocumentFactory(users=[(user, "owner")])
|
||||
child = factories.DocumentFactory(
|
||||
parent=parent,
|
||||
users=[(user, role)],
|
||||
title="Sub Document",
|
||||
)
|
||||
grandchild = factories.DocumentFactory(
|
||||
parent=child,
|
||||
title="Grandchild",
|
||||
)
|
||||
|
||||
assert child.is_child_of(parent)
|
||||
|
||||
# Duplicate the child (non-root) with descendants
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{child.id!s}/duplicate/",
|
||||
{"with_descendants": True},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
duplicated_child = models.Document.objects.get(id=response.json()["id"])
|
||||
|
||||
assert duplicated_child.title == "Copy of Sub Document"
|
||||
|
||||
dup_grandchildren = duplicated_child.get_children()
|
||||
assert dup_grandchildren.count() == 1
|
||||
dup_grandchild = dup_grandchildren.first()
|
||||
assert dup_grandchild.title == "Copy of Grandchild"
|
||||
assert dup_grandchild.duplicated_from == grandchild
|
||||
|
||||
|
||||
def test_api_documents_duplicate_without_descendants_should_not_duplicate_children():
|
||||
"""
|
||||
When with_descendants is not set or False, children should not be duplicated.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
# Create document tree
|
||||
root = factories.DocumentFactory(
|
||||
users=[(user, "owner")],
|
||||
title="Root",
|
||||
)
|
||||
child = factories.DocumentFactory(
|
||||
parent=root,
|
||||
title="Child",
|
||||
)
|
||||
|
||||
initial_count = models.Document.objects.count()
|
||||
assert initial_count == 2
|
||||
|
||||
# Duplicate without descendants (default behavior)
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{root.id!s}/duplicate/",
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
duplicated_root = models.Document.objects.get(id=response.json()["id"])
|
||||
|
||||
# Only root should be duplicated, not children
|
||||
assert models.Document.objects.count() == 3
|
||||
assert duplicated_root.get_children().count() == 0
|
||||
|
||||
|
||||
def test_api_documents_duplicate_with_descendants_preserves_link_configuration():
|
||||
"""
|
||||
Duplicating with descendants should preserve link configuration (link_reach, link_role)
|
||||
for all children when with_accesses is True.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
# Create document tree with specific link configurations
|
||||
root = factories.DocumentFactory(
|
||||
users=[(user, "owner")],
|
||||
title="Root",
|
||||
link_reach="public",
|
||||
link_role="reader",
|
||||
)
|
||||
child = factories.DocumentFactory(
|
||||
parent=root,
|
||||
title="Child",
|
||||
link_reach="restricted",
|
||||
link_role="editor",
|
||||
)
|
||||
|
||||
# Duplicate with descendants and accesses
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{root.id!s}/duplicate/",
|
||||
{"with_descendants": True, "with_accesses": True},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
duplicated_root = models.Document.objects.get(id=response.json()["id"])
|
||||
|
||||
# Check root link configuration
|
||||
assert duplicated_root.link_reach == root.link_reach
|
||||
assert duplicated_root.link_role == root.link_role
|
||||
|
||||
# Check child link configuration
|
||||
dup_children = duplicated_root.get_children()
|
||||
dup_child = dup_children.first()
|
||||
assert dup_child.link_reach == child.link_reach
|
||||
assert dup_child.link_role == child.link_role
|
||||
|
||||
|
||||
def test_api_documents_duplicate_with_descendants_complex_tree():
|
||||
"""
|
||||
Test duplication of a complex tree structure with multiple branches.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
# Create a complex tree:
|
||||
# root
|
||||
# / \
|
||||
# c1 c2
|
||||
# / \ \
|
||||
# gc1 gc2 gc3
|
||||
root = factories.DocumentFactory(
|
||||
users=[(user, "owner")],
|
||||
title="Root",
|
||||
)
|
||||
child1 = factories.DocumentFactory(parent=root, title="Child 1")
|
||||
child2 = factories.DocumentFactory(parent=root, title="Child 2")
|
||||
_grandchild1 = factories.DocumentFactory(parent=child1, title="GrandChild 1")
|
||||
_grandchild2 = factories.DocumentFactory(parent=child1, title="GrandChild 2")
|
||||
_grandchild3 = factories.DocumentFactory(parent=child2, title="GrandChild 3")
|
||||
|
||||
initial_count = models.Document.objects.count()
|
||||
assert initial_count == 6
|
||||
|
||||
# Duplicate with descendants
|
||||
response = client.post(
|
||||
f"/api/v1.0/documents/{root.id!s}/duplicate/",
|
||||
{"with_descendants": True},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
duplicated_root = models.Document.objects.get(id=response.json()["id"])
|
||||
|
||||
# All documents should be duplicated
|
||||
assert models.Document.objects.count() == 12
|
||||
|
||||
# Check structure is preserved
|
||||
dup_children = duplicated_root.get_children().order_by("title")
|
||||
assert dup_children.count() == 2
|
||||
|
||||
dup_child1 = dup_children.first()
|
||||
assert dup_child1.title == "Copy of Child 1"
|
||||
dup_grandchildren1 = dup_child1.get_children().order_by("title")
|
||||
assert dup_grandchildren1.count() == 2
|
||||
assert dup_grandchildren1.first().title == "Copy of GrandChild 1"
|
||||
assert dup_grandchildren1.last().title == "Copy of GrandChild 2"
|
||||
|
||||
dup_child2 = dup_children.last()
|
||||
assert dup_child2.title == "Copy of Child 2"
|
||||
dup_grandchildren2 = dup_child2.get_children()
|
||||
assert dup_grandchildren2.count() == 1
|
||||
assert dup_grandchildren2.first().title == "Copy of GrandChild 3"
|
||||
|
||||
@@ -29,6 +29,7 @@ def test_api_documents_retrieve_anonymous_public_standalone():
|
||||
"abilities": {
|
||||
"accesses_manage": False,
|
||||
"accesses_view": False,
|
||||
"ai_proxy": False,
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": document.link_role == "editor",
|
||||
@@ -107,6 +108,7 @@ def test_api_documents_retrieve_anonymous_public_parent():
|
||||
"abilities": {
|
||||
"accesses_manage": False,
|
||||
"accesses_view": False,
|
||||
"ai_proxy": False,
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": grand_parent.link_role == "editor",
|
||||
@@ -215,6 +217,7 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated(
|
||||
"abilities": {
|
||||
"accesses_manage": False,
|
||||
"accesses_view": False,
|
||||
"ai_proxy": document.link_role == "editor",
|
||||
"ai_transform": document.link_role == "editor",
|
||||
"ai_translate": document.link_role == "editor",
|
||||
"attachment_upload": document.link_role == "editor",
|
||||
@@ -300,6 +303,7 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
|
||||
"abilities": {
|
||||
"accesses_manage": False,
|
||||
"accesses_view": False,
|
||||
"ai_proxy": grand_parent.link_role == "editor",
|
||||
"ai_transform": grand_parent.link_role == "editor",
|
||||
"ai_translate": grand_parent.link_role == "editor",
|
||||
"attachment_upload": grand_parent.link_role == "editor",
|
||||
@@ -498,6 +502,7 @@ def test_api_documents_retrieve_authenticated_related_parent():
|
||||
"abilities": {
|
||||
"accesses_manage": access.role in ["administrator", "owner"],
|
||||
"accesses_view": True,
|
||||
"ai_proxy": access.role not in ["reader", "commenter"],
|
||||
"ai_transform": access.role not in ["reader", "commenter"],
|
||||
"ai_translate": access.role not in ["reader", "commenter"],
|
||||
"attachment_upload": access.role not in ["reader", "commenter"],
|
||||
@@ -1057,3 +1062,48 @@ def test_api_documents_retrieve_permanently_deleted_related(role, depth):
|
||||
|
||||
assert response.status_code == 404
|
||||
assert response.json() == {"detail": "Not found."}
|
||||
|
||||
|
||||
def test_api_documents_retrieve_without_content():
|
||||
"""
|
||||
Test retrieve using without_content query string should remove the content in the response
|
||||
"""
|
||||
|
||||
user = factories.UserFactory()
|
||||
|
||||
document = factories.DocumentFactory(creator=user, users=[(user, "owner")])
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
with mock.patch("core.models.Document.content") as mock_document_content:
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{document.id!s}/?without_content=true"
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
payload = response.json()
|
||||
assert "content" not in payload
|
||||
mock_document_content.assert_not_called()
|
||||
|
||||
|
||||
def test_api_documents_retrieve_without_content_invalid_value():
|
||||
"""
|
||||
Test retrieve using without_content query string but an invalid value
|
||||
should return a 400
|
||||
"""
|
||||
|
||||
user = factories.UserFactory()
|
||||
|
||||
document = factories.DocumentFactory(creator=user, users=[(user, "owner")])
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1.0/documents/{document.id!s}/?without_content=invalid-value"
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
assert response.json() == ["Must be a valid boolean."]
|
||||
|
||||
@@ -72,6 +72,7 @@ def test_api_documents_trashbin_format():
|
||||
"abilities": {
|
||||
"accesses_manage": False,
|
||||
"accesses_view": False,
|
||||
"ai_proxy": False,
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": False,
|
||||
|
||||
@@ -19,7 +19,10 @@ pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@override_settings(
|
||||
AI_BOT={"name": "Test Bot", "color": "#000000"},
|
||||
AI_FEATURE_ENABLED=False,
|
||||
AI_FEATURE_BLOCKNOTE_ENABLED=False,
|
||||
AI_FEATURE_LEGACY_ENABLED=False,
|
||||
API_USERS_SEARCH_QUERY_MIN_LENGTH=6,
|
||||
COLLABORATION_WS_URL="http://testcollab/",
|
||||
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY=True,
|
||||
@@ -44,7 +47,10 @@ def test_api_config(is_authenticated):
|
||||
response = client.get("/api/v1.0/config/")
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response.json() == {
|
||||
"AI_BOT": {"name": "Test Bot", "color": "#000000"},
|
||||
"AI_FEATURE_ENABLED": False,
|
||||
"AI_FEATURE_BLOCKNOTE_ENABLED": False,
|
||||
"AI_FEATURE_LEGACY_ENABLED": False,
|
||||
"API_USERS_SEARCH_QUERY_MIN_LENGTH": 6,
|
||||
"COLLABORATION_WS_URL": "http://testcollab/",
|
||||
"COLLABORATION_WS_NOT_CONNECTED_READY_ONLY": True,
|
||||
|
||||
@@ -155,6 +155,7 @@ def test_models_documents_get_abilities_forbidden(
|
||||
expected_abilities = {
|
||||
"accesses_manage": False,
|
||||
"accesses_view": False,
|
||||
"ai_proxy": False,
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": False,
|
||||
@@ -220,6 +221,7 @@ def test_models_documents_get_abilities_reader(
|
||||
expected_abilities = {
|
||||
"accesses_manage": False,
|
||||
"accesses_view": False,
|
||||
"ai_proxy": False,
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": False,
|
||||
@@ -290,6 +292,7 @@ def test_models_documents_get_abilities_commenter(
|
||||
expected_abilities = {
|
||||
"accesses_manage": False,
|
||||
"accesses_view": False,
|
||||
"ai_proxy": False,
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": False,
|
||||
@@ -357,6 +360,7 @@ def test_models_documents_get_abilities_editor(
|
||||
expected_abilities = {
|
||||
"accesses_manage": False,
|
||||
"accesses_view": False,
|
||||
"ai_proxy": is_authenticated,
|
||||
"ai_transform": is_authenticated,
|
||||
"ai_translate": is_authenticated,
|
||||
"attachment_upload": True,
|
||||
@@ -413,6 +417,7 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
|
||||
expected_abilities = {
|
||||
"accesses_manage": True,
|
||||
"accesses_view": True,
|
||||
"ai_proxy": True,
|
||||
"ai_transform": True,
|
||||
"ai_translate": True,
|
||||
"attachment_upload": True,
|
||||
@@ -455,6 +460,7 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
|
||||
assert document.get_abilities(user) == {
|
||||
"accesses_manage": False,
|
||||
"accesses_view": False,
|
||||
"ai_proxy": False,
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": False,
|
||||
@@ -501,6 +507,7 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries)
|
||||
expected_abilities = {
|
||||
"accesses_manage": True,
|
||||
"accesses_view": True,
|
||||
"ai_proxy": True,
|
||||
"ai_transform": True,
|
||||
"ai_translate": True,
|
||||
"attachment_upload": True,
|
||||
@@ -557,6 +564,7 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
|
||||
expected_abilities = {
|
||||
"accesses_manage": False,
|
||||
"accesses_view": True,
|
||||
"ai_proxy": True,
|
||||
"ai_transform": True,
|
||||
"ai_translate": True,
|
||||
"attachment_upload": True,
|
||||
@@ -620,6 +628,7 @@ def test_models_documents_get_abilities_reader_user(
|
||||
"accesses_view": True,
|
||||
# If you get your editor rights from the link role and not your access role
|
||||
# You should not access AI if it's restricted to users with specific access
|
||||
"ai_proxy": access_from_link and ai_access_setting != "restricted",
|
||||
"ai_transform": access_from_link and ai_access_setting != "restricted",
|
||||
"ai_translate": access_from_link and ai_access_setting != "restricted",
|
||||
"attachment_upload": access_from_link,
|
||||
@@ -686,6 +695,7 @@ def test_models_documents_get_abilities_commenter_user(
|
||||
"accesses_view": True,
|
||||
# If you get your editor rights from the link role and not your access role
|
||||
# You should not access AI if it's restricted to users with specific access
|
||||
"ai_proxy": access_from_link and ai_access_setting != "restricted",
|
||||
"ai_transform": access_from_link and ai_access_setting != "restricted",
|
||||
"ai_translate": access_from_link and ai_access_setting != "restricted",
|
||||
"attachment_upload": access_from_link,
|
||||
@@ -747,6 +757,7 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
|
||||
assert abilities == {
|
||||
"accesses_manage": False,
|
||||
"accesses_view": True,
|
||||
"ai_proxy": False,
|
||||
"ai_transform": False,
|
||||
"ai_translate": False,
|
||||
"attachment_upload": False,
|
||||
@@ -878,6 +889,7 @@ def test_models_document_get_abilities_ai_access_authenticated(is_authenticated,
|
||||
document = factories.DocumentFactory(link_reach=reach, link_role="editor")
|
||||
|
||||
abilities = document.get_abilities(user)
|
||||
assert abilities["ai_proxy"] is True
|
||||
assert abilities["ai_transform"] is True
|
||||
assert abilities["ai_translate"] is True
|
||||
|
||||
@@ -897,6 +909,7 @@ def test_models_document_get_abilities_ai_access_public(is_authenticated, reach)
|
||||
document = factories.DocumentFactory(link_reach=reach, link_role="editor")
|
||||
|
||||
abilities = document.get_abilities(user)
|
||||
assert abilities["ai_proxy"] == is_authenticated
|
||||
assert abilities["ai_transform"] == is_authenticated
|
||||
assert abilities["ai_translate"] == is_authenticated
|
||||
|
||||
@@ -1021,7 +1034,10 @@ def test_models_documents__email_invitation__success():
|
||||
f"Test Sender (sender@example.com) invited you with the role "editor" "
|
||||
f"on the following document: {document.title}" in email_content
|
||||
)
|
||||
assert f"docs/{document.id}/" in email_content
|
||||
assert (
|
||||
f"docs/{document.id}/?utm_source=docssharelink&utm_campaign={document.id}"
|
||||
in email_content
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -1051,10 +1067,18 @@ def test_models_documents__email_invitation__url_app_param(email_url_app):
|
||||
|
||||
# Determine expected domain
|
||||
if email_url_app:
|
||||
assert f"https://test-example.com/docs/{document.id}/" in email_content
|
||||
expected_url = (
|
||||
f"https://test-example.com/docs/{document.id}/"
|
||||
f"?utm_source=docssharelink&utm_campaign={document.id}"
|
||||
)
|
||||
assert expected_url in email_content
|
||||
else:
|
||||
# Default Site domain is example.com
|
||||
assert f"example.com/docs/{document.id}/" in email_content
|
||||
expected_url = (
|
||||
f"example.com/docs/{document.id}/"
|
||||
f"?utm_source=docssharelink&utm_campaign={document.id}"
|
||||
)
|
||||
assert expected_url in email_content
|
||||
|
||||
|
||||
def test_models_documents__email_invitation__success_empty_title():
|
||||
@@ -1085,7 +1109,10 @@ def test_models_documents__email_invitation__success_empty_title():
|
||||
"Test Sender (sender@example.com) invited you with the role "editor" "
|
||||
"on the following document: Untitled Document" in email_content
|
||||
)
|
||||
assert f"docs/{document.id}/" in email_content
|
||||
assert (
|
||||
f"docs/{document.id}/?utm_source=docssharelink&utm_campaign={document.id}"
|
||||
in email_content
|
||||
)
|
||||
|
||||
|
||||
def test_models_documents__email_invitation__success_fr():
|
||||
@@ -1120,7 +1147,10 @@ def test_models_documents__email_invitation__success_fr():
|
||||
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
|
||||
)
|
||||
assert f"docs/{document.id}/" in email_content
|
||||
assert (
|
||||
f"docs/{document.id}/?utm_source=docssharelink&utm_campaign={document.id}"
|
||||
in email_content
|
||||
)
|
||||
|
||||
|
||||
@mock.patch(
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
Unit tests for the User model
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test.utils import override_settings
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -74,3 +78,217 @@ def test_modes_users_convert_valid_invitations():
|
||||
id=invitation_other_document.id
|
||||
).exists()
|
||||
assert models.Invitation.objects.filter(id=other_email_invitation.id).exists()
|
||||
|
||||
|
||||
@override_settings(USER_ONBOARDING_DOCUMENTS=[])
|
||||
def test_models_users_handle_onboarding_documents_access_empty_setting():
|
||||
"""
|
||||
When USER_ONBOARDING_DOCUMENTS is empty, no accesses should be created.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
assert models.DocumentAccess.objects.filter(user=user).count() == 0
|
||||
|
||||
|
||||
def test_models_users_handle_onboarding_documents_access_with_single_document():
|
||||
"""
|
||||
When USER_ONBOARDING_DOCUMENTS has a valid document ID,
|
||||
an access should be created for the new user with the READER role.
|
||||
|
||||
The document should be pinned as a favorite for the user.
|
||||
"""
|
||||
document = factories.DocumentFactory()
|
||||
|
||||
with override_settings(USER_ONBOARDING_DOCUMENTS=[str(document.id)]):
|
||||
user = factories.UserFactory()
|
||||
|
||||
assert (
|
||||
models.DocumentAccess.objects.filter(user=user, document=document).count() == 1
|
||||
)
|
||||
|
||||
access = models.DocumentAccess.objects.get(user=user, document=document)
|
||||
assert access.role == models.RoleChoices.READER
|
||||
|
||||
user_favorites = models.DocumentFavorite.objects.filter(user=user)
|
||||
assert user_favorites.count() == 1
|
||||
assert user_favorites.filter(document=document).exists()
|
||||
|
||||
|
||||
def test_models_users_handle_onboarding_documents_access_with_multiple_documents():
|
||||
"""
|
||||
When USER_ONBOARDING_DOCUMENTS has multiple valid document IDs,
|
||||
accesses should be created for all documents.
|
||||
|
||||
All accesses should have the READER role.
|
||||
All documents should be pinned as favorites for the user.
|
||||
"""
|
||||
document1 = factories.DocumentFactory(title="Document 1")
|
||||
document2 = factories.DocumentFactory(title="Document 2")
|
||||
document3 = factories.DocumentFactory(title="Document 3")
|
||||
|
||||
with override_settings(
|
||||
USER_ONBOARDING_DOCUMENTS=[
|
||||
str(document1.id),
|
||||
str(document2.id),
|
||||
str(document3.id),
|
||||
]
|
||||
):
|
||||
user = factories.UserFactory()
|
||||
|
||||
user_accesses = models.DocumentAccess.objects.filter(user=user)
|
||||
assert user_accesses.count() == 3
|
||||
|
||||
assert models.DocumentAccess.objects.filter(user=user, document=document1).exists()
|
||||
assert models.DocumentAccess.objects.filter(user=user, document=document2).exists()
|
||||
assert models.DocumentAccess.objects.filter(user=user, document=document3).exists()
|
||||
|
||||
for access in user_accesses:
|
||||
assert access.role == models.RoleChoices.READER
|
||||
|
||||
user_favorites = models.DocumentFavorite.objects.filter(user=user)
|
||||
assert user_favorites.count() == 3
|
||||
assert user_favorites.filter(document=document1).exists()
|
||||
assert user_favorites.filter(document=document2).exists()
|
||||
assert user_favorites.filter(document=document3).exists()
|
||||
|
||||
|
||||
def test_models_users_handle_onboarding_documents_access_with_invalid_document_id():
|
||||
"""
|
||||
When USER_ONBOARDING_DOCUMENTS has an invalid document ID,
|
||||
it should be skipped and logged, but not raise an exception.
|
||||
"""
|
||||
invalid_id = uuid.uuid4()
|
||||
|
||||
with override_settings(USER_ONBOARDING_DOCUMENTS=[str(invalid_id)]):
|
||||
with patch("core.models.logger") as mock_logger:
|
||||
user = factories.UserFactory()
|
||||
|
||||
mock_logger.warning.assert_called_once()
|
||||
call_args = mock_logger.warning.call_args
|
||||
assert "Onboarding document with id" in call_args[0][0]
|
||||
|
||||
assert models.DocumentAccess.objects.filter(user=user).count() == 0
|
||||
|
||||
|
||||
def test_models_users_handle_onboarding_documents_access_duplicate_prevention():
|
||||
"""
|
||||
If the same document is listed multiple times in USER_ONBOARDING_DOCUMENTS,
|
||||
it should only create one access (or handle duplicates gracefully).
|
||||
"""
|
||||
document = factories.DocumentFactory()
|
||||
|
||||
with override_settings(
|
||||
USER_ONBOARDING_DOCUMENTS=[str(document.id), str(document.id)]
|
||||
):
|
||||
user = factories.UserFactory()
|
||||
|
||||
user_accesses = models.DocumentAccess.objects.filter(user=user, document=document)
|
||||
|
||||
assert user_accesses.count() >= 1
|
||||
|
||||
|
||||
@override_settings(USER_ONBOARDING_SANDBOX_DOCUMENT=None)
|
||||
def test_models_users_duplicate_onboarding_sandbox_document_no_setting():
|
||||
"""
|
||||
When USER_ONBOARDING_SANDBOX_DOCUMENT is not set, no sandbox document should be created.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
assert (
|
||||
models.Document.objects.filter(creator=user, title__icontains="Sandbox").count()
|
||||
== 0
|
||||
)
|
||||
|
||||
initial_accesses = models.DocumentAccess.objects.filter(user=user).count()
|
||||
assert initial_accesses == 0
|
||||
|
||||
|
||||
def test_models_users_duplicate_onboarding_sandbox_document_creates_sandbox():
|
||||
"""
|
||||
When USER_ONBOARDING_SANDBOX_DOCUMENT is set with a valid template document,
|
||||
a new sandbox document should be created for the user with OWNER access.
|
||||
"""
|
||||
template_document = factories.DocumentFactory(title="Getting started with Docs")
|
||||
|
||||
with override_settings(USER_ONBOARDING_SANDBOX_DOCUMENT=str(template_document.id)):
|
||||
user = factories.UserFactory()
|
||||
|
||||
sandbox_docs = models.Document.objects.filter(
|
||||
creator=user, title="Getting started with Docs"
|
||||
)
|
||||
assert sandbox_docs.count() == 1
|
||||
|
||||
sandbox_doc = sandbox_docs.first()
|
||||
assert sandbox_doc.creator == user
|
||||
assert sandbox_doc.duplicated_from == template_document
|
||||
|
||||
access = models.DocumentAccess.objects.get(user=user, document=sandbox_doc)
|
||||
assert access.role == models.RoleChoices.OWNER
|
||||
|
||||
|
||||
def test_models_users_duplicate_onboarding_sandbox_document_with_invalid_template_id():
|
||||
"""
|
||||
When USER_ONBOARDING_SANDBOX_DOCUMENT has an invalid document ID,
|
||||
it should be skipped and logged, but not raise an exception.
|
||||
"""
|
||||
invalid_id = uuid.uuid4()
|
||||
|
||||
with override_settings(USER_ONBOARDING_SANDBOX_DOCUMENT=str(invalid_id)):
|
||||
with patch("core.models.logger") as mock_logger:
|
||||
user = factories.UserFactory()
|
||||
|
||||
mock_logger.warning.assert_called_once()
|
||||
call_args = mock_logger.warning.call_args
|
||||
assert "Onboarding sandbox document with id" in call_args[0][0]
|
||||
|
||||
sandbox_docs = models.Document.objects.filter(creator=user)
|
||||
assert sandbox_docs.count() == 0
|
||||
|
||||
|
||||
def test_models_users_duplicate_onboarding_sandbox_document_creates_unique_sandbox_per_user():
|
||||
"""
|
||||
Each new user should get their own independent sandbox document.
|
||||
"""
|
||||
template_document = factories.DocumentFactory(title="Getting started with Docs")
|
||||
|
||||
with override_settings(USER_ONBOARDING_SANDBOX_DOCUMENT=str(template_document.id)):
|
||||
user1 = factories.UserFactory()
|
||||
user2 = factories.UserFactory()
|
||||
|
||||
sandbox_docs_user1 = models.Document.objects.filter(
|
||||
creator=user1, title="Getting started with Docs"
|
||||
)
|
||||
sandbox_docs_user2 = models.Document.objects.filter(
|
||||
creator=user2, title="Getting started with Docs"
|
||||
)
|
||||
|
||||
assert sandbox_docs_user1.count() == 1
|
||||
assert sandbox_docs_user2.count() == 1
|
||||
|
||||
assert sandbox_docs_user1.first().id != sandbox_docs_user2.first().id
|
||||
|
||||
|
||||
def test_models_users_duplicate_onboarding_sandbox_document_integration_with_other_methods():
|
||||
"""
|
||||
Verify that sandbox creation works alongside other onboarding methods.
|
||||
"""
|
||||
template_document = factories.DocumentFactory(title="Getting started with Docs")
|
||||
onboarding_doc = factories.DocumentFactory(title="Onboarding Document")
|
||||
|
||||
with override_settings(
|
||||
USER_ONBOARDING_SANDBOX_DOCUMENT=str(template_document.id),
|
||||
USER_ONBOARDING_DOCUMENTS=[str(onboarding_doc.id)],
|
||||
):
|
||||
user = factories.UserFactory()
|
||||
|
||||
sandbox_doc = models.Document.objects.filter(
|
||||
creator=user, title="Getting started with Docs"
|
||||
).first()
|
||||
|
||||
user_accesses = models.DocumentAccess.objects.filter(user=user)
|
||||
assert user_accesses.count() == 2
|
||||
|
||||
sandbox_access = user_accesses.get(document=sandbox_doc)
|
||||
onboarding_access = user_accesses.get(document=onboarding_doc)
|
||||
|
||||
assert sandbox_access.role == models.RoleChoices.OWNER
|
||||
assert onboarding_access.role == models.RoleChoices.READER
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
"""
|
||||
Test ai API endpoints in the impress core app.
|
||||
Test AI services in the impress core app.
|
||||
"""
|
||||
# pylint: disable=protected-access
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
@@ -9,12 +11,33 @@ from django.test.utils import override_settings
|
||||
|
||||
import pytest
|
||||
from openai import OpenAIError
|
||||
from pydantic_ai.ui.vercel_ai.request_types import TextUIPart, UIMessage
|
||||
|
||||
from core.services.ai_services import AIService
|
||||
from core.services.ai_services import (
|
||||
BLOCKNOTE_TOOL_STRICT_PROMPT,
|
||||
AIService,
|
||||
convert_async_generator_to_sync,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def ai_settings(settings):
|
||||
"""Fixture to set AI settings."""
|
||||
settings.AI_MODEL = "llama"
|
||||
settings.AI_BASE_URL = "http://example.com"
|
||||
settings.AI_API_KEY = "test-key"
|
||||
settings.AI_FEATURE_ENABLED = True
|
||||
settings.AI_FEATURE_BLOCKNOTE_ENABLED = True
|
||||
settings.AI_FEATURE_LEGACY_ENABLED = True
|
||||
settings.LANGFUSE_PUBLIC_KEY = None
|
||||
settings.AI_VERCEL_SDK_VERSION = 6
|
||||
|
||||
|
||||
# -- AIService.__init__ --
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"setting_name, setting_value",
|
||||
[
|
||||
@@ -23,22 +46,25 @@ pytestmark = pytest.mark.django_db
|
||||
("AI_MODEL", None),
|
||||
],
|
||||
)
|
||||
def test_api_ai_setting_missing(setting_name, setting_value):
|
||||
def test_services_ai_setting_missing(setting_name, setting_value, settings):
|
||||
"""Setting should be set"""
|
||||
setattr(settings, setting_name, setting_value)
|
||||
|
||||
with override_settings(**{setting_name: setting_value}):
|
||||
with pytest.raises(
|
||||
ImproperlyConfigured,
|
||||
match="AI configuration not set",
|
||||
):
|
||||
AIService()
|
||||
with pytest.raises(
|
||||
ImproperlyConfigured,
|
||||
match="AI configuration not set",
|
||||
):
|
||||
AIService()
|
||||
|
||||
|
||||
# -- AIService.transform --
|
||||
|
||||
|
||||
@override_settings(
|
||||
AI_BASE_URL="http://example.com", AI_API_KEY="test-key", AI_MODEL="test-model"
|
||||
)
|
||||
@patch("openai.resources.chat.completions.Completions.create")
|
||||
def test_api_ai__client_error(mock_create):
|
||||
def test_services_ai_client_error(mock_create):
|
||||
"""Fail when the client raises an error"""
|
||||
|
||||
mock_create.side_effect = OpenAIError("Mocked client error")
|
||||
@@ -54,7 +80,7 @@ def test_api_ai__client_error(mock_create):
|
||||
AI_BASE_URL="http://example.com", AI_API_KEY="test-key", AI_MODEL="test-model"
|
||||
)
|
||||
@patch("openai.resources.chat.completions.Completions.create")
|
||||
def test_api_ai__client_invalid_response(mock_create):
|
||||
def test_services_ai_client_invalid_response(mock_create):
|
||||
"""Fail when the client response is invalid"""
|
||||
|
||||
mock_create.return_value = MagicMock(
|
||||
@@ -72,7 +98,7 @@ def test_api_ai__client_invalid_response(mock_create):
|
||||
AI_BASE_URL="http://example.com", AI_API_KEY="test-key", AI_MODEL="test-model"
|
||||
)
|
||||
@patch("openai.resources.chat.completions.Completions.create")
|
||||
def test_api_ai__success(mock_create):
|
||||
def test_services_ai_success(mock_create):
|
||||
"""The AI request should work as expect when called with valid arguments."""
|
||||
|
||||
mock_create.return_value = MagicMock(
|
||||
@@ -82,3 +108,483 @@ def test_api_ai__success(mock_create):
|
||||
response = AIService().transform("hello", "prompt")
|
||||
|
||||
assert response == {"answer": "Salut"}
|
||||
|
||||
|
||||
# -- AIService.translate --
|
||||
|
||||
|
||||
@patch("openai.resources.chat.completions.Completions.create")
|
||||
def test_services_ai_translate_success(mock_create):
|
||||
"""Translate should call the AI API with the correct language prompt."""
|
||||
|
||||
mock_create.return_value = MagicMock(
|
||||
choices=[MagicMock(message=MagicMock(content="Bonjour"))]
|
||||
)
|
||||
|
||||
response = AIService().translate("<p>Hello</p>", "fr")
|
||||
|
||||
assert response == {"answer": "Bonjour"}
|
||||
call_args = mock_create.call_args
|
||||
system_content = call_args[1]["messages"][0]["content"]
|
||||
assert "French" in system_content or "fr" in system_content
|
||||
|
||||
|
||||
@patch("openai.resources.chat.completions.Completions.create")
|
||||
def test_services_ai_translate_unknown_language(mock_create):
|
||||
"""Translate with an unknown language code should use the code as-is."""
|
||||
|
||||
mock_create.return_value = MagicMock(
|
||||
choices=[MagicMock(message=MagicMock(content="Translated"))]
|
||||
)
|
||||
|
||||
response = AIService().translate("<p>Hello</p>", "xx-unknown")
|
||||
|
||||
assert response == {"answer": "Translated"}
|
||||
call_args = mock_create.call_args
|
||||
system_content = call_args[1]["messages"][0]["content"]
|
||||
assert "xx-unknown" in system_content
|
||||
|
||||
|
||||
# -- convert_async_generator_to_sync --
|
||||
|
||||
|
||||
def test_convert_async_generator_to_sync_basic():
|
||||
"""Should convert an async generator yielding items to a sync iterator."""
|
||||
|
||||
async def async_gen():
|
||||
for item in ["hello", "world", "!"]:
|
||||
yield item
|
||||
|
||||
result = list(convert_async_generator_to_sync(async_gen()))
|
||||
assert result == ["hello", "world", "!"]
|
||||
|
||||
|
||||
def test_convert_async_generator_to_sync_empty():
|
||||
"""Should handle an empty async generator."""
|
||||
|
||||
async def async_gen():
|
||||
return
|
||||
yield
|
||||
|
||||
result = list(convert_async_generator_to_sync(async_gen()))
|
||||
assert not result
|
||||
|
||||
|
||||
def test_convert_async_generator_to_sync_exception():
|
||||
"""Should propagate exceptions from the async generator."""
|
||||
|
||||
async def async_gen():
|
||||
yield "first"
|
||||
raise ValueError("async error")
|
||||
|
||||
sync_iter = convert_async_generator_to_sync(async_gen())
|
||||
assert next(sync_iter) == "first"
|
||||
|
||||
with pytest.raises(ValueError, match="async error"):
|
||||
next(sync_iter)
|
||||
|
||||
|
||||
# -- AIService.inject_document_state_messages --
|
||||
|
||||
|
||||
def test_inject_document_state_messages_no_metadata():
|
||||
"""Messages without documentState metadata should pass through unchanged."""
|
||||
messages = [
|
||||
UIMessage(role="user", id="msg-1", parts=[TextUIPart(text="Hello")]),
|
||||
]
|
||||
|
||||
result = AIService.inject_document_state_messages(messages)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].id == "msg-1"
|
||||
|
||||
|
||||
def test_inject_document_state_messages_with_selection():
|
||||
"""A user message with documentState and selection should get an
|
||||
assistant context message prepended."""
|
||||
messages = [
|
||||
UIMessage(
|
||||
role="user",
|
||||
id="msg-1",
|
||||
parts=[TextUIPart(text="Fix this")],
|
||||
metadata={
|
||||
"documentState": {
|
||||
"selection": {"start": 0, "end": 5},
|
||||
"selectedBlocks": [{"type": "paragraph", "content": "Hello"}],
|
||||
"blocks": [
|
||||
{"type": "paragraph", "content": "Hello"},
|
||||
{"type": "paragraph", "content": "World"},
|
||||
],
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
result = AIService.inject_document_state_messages(messages)
|
||||
|
||||
assert len(result) == 2
|
||||
# First message should be the injected assistant context
|
||||
assert result[0].role == "assistant"
|
||||
assert result[0].id == "assistant-document-state-msg-1"
|
||||
assert len(result[0].parts) == 4
|
||||
assert "selection" in result[0].parts[0].text.lower()
|
||||
# Second message should be the original user message
|
||||
assert result[1].id == "msg-1"
|
||||
|
||||
|
||||
def test_inject_document_state_messages_without_selection():
|
||||
"""A user message with documentState but no selection should describe
|
||||
the full document context."""
|
||||
messages = [
|
||||
UIMessage(
|
||||
role="user",
|
||||
id="msg-1",
|
||||
parts=[TextUIPart(text="Summarize")],
|
||||
metadata={
|
||||
"documentState": {
|
||||
"selection": None,
|
||||
"blocks": [
|
||||
{"type": "paragraph", "content": "Hello"},
|
||||
],
|
||||
"isEmptyDocument": False,
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
result = AIService.inject_document_state_messages(messages)
|
||||
|
||||
assert len(result) == 2
|
||||
assistant_msg = result[0]
|
||||
assert assistant_msg.role == "assistant"
|
||||
assert len(assistant_msg.parts) == 2
|
||||
assert "no active selection" in assistant_msg.parts[0].text.lower()
|
||||
assert "prefer updating" in assistant_msg.parts[0].text.lower()
|
||||
|
||||
|
||||
def test_inject_document_state_messages_empty_document():
|
||||
"""When the document is empty, the injected message should instruct
|
||||
updating the empty block first."""
|
||||
messages = [
|
||||
UIMessage(
|
||||
role="user",
|
||||
id="msg-1",
|
||||
parts=[TextUIPart(text="Write something")],
|
||||
metadata={
|
||||
"documentState": {
|
||||
"selection": None,
|
||||
"blocks": [{"type": "paragraph", "content": ""}],
|
||||
"isEmptyDocument": True,
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
result = AIService.inject_document_state_messages(messages)
|
||||
|
||||
assert len(result) == 2
|
||||
assistant_msg = result[0]
|
||||
assert "update the empty block" in assistant_msg.parts[0].text.lower()
|
||||
|
||||
|
||||
def test_inject_document_state_messages_mixed():
|
||||
"""Only user messages with documentState get assistant context;
|
||||
other messages pass through unchanged."""
|
||||
messages = [
|
||||
UIMessage(
|
||||
role="assistant",
|
||||
id="msg-0",
|
||||
parts=[TextUIPart(text="Previous response")],
|
||||
),
|
||||
UIMessage(
|
||||
role="user",
|
||||
id="msg-1",
|
||||
parts=[TextUIPart(text="Hello")],
|
||||
),
|
||||
UIMessage(
|
||||
role="user",
|
||||
id="msg-2",
|
||||
parts=[TextUIPart(text="Fix this")],
|
||||
metadata={
|
||||
"documentState": {
|
||||
"selection": {"start": 0, "end": 5},
|
||||
"selectedBlocks": [{"type": "paragraph", "content": "Hello"}],
|
||||
"blocks": [{"type": "paragraph", "content": "Hello"}],
|
||||
}
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
result = AIService.inject_document_state_messages(messages)
|
||||
|
||||
# 3 original + 1 injected assistant message before msg-2
|
||||
assert len(result) == 4
|
||||
assert result[0].id == "msg-0"
|
||||
assert result[1].id == "msg-1"
|
||||
assert result[2].role == "assistant"
|
||||
assert result[2].id == "assistant-document-state-msg-2"
|
||||
assert result[3].id == "msg-2"
|
||||
|
||||
|
||||
# -- AIService.tool_definitions_to_toolset --
|
||||
|
||||
|
||||
def test_tool_definitions_to_toolset():
|
||||
"""Should convert frontend tool definitions to an ExternalToolset."""
|
||||
tool_definitions = {
|
||||
"applyOperations": {
|
||||
"description": "Apply operations to the document",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"operations": {"type": "array"},
|
||||
},
|
||||
},
|
||||
"outputSchema": {"type": "object"},
|
||||
},
|
||||
"insertBlocks": {
|
||||
"description": "Insert blocks",
|
||||
"inputSchema": {"type": "object"},
|
||||
},
|
||||
}
|
||||
|
||||
toolset = AIService.tool_definitions_to_toolset(tool_definitions)
|
||||
|
||||
# The ExternalToolset wraps ToolDefinition objects
|
||||
assert toolset is not None
|
||||
# Access internal tool definitions
|
||||
tool_defs = toolset.tool_defs
|
||||
assert len(tool_defs) == 2
|
||||
|
||||
names = {td.name for td in tool_defs}
|
||||
assert names == {"applyOperations", "insertBlocks"}
|
||||
|
||||
for td in tool_defs:
|
||||
assert td.kind == "external"
|
||||
if td.name == "applyOperations":
|
||||
assert td.description == "Apply operations to the document"
|
||||
assert td.metadata == {"output_schema": {"type": "object"}}
|
||||
|
||||
|
||||
def test_tool_definitions_to_toolset_missing_fields():
|
||||
"""Should handle tool definitions with missing optional fields."""
|
||||
tool_definitions = {
|
||||
"myTool": {},
|
||||
}
|
||||
|
||||
toolset = AIService.tool_definitions_to_toolset(tool_definitions)
|
||||
|
||||
tool_defs = toolset.tool_defs
|
||||
assert len(tool_defs) == 1
|
||||
assert tool_defs[0].name == "myTool"
|
||||
assert tool_defs[0].description == ""
|
||||
assert tool_defs[0].parameters_json_schema == {}
|
||||
assert tool_defs[0].metadata == {"output_schema": None}
|
||||
|
||||
|
||||
# -- AIService.stream --
|
||||
|
||||
|
||||
@patch.object(AIService, "_build_async_stream")
|
||||
def test_services_ai_stream_sync_mode(mock_build, monkeypatch):
|
||||
"""In sync mode, stream() should return a sync iterator."""
|
||||
|
||||
async def mock_async_gen():
|
||||
yield "chunk1"
|
||||
yield "chunk2"
|
||||
|
||||
mock_build.return_value = mock_async_gen()
|
||||
monkeypatch.setenv("PYTHON_SERVER_MODE", "sync")
|
||||
|
||||
service = AIService()
|
||||
request = MagicMock()
|
||||
result = service.stream(request)
|
||||
|
||||
# Should be a regular (sync) iterator, not async
|
||||
assert not isinstance(result, AsyncIterator)
|
||||
assert list(result) == ["chunk1", "chunk2"]
|
||||
mock_build.assert_called_once_with(request)
|
||||
|
||||
|
||||
@patch.object(AIService, "_build_async_stream")
|
||||
def test_services_ai_stream_async_mode(mock_build, monkeypatch):
|
||||
"""In async mode, stream() should return the async iterator directly."""
|
||||
|
||||
async def mock_async_gen():
|
||||
yield "chunk1"
|
||||
yield "chunk2"
|
||||
|
||||
mock_async_iter = mock_async_gen()
|
||||
mock_build.return_value = mock_async_iter
|
||||
monkeypatch.setenv("PYTHON_SERVER_MODE", "async")
|
||||
|
||||
service = AIService()
|
||||
request = MagicMock()
|
||||
result = service.stream(request)
|
||||
|
||||
assert result is mock_async_iter
|
||||
mock_build.assert_called_once_with(request)
|
||||
|
||||
|
||||
@patch.object(AIService, "_build_async_stream")
|
||||
def test_services_ai_stream_defaults_to_sync(mock_build, monkeypatch):
|
||||
"""When PYTHON_SERVER_MODE is not set, stream() should default to sync."""
|
||||
|
||||
async def mock_async_gen():
|
||||
yield "data"
|
||||
|
||||
mock_build.return_value = mock_async_gen()
|
||||
monkeypatch.delenv("PYTHON_SERVER_MODE", raising=False)
|
||||
|
||||
service = AIService()
|
||||
request = MagicMock()
|
||||
result = service.stream(request)
|
||||
|
||||
# Default should be sync mode
|
||||
assert not isinstance(result, AsyncIterator)
|
||||
assert list(result) == ["data"]
|
||||
|
||||
|
||||
# -- AIService._build_async_stream --
|
||||
|
||||
|
||||
@patch("core.services.ai_services.VercelAIAdapter")
|
||||
def test_services_ai_build_async_stream(mock_adapter_cls):
|
||||
"""_build_async_stream should build the pydantic-ai streaming pipeline."""
|
||||
|
||||
async def mock_encode():
|
||||
yield "event-data"
|
||||
|
||||
mock_run_input = MagicMock()
|
||||
mock_run_input.model_extra = None
|
||||
mock_run_input.messages = []
|
||||
mock_adapter_cls.build_run_input.return_value = mock_run_input
|
||||
|
||||
mock_adapter_instance = MagicMock()
|
||||
mock_adapter_instance.run_stream.return_value = MagicMock()
|
||||
mock_adapter_instance.encode_stream.return_value = mock_encode()
|
||||
mock_adapter_cls.return_value = mock_adapter_instance
|
||||
|
||||
service = AIService()
|
||||
request = MagicMock()
|
||||
request.META = {"HTTP_ACCEPT": "text/event-stream"}
|
||||
request.raw_body = b'{"messages": []}'
|
||||
|
||||
result = service._build_async_stream(request)
|
||||
assert isinstance(result, AsyncIterator)
|
||||
mock_adapter_cls.build_run_input.assert_called_once_with(b'{"messages": []}')
|
||||
mock_adapter_instance.run_stream.assert_called_once()
|
||||
mock_adapter_instance.encode_stream.assert_called_once()
|
||||
|
||||
|
||||
@patch("core.services.ai_services.VercelAIAdapter")
|
||||
def test_services_ai_build_async_stream_with_tool_definitions(mock_adapter_cls):
|
||||
"""_build_async_stream should build an ExternalToolset when
|
||||
toolDefinitions are present in the request."""
|
||||
|
||||
async def mock_encode():
|
||||
yield "event-data"
|
||||
|
||||
mock_run_input = MagicMock()
|
||||
mock_run_input.model_extra = {
|
||||
"toolDefinitions": {
|
||||
"myTool": {
|
||||
"description": "A tool",
|
||||
"inputSchema": {"type": "object"},
|
||||
}
|
||||
}
|
||||
}
|
||||
mock_run_input.messages = []
|
||||
mock_adapter_cls.build_run_input.return_value = mock_run_input
|
||||
|
||||
mock_adapter_instance = MagicMock()
|
||||
mock_adapter_instance.run_stream.return_value = MagicMock()
|
||||
mock_adapter_instance.encode_stream.return_value = mock_encode()
|
||||
mock_adapter_cls.return_value = mock_adapter_instance
|
||||
|
||||
service = AIService()
|
||||
request = MagicMock()
|
||||
request.META = {}
|
||||
request.raw_body = b"{}"
|
||||
|
||||
service._build_async_stream(request)
|
||||
# run_stream should have been called with a toolset
|
||||
call_kwargs = mock_adapter_instance.run_stream.call_args[1]
|
||||
assert call_kwargs["toolsets"] is not None
|
||||
assert len(call_kwargs["toolsets"]) == 1
|
||||
|
||||
|
||||
@patch("core.services.ai_services.VercelAIAdapter")
|
||||
def test_services_ai_build_async_stream_with_tool_definitions_required_system_prompt(
|
||||
mock_adapter_cls,
|
||||
):
|
||||
"""The presence of the applyDocumentOperations tool must force the addition
|
||||
of a system prompt"""
|
||||
|
||||
async def mock_encode():
|
||||
yield "event-data"
|
||||
|
||||
mock_run_input = MagicMock()
|
||||
mock_run_input.model_extra = {
|
||||
"toolDefinitions": {
|
||||
"applyDocumentOperations": {
|
||||
"description": "A tool",
|
||||
"inputSchema": {"type": "object"},
|
||||
}
|
||||
}
|
||||
}
|
||||
mock_run_input.messages = []
|
||||
mock_adapter_cls.build_run_input.return_value = mock_run_input
|
||||
|
||||
mock_adapter_instance = MagicMock()
|
||||
mock_adapter_instance.run_stream.return_value = MagicMock()
|
||||
mock_adapter_instance.encode_stream.return_value = mock_encode()
|
||||
mock_adapter_cls.return_value = mock_adapter_instance
|
||||
|
||||
service = AIService()
|
||||
request = MagicMock()
|
||||
request.META = {}
|
||||
request.raw_body = b"{}"
|
||||
|
||||
service._build_async_stream(request)
|
||||
# run_stream should have been called with a toolset
|
||||
call_kwargs = mock_adapter_instance.run_stream.call_args[1]
|
||||
assert call_kwargs["toolsets"] is not None
|
||||
assert len(call_kwargs["toolsets"]) == 1
|
||||
assert len(mock_run_input.messages) == 1
|
||||
assert mock_run_input.messages[0].id == "system-force-tool-usage"
|
||||
assert mock_run_input.messages[0].role == "system"
|
||||
assert mock_run_input.messages[0].parts[0].text == BLOCKNOTE_TOOL_STRICT_PROMPT
|
||||
|
||||
|
||||
@patch("core.services.ai_services.Agent")
|
||||
@patch("core.services.ai_services.VercelAIAdapter")
|
||||
def test_services_ai_build_async_stream_langfuse_enabled(
|
||||
mock_adapter_cls, mock_agent_cls, settings
|
||||
):
|
||||
"""When LANGFUSE_PUBLIC_KEY is set, instrument should be enabled."""
|
||||
settings.LANGFUSE_PUBLIC_KEY = "pk-test-123"
|
||||
|
||||
async def mock_encode():
|
||||
yield "data"
|
||||
|
||||
mock_run_input = MagicMock()
|
||||
mock_run_input.model_extra = None
|
||||
mock_run_input.messages = []
|
||||
mock_adapter_cls.build_run_input.return_value = mock_run_input
|
||||
|
||||
mock_adapter_instance = MagicMock()
|
||||
mock_adapter_instance.run_stream.return_value = MagicMock()
|
||||
mock_adapter_instance.encode_stream.return_value = mock_encode()
|
||||
mock_adapter_cls.return_value = mock_adapter_instance
|
||||
|
||||
service = AIService()
|
||||
request = MagicMock()
|
||||
request.META = {}
|
||||
request.raw_body = b"{}"
|
||||
|
||||
service._build_async_stream(request)
|
||||
mock_agent_cls.instrument_all.assert_called_once()
|
||||
# Agent should be created with instrument=True
|
||||
mock_agent_cls.assert_called_once()
|
||||
assert mock_agent_cls.call_args[1]["instrument"] is True
|
||||
|
||||
18
src/backend/impress/asgi.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
ASGI config for impress project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/dev/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from configurations.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "impress.settings")
|
||||
os.environ.setdefault("DJANGO_CONFIGURATION", "Development")
|
||||
os.environ.setdefault("PYTHON_SERVER_MODE", "async")
|
||||
|
||||
application = get_asgi_application()
|
||||
@@ -3,8 +3,8 @@
|
||||
"default": {
|
||||
"logo": {
|
||||
"src": "/assets/icon-docs.svg",
|
||||
"width": "54px",
|
||||
"alt": "Docs Logo",
|
||||
"style": { "width": "54px", "height": "auto" },
|
||||
"withTitle": true
|
||||
},
|
||||
"externalLinks": [
|
||||
@@ -125,5 +125,38 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"with-proconnect": false,
|
||||
"icon-banner": {
|
||||
"src": "/assets/icon-docs.svg",
|
||||
"style": {
|
||||
"width": "64px",
|
||||
"height": "auto"
|
||||
},
|
||||
"alt": ""
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"logo": {},
|
||||
"icon": {
|
||||
"src": "/assets/icon-docs.svg",
|
||||
"style": {
|
||||
"width": "32px",
|
||||
"height": "auto"
|
||||
},
|
||||
"alt": "",
|
||||
"withTitle": true
|
||||
}
|
||||
},
|
||||
"favicon": {
|
||||
"light": {
|
||||
"href": "/assets/favicon-light.png",
|
||||
"type": "image/png"
|
||||
},
|
||||
"dark": {
|
||||
"href": "/assets/favicon-dark.png",
|
||||
"type": "image/png"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +326,7 @@ class Base(Configuration):
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"core.middleware.ForceSessionMiddleware",
|
||||
"core.middleware.SaveRawBodyMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"dockerflow.django.middleware.DockerflowMiddleware",
|
||||
"csp.middleware.CSPMiddleware",
|
||||
@@ -687,24 +688,48 @@ class Base(Configuration):
|
||||
default=True, environ_name="ALLOW_LOGOUT_GET_METHOD", environ_prefix=None
|
||||
)
|
||||
|
||||
# AI service
|
||||
AI_FEATURE_ENABLED = values.BooleanValue(
|
||||
default=False, environ_name="AI_FEATURE_ENABLED", environ_prefix=None
|
||||
)
|
||||
AI_API_KEY = SecretFileValue(None, environ_name="AI_API_KEY", environ_prefix=None)
|
||||
AI_BASE_URL = values.Value(None, environ_name="AI_BASE_URL", environ_prefix=None)
|
||||
AI_MODEL = values.Value(None, environ_name="AI_MODEL", environ_prefix=None)
|
||||
# AI settings
|
||||
AI_ALLOW_REACH_FROM = values.Value(
|
||||
choices=("public", "authenticated", "restricted"),
|
||||
default="authenticated",
|
||||
environ_name="AI_ALLOW_REACH_FROM",
|
||||
environ_prefix=None,
|
||||
)
|
||||
AI_API_KEY = SecretFileValue(None, environ_name="AI_API_KEY", environ_prefix=None)
|
||||
AI_BASE_URL = values.Value(None, environ_name="AI_BASE_URL", environ_prefix=None)
|
||||
AI_BOT = values.DictValue(
|
||||
default={
|
||||
"name": _("Docs AI"),
|
||||
"color": "#8bc6ff",
|
||||
},
|
||||
environ_name="AI_BOT",
|
||||
environ_prefix=None,
|
||||
)
|
||||
AI_DOCUMENT_RATE_THROTTLE_RATES = {
|
||||
"minute": 5,
|
||||
"hour": 100,
|
||||
"day": 500,
|
||||
}
|
||||
# Master settings to enable AI features, if you set it to False,
|
||||
# all AI features will be disabled even if the other settings are enabled.
|
||||
AI_FEATURE_ENABLED = values.BooleanValue(
|
||||
default=False, environ_name="AI_FEATURE_ENABLED", environ_prefix=None
|
||||
)
|
||||
# Far better UI but more flaky for the moment
|
||||
# ⚠️ AGPL license, be sure to comply with the Blocknote license
|
||||
# if you enable it (https://www.blocknotejs.org/)
|
||||
AI_FEATURE_BLOCKNOTE_ENABLED = values.BooleanValue(
|
||||
default=False, environ_name="AI_FEATURE_BLOCKNOTE_ENABLED", environ_prefix=None
|
||||
)
|
||||
# UI with less features but more stable
|
||||
# MIT friendly license, you can enable it without worrying about the license
|
||||
AI_FEATURE_LEGACY_ENABLED = values.BooleanValue(
|
||||
default=True, environ_name="AI_FEATURE_LEGACY_ENABLED", environ_prefix=None
|
||||
)
|
||||
AI_MODEL = values.Value(None, environ_name="AI_MODEL", environ_prefix=None)
|
||||
AI_VERCEL_SDK_VERSION = values.IntegerValue(
|
||||
6, environ_name="AI_VERCEL_SDK_VERSION", environ_prefix=None
|
||||
)
|
||||
AI_USER_RATE_THROTTLE_RATES = {
|
||||
"minute": 3,
|
||||
"hour": 50,
|
||||
@@ -736,7 +761,7 @@ class Base(Configuration):
|
||||
|
||||
# Imported file settings
|
||||
CONVERSION_FILE_MAX_SIZE = values.IntegerValue(
|
||||
20 * MB, # 10MB
|
||||
20 * MB,
|
||||
environ_name="CONVERSION_FILE_MAX_SIZE",
|
||||
environ_prefix=None,
|
||||
)
|
||||
@@ -884,7 +909,12 @@ class Base(Configuration):
|
||||
USER_RECONCILIATION_FORM_URL = values.Value(
|
||||
None, environ_name="USER_RECONCILIATION_FORM_URL", environ_prefix=None
|
||||
)
|
||||
|
||||
USER_ONBOARDING_DOCUMENTS = values.ListValue(
|
||||
[], environ_name="USER_ONBOARDING_DOCUMENTS", environ_prefix=None
|
||||
)
|
||||
USER_ONBOARDING_SANDBOX_DOCUMENT = values.Value(
|
||||
None, environ_name="USER_ONBOARDING_SANDBOX_DOCUMENT", environ_prefix=None
|
||||
)
|
||||
# Marketing and communication settings
|
||||
SIGNUP_NEW_USER_TO_MARKETING_EMAIL = values.BooleanValue(
|
||||
False,
|
||||
|
||||
@@ -13,5 +13,6 @@ from configurations.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "impress.settings")
|
||||
os.environ.setdefault("DJANGO_CONFIGURATION", "Development")
|
||||
os.environ.setdefault("PYTHON_SERVER_MODE", "sync")
|
||||
|
||||
application = get_wsgi_application()
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Breton\n"
|
||||
"Language: br_FR\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "Titouroù personel"
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "Aotreoù"
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Deiziadoù a-bouez"
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr "Gwezennadur"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "Titl"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
msgstr "Me eo an aozer"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr "Kuzhet"
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr "Sinedoù"
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
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:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
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:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr "Ar vaezienn-mañ a zo rekis."
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "eilenn {title}"
|
||||
@@ -135,262 +143,374 @@ msgstr "Kleiz"
|
||||
msgid "Right"
|
||||
msgstr "Dehoù"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "alc'hwez kentañ evit an enrollañ evel UIID"
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr "krouet d'ar/al"
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "deiziad hag eurvezh krouidigezh an enrolladenn"
|
||||
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr "hizivaet d'ar/al"
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "deiziad hag eurvezh m'eo bet hizivaet an enrolladenn"
|
||||
|
||||
#: build/lib/core/models.py:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr "N'hon eus kavet implijer ebet gant an isstrollad-mañ met ar postel a zo liammet ouzh un implijer enrollet."
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr "isstrollad"
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr "anv klok"
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr "anv berr"
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr "postel identelezh"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr "postel ar merour"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr "yezh"
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "Ar yezh a vo implijet evit etrefas an implijer."
|
||||
|
||||
#: build/lib/core/models.py:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "Ar gwerzhid-eur a vo implijet evit etrefas an implijer."
|
||||
|
||||
#: build/lib/core/models.py:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr "trevnad"
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Pe vefe an implijer un aparailh pe un implijer gwirion."
|
||||
|
||||
#: build/lib/core/models.py:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr "statud ar skipailh"
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Ma c'hall an implijer kevreañ ouzh al lec'hienn verañ-mañ."
|
||||
|
||||
#: build/lib/core/models.py:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr "oberiant"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Ma rank bezañ tretet an implijer-mañ evel oberiant. Diziuzit an dra-mañ e-plas dilemel kontoù."
|
||||
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr "implijer"
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr "implijerien"
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr "titl"
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr "bomm"
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr "Restr"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr "Restroù"
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr "Restr hep titl"
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr "Digeriñ"
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} en deus rannet ur restr ganeoc'h!"
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} en deus pedet ac'hanoc'h gant ar rol \"{role}\" war ar restr da-heul:"
|
||||
|
||||
#: build/lib/core/models.py:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} en deus rannet ur restr ganeoc'h: {title}"
|
||||
|
||||
#: build/lib/core/models.py:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Roud liamm ar restr/an implijer"
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Roudoù liamm ar restr/an implijer"
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Ur roud liamm a zo dija evit an restr/an implijer."
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr "Restr muiañ-karet"
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr "Restroù muiañ-karet"
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Ar restr-mañ a zo ur restr muiañ karet gant an implijer-mañ."
|
||||
|
||||
#: build/lib/core/models.py:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr "Liamm restr/implijer"
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr "Liammoù restr/implijer"
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr "An implijer-mañ a zo dija er restr-mañ."
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Ar skipailh-mañ a zo dija en restr-mañ."
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "An implijer pe ar skipailh a rank bezañ termenet, ket an daou avat."
|
||||
|
||||
#: build/lib/core/models.py:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr "Goulenn tizhout ar restr"
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Goulennoù tizhout ar restr"
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "An implijer en deus goulennet tizhout ar restr-mañ."
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "{name} en defe c'hoant da dizhout ar restr-mañ!"
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} en defe c'hoant da dizhout ar restr da-heul:"
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} en defe c'hoant da dizhout ar restr: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr "postel"
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr "Pedadenn d'ur restr"
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr "Pedadennoù d'ur restr"
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Ar postel-mañ a zo liammet ouzh un implijer enskrivet."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Language: de_DE\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "Persönliche Daten"
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "Berechtigungen"
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Wichtige Daten"
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr "Baumstruktur"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
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:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr "Favorit"
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Ein neues Dokument wurde in Ihrem Namen erstellt!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Sie sind Besitzer eines neuen Dokuments:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "Kopie von {title}"
|
||||
@@ -135,262 +143,374 @@ msgstr "Links"
|
||||
msgid "Right"
|
||||
msgstr "Rechts"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "primärer Schlüssel für den Datensatz als UUID"
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr "Erstellt"
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
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:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr "Aktualisiert"
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
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:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr "unter"
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr "Name"
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr "Kurzbezeichnung"
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr "Identitäts-E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr "Admin E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr "Sprache"
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
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:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
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:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr "Gerät"
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
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:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr "Status des Teammitgliedes"
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
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:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr "aktiviert"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
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:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr "Auszug"
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr "Dokument"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr "Dokumente"
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr "Unbenanntes Dokument"
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr "Öffnen"
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} hat ein Dokument mit Ihnen geteilt!"
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, 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:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, 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:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Dokument/Benutzer Linkverfolgung"
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Dokument/Benutzer Linkverfolgung"
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Für dieses Dokument/ diesen Benutzer ist bereits eine Linkverfolgung vorhanden."
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr "Dokumentenfavorit"
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr "Dokumentfavoriten"
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
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:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr "Dokument/Benutzerbeziehung"
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr "Dokument/Benutzerbeziehungen"
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Dieser Benutzer befindet sich bereits in diesem Dokument."
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Dieses Team befindet sich bereits in diesem Dokument."
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
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:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr "E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr "Einladung zum Dokument"
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr "Dokumenteinladungen"
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Diese E-Mail ist bereits einem registrierten Benutzer zugeordnet."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: English\n"
|
||||
"Language: en_US\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
@@ -135,262 +143,374 @@ msgstr ""
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Language: es_ES\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "Información Personal"
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "Permisos"
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Fechas importantes"
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr "Estructura en árbol"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "Título"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
msgstr "Yo soy el creador"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr "Favorito"
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "¡Un nuevo documento se ha creado por ti!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
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:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "copia de {title}"
|
||||
@@ -135,262 +143,374 @@ msgstr "Izquierda"
|
||||
msgid "Right"
|
||||
msgstr "Derecha"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "clave primaria para el registro como UUID"
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr "creado el"
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
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:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr "actualizado el"
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
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:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr "sub (UUID)"
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr "Obligatorio. 255 caracteres o menos. Solo caracteres ASCII."
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr "nombre completo"
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr "nombre abreviado"
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr "correo electrónico de identidad"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr "correo electrónico del administrador"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr "idioma"
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
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:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
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:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr "dispositivo"
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
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:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr "rol en el equipo"
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
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:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr "activo"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
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:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr "usuario"
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr "usuarios"
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr "título"
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr "resumen"
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr "Documento"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr "Documentos"
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr "Documento sin título"
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr "Abrir"
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "¡{name} ha compartido un documento contigo!"
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, 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:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} ha compartido un documento contigo: {title}"
|
||||
|
||||
#: build/lib/core/models.py:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Traza del enlace de documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Trazas del enlace de documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
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:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr "Documento favorito"
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr "Documentos favoritos"
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
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:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr "Relación documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr "Relaciones documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Este usuario ya forma parte del documento."
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Este equipo ya forma parte del documento."
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
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:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr "Solicitud de acceso"
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Solicitud de accesos"
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "Este usuario ya ha solicitado acceso a este documento."
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "¡{name} desea acceder a un documento!"
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} desea acceso al siguiente documento:"
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} está pidiendo acceso al documento: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr "dirección de correo electrónico"
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr "Invitación al documento"
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr "Invitaciones a documentos"
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Este correo electrónico está asociado a un usuario registrado."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr_FR\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "Infos Personnelles"
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "Permissions"
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Dates importantes"
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr "Tâche d'importation créée et mise en file d'attente."
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr "Traiter les rapprochements de l'utilisateur sélectionné"
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr "Arborescence"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
msgstr "Je suis l'auteur"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr "Masqué"
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr "Favoris"
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Un nouveau document a été créé pour vous !"
|
||||
|
||||
#: build/lib/core/api/serializers.py:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
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:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr "Ce champ est obligatoire."
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr "La portée du lien '%(link_reach)s' n'est pas autorisée en fonction de la configuration du document parent."
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "copie de {title}"
|
||||
@@ -135,262 +143,381 @@ msgstr "Gauche"
|
||||
msgid "Right"
|
||||
msgstr "Droite"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr "identifiant/id"
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "clé primaire pour l'enregistrement en tant que UUID"
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr "créé le"
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "date et heure de création de l'enregistrement"
|
||||
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr "mis à jour le"
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "date et heure de la dernière mise à jour de l'enregistrement"
|
||||
|
||||
#: build/lib/core/models.py:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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é."
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr "sous-groupe"
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr "Obligatoire. 255 caractères ou moins. Caractères ASCII uniquement."
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr "nom complet"
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr "nom court"
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr "adresse e-mail d'identité"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr "adresse e-mail de l'administrateur"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr "langue"
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "La langue dans laquelle l'utilisateur veut voir l'interface."
|
||||
|
||||
#: build/lib/core/models.py:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "Le fuseau horaire dans lequel l'utilisateur souhaite voir les heures."
|
||||
|
||||
#: build/lib/core/models.py:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr "appareil"
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Si l'utilisateur est un appareil ou un utilisateur réel."
|
||||
|
||||
#: build/lib/core/models.py:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr "statut d'équipe"
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Si l'utilisateur peut se connecter à ce site d'administration."
|
||||
|
||||
#: build/lib/core/models.py:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr "actif"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
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."
|
||||
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr "utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr "utilisateurs"
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr "Adresse email active"
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr "Adresse email à désactiver"
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr "Identifiant unique dans le fichier source"
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr "En attente"
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr "Prêt"
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr "Terminé"
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr "Erreur"
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr "rapprochement de l'utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr "rapprochements de l'utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr "Vous avez demandé un rapprochement de vos comptes utilisateur sur Docs.\n"
|
||||
" Pour confirmer que vous êtes bien à l'origine de cette demande\n"
|
||||
" et que cet e-mail vous appartient :"
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr "Confirmez en cliquant sur le lien pour commencer le rapprochement"
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr "Cliquez ici"
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr "Confirmer"
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr "Votre demande de rapprochement a été traitée.\n"
|
||||
" De nouveaux documents sont probablement associés à votre compte :"
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr "Vos comptes ont été fusionnés"
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr "Cliquez ici pour voir"
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr "Voir mes documents"
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr "Fichier CSV"
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr "En cours"
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr "importation CSV de rapprochement utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr "importations CSV de rapprochement utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr "Votre demande de rapprochement n'a pas abouti.\n"
|
||||
" Le rapprochement a échoué pour les adresses e-mail suivantes :\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Veuillez vérifier qu'il n'y a pas de fautes de frappe.\n"
|
||||
" Vous pouvez envoyer une nouvelle demande avec des adresses e-mail valides."
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr "Le rapprochement de vos comptes Docs n'est pas terminé"
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr "Faire une nouvelle demande"
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr "titre"
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr "extrait"
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr "Document"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr "Documents"
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr "Document sans titre"
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr "Ouvrir"
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} a partagé un document avec vous!"
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, 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 :"
|
||||
|
||||
#: build/lib/core/models.py:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} a partagé un document avec vous : {title}"
|
||||
|
||||
#: build/lib/core/models.py:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Trace du lien document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Traces du lien document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Une trace de lien existe déjà pour ce document/utilisateur."
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr "Document favori"
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr "Documents favoris"
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
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."
|
||||
|
||||
#: build/lib/core/models.py:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr "Relation document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr "Relations document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Cet utilisateur est déjà dans ce document."
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Cette équipe est déjà dans ce document."
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
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:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr "Demande d'accès au document"
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Demande d'accès au document"
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "Cet utilisateur a déjà demandé l'accès à ce document."
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "{name} souhaiterait accéder au document suivant !"
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} souhaiterait accéder au document suivant :"
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} demande l'accès au document : {title}"
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr "Conversation"
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr "Conversations"
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr "Anonyme"
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr "Commentaire"
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr "Commentaires"
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr "Cet émoji a déjà été réagi à ce commentaire."
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr "Réaction"
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr "Réactions"
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr "adresse e-mail"
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr "Invitation à un document"
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr "Invitations à un document"
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Cette adresse email est déjà associée à un utilisateur inscrit."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr "Docs IA"
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"Language: it_IT\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "Informazioni personali"
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "Permessi"
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Date importanti"
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr "Struttura ad albero"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "Titolo"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
msgstr "Il creatore sono io"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr "Preferiti"
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Un nuovo documento è stato creato a tuo nome!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Sei ora proprietario di un nuovo documento:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "copia di {title}"
|
||||
@@ -135,262 +143,374 @@ msgstr "Sinistra"
|
||||
msgid "Right"
|
||||
msgstr "Destra"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr "Id"
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "chiave primaria per il record come UUID"
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr "creato il"
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
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:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr "aggiornato il"
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
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:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr "nome completo"
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr "nome"
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr "indirizzo email di identità"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr "Indirizzo email dell'amministratore"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr "lingua"
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
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:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
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:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr "dispositivo"
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
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:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr "stato del personale"
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
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:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr "attivo"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
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:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr "utente"
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr "utenti"
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr "titolo"
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr "Documento"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr "Documenti"
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr "Documento senza titolo"
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr "Apri"
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} ha condiviso un documento con te!"
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, 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:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, 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:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr "Documento preferito"
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr "Documenti preferiti"
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Questo utente è già presente in questo documento."
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Questo team è già presente in questo documento."
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr "indirizzo e-mail"
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr "Invito al documento"
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr "Inviti al documento"
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Questa email è già associata a un utente registrato."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Language: nl_NL\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "Persoonlijke informatie"
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "Machtigingen"
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Belangrijke data"
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr "Import taak gemaakt en in de wachtrij geplaatst."
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr "Verwerk geselecteerde gebruikers samenvoeging"
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr "Boomstructuur"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
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:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr "Gemaskeerd"
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr "Favoriet"
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Een nieuw document is namens u gemaakt!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "U heeft eigenaarschap van een nieuw document gekregen:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr "Dit veld is verplicht."
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr "Link bereik '%(link_reach)s' is niet toegestaan op basis van bovenliggende documentconfiguratie."
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "kopie van {title}"
|
||||
@@ -135,262 +143,381 @@ msgstr "Links"
|
||||
msgid "Right"
|
||||
msgstr "Rechts"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "primaire sleutel voor dossier als UUID"
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr "gecreëerd op"
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "datum en tijd waarop dossier is gecreeërd"
|
||||
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr "Laatst gewijzigd op"
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
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:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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 dit id, maar de email is al geassocieerd met een geregistreerde gebruiker."
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr "Vereist. 255 tekens of minder. Alleen ASCII tekens."
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr "volledige naam"
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr "gebruikersnaam"
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr "identiteit emailadres"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr "admin emailadres"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr "taal"
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "De taal waarin de gebruiker de interface wil zien."
|
||||
|
||||
#: build/lib/core/models.py:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "De tijdzone waarin de gebruiker de tijden wil zien."
|
||||
|
||||
#: build/lib/core/models.py:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr "apparaat"
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
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:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr "beheerder status"
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Of de gebruiker kan inloggen in het beheer gedeelte."
|
||||
|
||||
#: build/lib/core/models.py:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr "actief"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
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:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr "gebruiker"
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr "gebruikers"
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr "Actieve e-mail adres"
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr "E-mailadres om te deactiveren"
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr "Unieke ID in het bronbestand"
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr "In behandeling"
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr "Klaar"
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr "Klaar"
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr "Fout"
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr "gebruiker samenvoegen"
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr "gebruikers samenvoegen"
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr "Je hebt gevraagd om een samenvoeging van je gebruikersaccounts op Docs.\n"
|
||||
" Om te bevestigen dat u degene bent die het verzoek\n"
|
||||
" heeft geïnitieerd en dat deze e-mail van u is:"
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr "Bevestig door te klikken op de link om de samenvoeging te starten"
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr "Klik hier"
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr "Bevestig"
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr "Uw samenvoegingsverzoek is verwerkt.\n"
|
||||
" Nieuwe documenten worden waarschijnlijk geassocieerd met uw account:"
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr "Je accounts zijn samengevoegd"
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr "Klik hier om te bekijken"
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr "Mijn documenten bekijken"
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr "CSV bestand"
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr "Bezig"
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr "gebruiker samenvoeging CSV import"
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr "gebruiker reconciliation CSV imports"
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr "Uw verzoek tot verzoening is mislukt.\n"
|
||||
" Verzoening mislukt voor de volgende e-mailadressen:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Controleer op typefouten.\n"
|
||||
" U kunt een ander verzoek indienen met de geldige e-mailadressen."
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr "Samenvoeging van je Docs accounts is niet voltooid"
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr "Maak een nieuw verzoek"
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr "titel"
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr "uittreksel"
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr "Document"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr "Documenten"
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr "Naamloos Document"
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr "Open"
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} heeft een document met u gedeeld!"
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, 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:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, 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:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Document/gebruiker link"
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Document/gebruiker link"
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Een link bestaat al voor dit document/deze gebruiker."
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr "Document favoriet"
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr "Document favorieten"
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Dit document is al in gebruik als favoriet door dezelfde gebruiker."
|
||||
|
||||
#: build/lib/core/models.py:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr "Document/gebruiker relatie"
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr "Document/gebruiker relaties"
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr "De gebruiker bestaat al in dit document."
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Dit team bestaat al in dit document."
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
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:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr "Document verzoekt om toegang"
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Document verzoekt om toegangen"
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "Deze gebruiker heeft al om toegang tot dit document gevraagd."
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "{name} verzoekt toegang tot een document!"
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} verzoekt toegang tot het volgende document:"
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} verzoekt toegang tot het document: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr "Kanaal"
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr "Kanalen"
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr "Anoniem"
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr "Reactie"
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr "Reacties"
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr "Deze emoji is al op deze opmerking gereageerd."
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr "Reactie"
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr "Reacties"
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr "e-mailadres"
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr "Document uitnodiging"
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr "Document uitnodigingen"
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Deze email is al geassocieerd met een geregistreerde gebruiker."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr "Docs AI"
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese\n"
|
||||
"Language: pt_PT\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "Informações Pessoais"
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "Permissões"
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Datas importantes"
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr "Estrutura de árvore"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "Título"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
msgstr "Eu sou o criador"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr "Favorito"
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Um novo documento foi criado em seu nome!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "A propriedade de um novo documento foi concedida a você:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "cópia de {title}"
|
||||
@@ -135,262 +143,374 @@ msgstr ""
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"Language: ru_RU\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "Личная информация"
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "Разрешения"
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Важные даты"
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr "Задание по импорту создано и поставлено в очередь."
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr "Обработка выбранных пользовательских сверок"
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr "Древовидная структура"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "Заголовок"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
msgstr "Создатель - я"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr "Скрытый"
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr "Избранное"
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Новый документ был создан от вашего имени!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Вы назначены владельцем для нового документа:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr "Это поле обязательное."
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr "Доступ по ссылке '%(link_reach)s' запрещён в соответствии с настройками родительского документа."
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "копия {title}"
|
||||
@@ -135,262 +143,381 @@ msgstr "Слева"
|
||||
msgid "Right"
|
||||
msgstr "Справа"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "первичный ключ для записи как UUID"
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr "создано"
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "дата и время создания записи"
|
||||
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr "обновлено"
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "дата и время последнего обновления записи"
|
||||
|
||||
#: build/lib/core/models.py:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr "вложение"
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr "Обязательно. 255 символов или меньше. Только ASCII символы."
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr "полное имя"
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr "короткое имя"
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr "личный адрес электронной почты"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr "e-mail администратора"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr "язык"
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "Язык, на котором пользователь хочет видеть интерфейс."
|
||||
|
||||
#: build/lib/core/models.py:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "Часовой пояс, в котором пользователь хочет видеть время."
|
||||
|
||||
#: build/lib/core/models.py:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr "устройство"
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Пользователь является устройством или человеком."
|
||||
|
||||
#: build/lib/core/models.py:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr "статус сотрудника"
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Может ли пользователь войти на этот административный сайт."
|
||||
|
||||
#: build/lib/core/models.py:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr "активный"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Должен ли пользователь рассматриваться как активный. Альтернатива удалению учётных записей."
|
||||
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr "пользователь"
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr "пользователи"
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr "Активный адрес электронной почты"
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr "Адрес электронной почты для деактивации"
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr "Уникальный идентификатор в исходном файле"
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr "В обработке"
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr "Готово"
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr "Выполнено"
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr "Ошибка"
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr "сверка данных пользователя"
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr "сверки данных пользователя"
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr "Вы запросили сверку учётных записей пользователя в Docs.\n"
|
||||
" Чтобы подтвердить факт того, что вы являетесь инициатором запроса\n"
|
||||
" и что этот адрес принадлежит вам:"
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr "Чтобы начать сверку, подтвердите это, нажав на ссылку"
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr "Нажмите здесь"
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr "Подтверждение"
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr "Ваш запрос на сверку был обработан.\n"
|
||||
" Новые документы, вероятно, связаны с вашей учётной записью:"
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr "Ваши учётные записи были объединены"
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr "Нажмите здесь, чтобы просмотреть"
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr "Просмотреть мои документы"
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr "CSV-файл"
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr "Выполнение"
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr "импорт из CSV сверки пользователей"
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr "импорты из CSV сверки пользователями"
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr "Ваш запрос на сверку не удался.\n"
|
||||
" Сверка не удалась для следующих email адресов:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Пожалуйста, проверьте, нет ли в них опечаток.\n"
|
||||
" Вы можете отправить ещё один запрос с действительными адресами электронной почты."
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr "Сверка ваших учётных записей Docs не завершена"
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr "Создать новый запрос"
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr "заголовок"
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr "отрывок"
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr "Документ"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr "Документы"
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr "Безымянный документ"
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr "Открыть"
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} делится с вами документом!"
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} приглашает вас присоединиться к следующему документу с ролью \"{role}\":"
|
||||
|
||||
#: build/lib/core/models.py:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} делится с вами документом: {title}"
|
||||
|
||||
#: build/lib/core/models.py:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Трассировка связи документ/пользователь"
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Трассировка связей документ/пользователь"
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Для этого документа/пользователя уже существует трассировка ссылки."
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr "Избранный документ"
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr "Избранные документы"
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Этот документ уже помечен как избранный для этого пользователя."
|
||||
|
||||
#: build/lib/core/models.py:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr "Отношение документ/пользователь"
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr "Отношения документ/пользователь"
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Этот пользователь уже имеет доступ к этому документу."
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Эта команда уже имеет доступ к этому документу."
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "Может быть выбран либо пользователь, либо команда, но не оба варианта сразу."
|
||||
|
||||
#: build/lib/core/models.py:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr "Документ запрашивает доступ"
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Документ запрашивает доступы"
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "Этот пользователь уже запросил доступ к этому документу."
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "{name} хочет получить доступ к документу!"
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} хочет получить доступ к следующему документу:"
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} запрашивает доступ к документу: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr "Обсуждение"
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr "Обсуждения"
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr "Аноним"
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr "Комментарий"
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr "Комментарии"
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr "Этот эмодзи уже использован в этом комментарии."
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr "Реакция"
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr "Реакции"
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr "адрес электронной почты"
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr "Приглашение для документа"
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr "Приглашения для документов"
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Этот адрес уже связан с зарегистрированным пользователем."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr "Docs ИИ"
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Slovenian\n"
|
||||
"Language: sl_SI\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "Osebni podatki"
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "Dovoljenja"
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Pomembni datumi"
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr "Drevesna struktura"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "Naslov"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
msgstr "Ustvaril sem jaz"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr "Priljubljena"
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
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:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
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:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
@@ -135,262 +143,374 @@ msgstr "Levo"
|
||||
msgid "Right"
|
||||
msgstr "Desno"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "primarni ključ za zapis kot UUID"
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr "ustvarjen na"
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "datum in čas, ko je bil zapis ustvarjen"
|
||||
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr "posodobljeno dne"
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
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:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr "polno ime"
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr "kratko ime"
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr "elektronski naslov identitete"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr "elektronski naslov skrbnika"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr "jezik"
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
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:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
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:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr "naprava"
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Ali je uporabnik naprava ali pravi uporabnik."
|
||||
|
||||
#: build/lib/core/models.py:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr "kadrovski status"
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
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:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr "aktivni"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
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:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr "uporabnik"
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr "uporabniki"
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr "naslov"
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr "odlomek"
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr "Dokument"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr "Dokumenti"
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr "Dokument brez naslova"
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr "Odpri"
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} je delil dokument z vami!"
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, 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:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} je delil dokument z vami: {title}"
|
||||
|
||||
#: build/lib/core/models.py:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Dokument/sled povezave uporabnika"
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Sledi povezav dokumenta/uporabnika"
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Za ta dokument/uporabnika že obstaja sled povezave."
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr "Priljubljeni dokument"
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr "Priljubljeni dokumenti"
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
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:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr "Odnos dokument/uporabnik"
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr "Odnosi dokument/uporabnik"
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Ta uporabnik je že v tem dokumentu."
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Ta ekipa je že v tem dokumentu."
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
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:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr "elektronski naslov"
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr "Vabilo na dokument"
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr "Vabila na dokument"
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Ta e-poštni naslov je že povezan z registriranim uporabnikom."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Swedish\n"
|
||||
"Language: sv_SE\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "Personuppgifter"
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "Behörigheter"
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Viktiga datum"
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
msgstr "Skaparen är jag"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr "Favoriter"
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Ett nytt dokument skapades åt dig!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
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:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
@@ -135,262 +143,374 @@ msgstr ""
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr "aktiv"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr "Öppna"
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr "e-postadress"
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr "Bjud in dokument"
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr "Inbjudningar dokument"
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Denna e-postadress är redan associerad med en registrerad användare."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Turkish\n"
|
||||
"Language: tr_TR\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
@@ -135,262 +143,374 @@ msgstr ""
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Ukrainian\n"
|
||||
"Language: uk_UA\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "Особисті дані"
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "Дозволи"
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Важливі дати"
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr "Завдання імпорту створено і поставлено в чергу."
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr "Обробити обрані узгодження користувача"
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr "Ієрархічна структура"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "Заголовок"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
msgstr "Творець — я"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr "Приховано"
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr "Обране"
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Новий документ був створений від вашого імені!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Ви тепер є власником нового документа:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr "Це поле є обов’язковим."
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr "Доступ до посилання '%(link_reach)s' заборонено на основі конфігурації батьківського документа."
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "копія {title}"
|
||||
@@ -135,262 +143,381 @@ msgstr "Ліворуч"
|
||||
msgid "Right"
|
||||
msgstr "Праворуч"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "первинний ключ для запису як UUID"
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr "створено"
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "дата і час, коли запис було створено"
|
||||
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr "оновлено"
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "дата і час, коли запис був востаннє оновлений"
|
||||
|
||||
#: build/lib/core/models.py:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr "вкладений документ"
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr "Обов'язкове. 255 символів або менше. Тільки символи ASCII."
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr "повне ім'я"
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr "коротке ім'я"
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr "адреса електронної пошти особи"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr "електронна адреса адміністратора"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr "мова"
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "Мова, якою користувач хоче бачити інтерфейс."
|
||||
|
||||
#: build/lib/core/models.py:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "Часовий пояс, в якому користувач хоче бачити час."
|
||||
|
||||
#: build/lib/core/models.py:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr "пристрій"
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Чи є користувач пристроєм чи реальним користувачем."
|
||||
|
||||
#: build/lib/core/models.py:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr "статус співробітника"
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Чи може користувач увійти на цей сайт адміністратора."
|
||||
|
||||
#: build/lib/core/models.py:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr "активний"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Чи слід ставитися до цього користувача як до активного. Зніміть вибір замість видалення облікового запису."
|
||||
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr "користувач"
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr "користувачі"
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr "Активна електронна адреса"
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr "Електронна адреса, що буде деактивована"
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr "Унікальний ідентифікатор у вихідному файлі"
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr "В очікуванні"
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr "Готово"
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr "Виконано"
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr "Помилка"
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr "узгодження користувачів"
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr "узгодження користувачів"
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr "Ви запросили узгодження своїх облікових записів користувачів у Docs.\n"
|
||||
" Щоб підтвердити, що саме ви ініціювали запит\n"
|
||||
" і що ця електронна адреса належить вам:"
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr "Підтвердіть, натиснувши на посилання, щоб почати узгодження"
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr "Натисніть тут"
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr "Підтвердження"
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr "Ваш запит на узгодження оброблено.\n"
|
||||
" Нові документи, ймовірно, пов'язані з вашим обліковим записом:"
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr "Ваші облікові записи були об'єднані"
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr "Натисніть тут, щоб переглянути"
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr "Переглянути мої документи"
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr "CSV-файл"
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr "Виконується"
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr "імпорт CSV для узгодження користувачів"
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr "імпорт CSV для узгодження користувачів"
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr "Ваш запит на узгодження не був виконаний.\n"
|
||||
" Узгодження не вдалося для наступних адрес електронної пошти:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Перевірте, чи немає помилок.\n"
|
||||
" Ви можете надіслати інший запит із дійсними адресами електронної пошти."
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr "Узгодження ваших облікових записів не завершено"
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr "Зробити новий запит"
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr "заголовок"
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr "уривок"
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr "Документ"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr "Документи"
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr "Документ без назви"
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr "Відкрити"
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} ділиться з вами документом!"
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} запрошує вас для роботи з документом із роллю \"{role}\":"
|
||||
|
||||
#: build/lib/core/models.py:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} ділиться з вами документом: {title}"
|
||||
|
||||
#: build/lib/core/models.py:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Трасування посилання Документ/користувач"
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Трасування посилань Документ/користувач"
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Відстеження вже існуючих посилань для цього документа/користувача."
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr "Обраний документ"
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr "Обрані документи"
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Цей документ вже вказаний як обраний для одного користувача."
|
||||
|
||||
#: build/lib/core/models.py:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr "Відносини документ/користувач"
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr "Відносини документ/користувач"
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Цей користувач вже має доступ до цього документу."
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Ця команда вже має доступ до цього документа."
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "Вкажіть користувача або команду, а не обох."
|
||||
|
||||
#: build/lib/core/models.py:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr "Запит доступу до документа"
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Запит доступу для документа"
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "Цей користувач вже попросив доступ до цього документа."
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "{name} хоче отримати доступ до документа!"
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} бажає отримати доступ до наступного документа:"
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} запитує доступ до документа: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr "Обговорення"
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr "Обговорення"
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr "Анонім"
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr "Коментар"
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr "Коментарі"
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr "Цим емодзі вже відреагували на цей коментар."
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr "Реакція"
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr "Реакції"
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr "електронна адреса"
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr "Запрошення до редагування документа"
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr "Запрошення до редагування документів"
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Ця електронна пошта вже пов'язана з зареєстрованим користувачем."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr "Docs ШІ"
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-21 09:53+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 20:12\n"
|
||||
"POT-Creation-Date: 2026-03-06 09:37+0000\n"
|
||||
"PO-Revision-Date: 2026-03-09 14:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Language: zh_CN\n"
|
||||
@@ -17,57 +17,65 @@ msgstr ""
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:28 core/admin.py:28
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "個人資訊"
|
||||
|
||||
#: build/lib/core/admin.py:41 build/lib/core/admin.py:121 core/admin.py:41
|
||||
#: core/admin.py:121
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "權限"
|
||||
|
||||
#: build/lib/core/admin.py:53 core/admin.py:53
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "重要日期"
|
||||
|
||||
#: build/lib/core/admin.py:131 core/admin.py:131
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr "樹狀結構"
|
||||
|
||||
#: build/lib/core/api/filters.py:47 core/api/filters.py:47
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "標題"
|
||||
|
||||
#: build/lib/core/api/filters.py:61 core/api/filters.py:61
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
msgstr "建立者是我"
|
||||
|
||||
#: build/lib/core/api/filters.py:64 core/api/filters.py:64
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr "已隱藏"
|
||||
|
||||
#: build/lib/core/api/filters.py:67 core/api/filters.py:67
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Favorite"
|
||||
msgstr "我的最愛"
|
||||
|
||||
#: build/lib/core/api/serializers.py:505 core/api/serializers.py:505
|
||||
#: build/lib/core/api/serializers.py:513 core/api/serializers.py:513
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "已代表您建立新文件!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:509 core/api/serializers.py:509
|
||||
#: build/lib/core/api/serializers.py:517 core/api/serializers.py:517
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "您已獲得新文件的所有權:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:545 core/api/serializers.py:545
|
||||
#: build/lib/core/api/serializers.py:553 core/api/serializers.py:553
|
||||
msgid "This field is required."
|
||||
msgstr "此欄位為必填。"
|
||||
|
||||
#: build/lib/core/api/serializers.py:556 core/api/serializers.py:556
|
||||
#: build/lib/core/api/serializers.py:564 core/api/serializers.py:564
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr "根據父文件設定,不允許連結範圍「%(link_reach)s」。"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1122 core/api/viewsets.py:1122
|
||||
#: build/lib/core/api/viewsets.py:1279 core/api/viewsets.py:1279
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "{title} 的副本"
|
||||
@@ -135,262 +143,374 @@ msgstr "左"
|
||||
msgid "Right"
|
||||
msgstr "右"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr "ID"
|
||||
|
||||
#: build/lib/core/models.py:82 core/models.py:82
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "記錄的主鍵(UUID)"
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr "建立於"
|
||||
|
||||
#: build/lib/core/models.py:89 core/models.py:89
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "記錄建立的日期與時間"
|
||||
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr "更新於"
|
||||
|
||||
#: build/lib/core/models.py:95 core/models.py:95
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "記錄最後更新的日期與時間"
|
||||
|
||||
#: build/lib/core/models.py:131 core/models.py:131
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
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:142 core/models.py:142
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr "sub"
|
||||
|
||||
#: build/lib/core/models.py:143 core/models.py:143
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr "必填。255 個字元(含)以下。僅限 ASCII 字元。"
|
||||
|
||||
#: build/lib/core/models.py:151 core/models.py:151
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr "全名"
|
||||
|
||||
#: build/lib/core/models.py:153 core/models.py:153
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr "簡稱"
|
||||
|
||||
#: build/lib/core/models.py:156 core/models.py:156
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr "身份驗證電子郵件地址"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr "管理員電子郵件地址"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr "語言"
|
||||
|
||||
#: build/lib/core/models.py:169 core/models.py:169
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "使用者希望介面顯示的語言。"
|
||||
|
||||
#: build/lib/core/models.py:177 core/models.py:177
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "使用者希望時間顯示的時區。"
|
||||
|
||||
#: build/lib/core/models.py:180 core/models.py:180
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr "裝置"
|
||||
|
||||
#: build/lib/core/models.py:182 core/models.py:182
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "使用者是裝置還是真實使用者。"
|
||||
|
||||
#: build/lib/core/models.py:185 core/models.py:185
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr "工作人員狀態"
|
||||
|
||||
#: build/lib/core/models.py:187 core/models.py:187
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "使用者是否可以登入此管理後台。"
|
||||
|
||||
#: build/lib/core/models.py:190 core/models.py:190
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr "啟用"
|
||||
|
||||
#: build/lib/core/models.py:193 core/models.py:193
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "此使用者是否應被視為處於啟用狀態。請取消勾選此項而非刪除帳號。"
|
||||
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
#: build/lib/core/models.py:204 core/models.py:204
|
||||
msgid "user"
|
||||
msgstr "使用者"
|
||||
|
||||
#: build/lib/core/models.py:206 core/models.py:206
|
||||
#: build/lib/core/models.py:205 core/models.py:205
|
||||
msgid "users"
|
||||
msgstr "使用者"
|
||||
|
||||
#: build/lib/core/models.py:362 core/models.py:362
|
||||
#: build/lib/core/models.py:360 core/models.py:360
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:361 core/models.py:361
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:388 core/models.py:388
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:394 build/lib/core/models.py:692 core/models.py:394
|
||||
#: core/models.py:692
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:395 core/models.py:395
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:396 build/lib/core/models.py:694 core/models.py:396
|
||||
#: core/models.py:694
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:695 core/models.py:397
|
||||
#: core/models.py:695
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:405 core/models.py:405
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:644 core/models.py:644
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:650 core/models.py:650
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:655 build/lib/core/models.py:761 core/models.py:655
|
||||
#: core/models.py:761
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:656 core/models.py:656
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:667 core/models.py:667
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:677 core/models.py:677
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:678 core/models.py:678
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:703 core/models.py:703
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:748 core/models.py:748
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:756 core/models.py:756
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:861 core/models.py:861
|
||||
msgid "title"
|
||||
msgstr "標題"
|
||||
|
||||
#: build/lib/core/models.py:363 core/models.py:363
|
||||
#: build/lib/core/models.py:862 core/models.py:862
|
||||
msgid "excerpt"
|
||||
msgstr "摘要"
|
||||
|
||||
#: build/lib/core/models.py:412 core/models.py:412
|
||||
#: build/lib/core/models.py:911 core/models.py:911
|
||||
msgid "Document"
|
||||
msgstr "文件"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:912 core/models.py:912
|
||||
msgid "Documents"
|
||||
msgstr "文件"
|
||||
|
||||
#: build/lib/core/models.py:425 build/lib/core/models.py:828 core/models.py:425
|
||||
#: core/models.py:828
|
||||
#: build/lib/core/models.py:924 build/lib/core/models.py:1328
|
||||
#: core/models.py:924 core/models.py:1328
|
||||
msgid "Untitled Document"
|
||||
msgstr "未命名文件"
|
||||
|
||||
#: build/lib/core/models.py:829 core/models.py:829
|
||||
#: build/lib/core/models.py:1329 core/models.py:1329
|
||||
msgid "Open"
|
||||
msgstr "開啟"
|
||||
|
||||
#: build/lib/core/models.py:864 core/models.py:864
|
||||
#: build/lib/core/models.py:1364 core/models.py:1364
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} 與您分享了一份文件!"
|
||||
|
||||
#: build/lib/core/models.py:868 core/models.py:868
|
||||
#: build/lib/core/models.py:1368 core/models.py:1368
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} 邀請您以「{role}」角色參與以下文件:"
|
||||
|
||||
#: build/lib/core/models.py:874 core/models.py:874
|
||||
#: build/lib/core/models.py:1374 core/models.py:1374
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} 與您分享了一份文件:{title}"
|
||||
|
||||
#: build/lib/core/models.py:975 core/models.py:975
|
||||
#: build/lib/core/models.py:1475 core/models.py:1475
|
||||
msgid "Document/user link trace"
|
||||
msgstr "文件/使用者連結追蹤"
|
||||
|
||||
#: build/lib/core/models.py:976 core/models.py:976
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
msgid "Document/user link traces"
|
||||
msgstr "文件/使用者連結追蹤"
|
||||
|
||||
#: build/lib/core/models.py:982 core/models.py:982
|
||||
#: build/lib/core/models.py:1482 core/models.py:1482
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "此文件/使用者已存在連結追蹤。"
|
||||
|
||||
#: build/lib/core/models.py:1005 core/models.py:1005
|
||||
#: build/lib/core/models.py:1505 core/models.py:1505
|
||||
msgid "Document favorite"
|
||||
msgstr "文件收藏"
|
||||
|
||||
#: build/lib/core/models.py:1006 core/models.py:1006
|
||||
#: build/lib/core/models.py:1506 core/models.py:1506
|
||||
msgid "Document favorites"
|
||||
msgstr "文件收藏"
|
||||
|
||||
#: build/lib/core/models.py:1012 core/models.py:1012
|
||||
#: build/lib/core/models.py:1512 core/models.py:1512
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "此使用者已將此文件加入收藏。"
|
||||
|
||||
#: build/lib/core/models.py:1034 core/models.py:1034
|
||||
#: build/lib/core/models.py:1534 core/models.py:1534
|
||||
msgid "Document/user relation"
|
||||
msgstr "文件/使用者關聯"
|
||||
|
||||
#: build/lib/core/models.py:1035 core/models.py:1035
|
||||
#: build/lib/core/models.py:1535 core/models.py:1535
|
||||
msgid "Document/user relations"
|
||||
msgstr "文件/使用者關聯"
|
||||
|
||||
#: build/lib/core/models.py:1041 core/models.py:1041
|
||||
#: build/lib/core/models.py:1541 core/models.py:1541
|
||||
msgid "This user is already in this document."
|
||||
msgstr "此使用者已在此文件中。"
|
||||
|
||||
#: build/lib/core/models.py:1047 core/models.py:1047
|
||||
#: build/lib/core/models.py:1547 core/models.py:1547
|
||||
msgid "This team is already in this document."
|
||||
msgstr "此團隊已在此文件中。"
|
||||
|
||||
#: build/lib/core/models.py:1053 core/models.py:1053
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "必須設定使用者或團隊其中之一,不能同時設定兩者。"
|
||||
|
||||
#: build/lib/core/models.py:1204 core/models.py:1204
|
||||
#: build/lib/core/models.py:1704 core/models.py:1704
|
||||
msgid "Document ask for access"
|
||||
msgstr "要求文件存取權"
|
||||
|
||||
#: build/lib/core/models.py:1205 core/models.py:1205
|
||||
#: build/lib/core/models.py:1705 core/models.py:1705
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "要求文件存取權"
|
||||
|
||||
#: build/lib/core/models.py:1211 core/models.py:1211
|
||||
#: build/lib/core/models.py:1711 core/models.py:1711
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "此使用者已要求過存取此文件的權限。"
|
||||
|
||||
#: build/lib/core/models.py:1268 core/models.py:1268
|
||||
#: build/lib/core/models.py:1768 core/models.py:1768
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "{name} 想要存取文件!"
|
||||
|
||||
#: build/lib/core/models.py:1272 core/models.py:1272
|
||||
#: build/lib/core/models.py:1772 core/models.py:1772
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} 想要存取以下文件:"
|
||||
|
||||
#: build/lib/core/models.py:1278 core/models.py:1278
|
||||
#: build/lib/core/models.py:1778 core/models.py:1778
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} 正要求存取文件:{title}"
|
||||
|
||||
#: build/lib/core/models.py:1320 core/models.py:1320
|
||||
#: build/lib/core/models.py:1820 core/models.py:1820
|
||||
msgid "Thread"
|
||||
msgstr "對話串"
|
||||
|
||||
#: build/lib/core/models.py:1321 core/models.py:1321
|
||||
#: build/lib/core/models.py:1821 core/models.py:1821
|
||||
msgid "Threads"
|
||||
msgstr "對話串"
|
||||
|
||||
#: build/lib/core/models.py:1324 build/lib/core/models.py:1376
|
||||
#: core/models.py:1324 core/models.py:1376
|
||||
#: build/lib/core/models.py:1824 build/lib/core/models.py:1876
|
||||
#: core/models.py:1824 core/models.py:1876
|
||||
msgid "Anonymous"
|
||||
msgstr "匿名"
|
||||
|
||||
#: build/lib/core/models.py:1371 core/models.py:1371
|
||||
#: build/lib/core/models.py:1871 core/models.py:1871
|
||||
msgid "Comment"
|
||||
msgstr "評論"
|
||||
|
||||
#: build/lib/core/models.py:1372 core/models.py:1372
|
||||
#: build/lib/core/models.py:1872 core/models.py:1872
|
||||
msgid "Comments"
|
||||
msgstr "評論"
|
||||
|
||||
#: build/lib/core/models.py:1421 core/models.py:1421
|
||||
#: build/lib/core/models.py:1921 core/models.py:1921
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr "此評論已標記過此表情符號。"
|
||||
|
||||
#: build/lib/core/models.py:1425 core/models.py:1425
|
||||
#: build/lib/core/models.py:1925 core/models.py:1925
|
||||
msgid "Reaction"
|
||||
msgstr "回應"
|
||||
|
||||
#: build/lib/core/models.py:1426 core/models.py:1426
|
||||
#: build/lib/core/models.py:1926 core/models.py:1926
|
||||
msgid "Reactions"
|
||||
msgstr "回應"
|
||||
|
||||
#: build/lib/core/models.py:1436 core/models.py:1436
|
||||
#: build/lib/core/models.py:1936 core/models.py:1936
|
||||
msgid "email address"
|
||||
msgstr "電子郵件地址"
|
||||
|
||||
#: build/lib/core/models.py:1455 core/models.py:1455
|
||||
#: build/lib/core/models.py:1955 core/models.py:1955
|
||||
msgid "Document invitation"
|
||||
msgstr "文件邀請"
|
||||
|
||||
#: build/lib/core/models.py:1456 core/models.py:1456
|
||||
#: build/lib/core/models.py:1956 core/models.py:1956
|
||||
msgid "Document invitations"
|
||||
msgstr "文件邀請"
|
||||
|
||||
#: build/lib/core/models.py:1476 core/models.py:1476
|
||||
#: build/lib/core/models.py:1976 core/models.py:1976
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "此電子郵件地址已與已註冊使用者關聯。"
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
|
||||
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "impress"
|
||||
version = "4.5.0"
|
||||
version = "4.7.0"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -26,7 +26,7 @@ readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"beautifulsoup4==4.14.3",
|
||||
"boto3==1.42.17",
|
||||
"boto3==1.42.59",
|
||||
"Brotli==1.2.0",
|
||||
"celery[redis]==5.5.3",
|
||||
"django-configurations==2.5.1",
|
||||
@@ -34,34 +34,37 @@ dependencies = [
|
||||
"django-countries==8.2.0",
|
||||
"django-csp==4.0",
|
||||
"django-filter==25.2",
|
||||
"django-lasuite[all]==0.0.22",
|
||||
"django-lasuite[all]==0.0.24",
|
||||
"django-parler==2.3",
|
||||
"django-redis==6.0.0",
|
||||
"django-storages[s3]==1.14.6",
|
||||
"django-timezone-field>=5.1",
|
||||
"django<6.0.0",
|
||||
"django-treebeard==4.8.0",
|
||||
"django-treebeard==5.0.5",
|
||||
"djangorestframework==3.16.1",
|
||||
"drf_spectacular==0.29.0",
|
||||
"dockerflow==2024.4.2",
|
||||
"dockerflow==2026.1.26",
|
||||
"easy_thumbnails==2.10.1",
|
||||
"factory_boy==3.3.3",
|
||||
"gunicorn==23.0.0",
|
||||
"jsonschema==4.25.1",
|
||||
"langfuse==3.11.2",
|
||||
"gunicorn==25.1.0",
|
||||
"jsonschema==4.26.0",
|
||||
"langfuse==3.14.5",
|
||||
"lxml==6.0.2",
|
||||
"markdown==3.10",
|
||||
"markdown==3.10.2",
|
||||
"mozilla-django-oidc==5.0.2",
|
||||
"nested-multipart-parser==1.6.0",
|
||||
"openai==2.14.0",
|
||||
"psycopg[binary]==3.3.2",
|
||||
"pycrdt==0.12.44",
|
||||
"PyJWT==2.10.1",
|
||||
"openai==2.24.0",
|
||||
"psycopg[binary]==3.3.3",
|
||||
"pycrdt==0.12.47",
|
||||
"pydantic==2.12.5",
|
||||
"pydantic-ai-slim[openai,logfire,web]==1.58.0",
|
||||
"PyJWT==2.11.0",
|
||||
"python-magic==0.4.27",
|
||||
"redis<6.0.0",
|
||||
"requests==2.32.5",
|
||||
"sentry-sdk==2.48.0",
|
||||
"whitenoise==6.11.0",
|
||||
"sentry-sdk==2.53.0",
|
||||
"uvicorn==0.41.0",
|
||||
"whitenoise==6.12.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -74,21 +77,21 @@ dependencies = [
|
||||
dev = [
|
||||
"django-extensions==4.1",
|
||||
"django-test-migrations==1.5.0",
|
||||
"drf-spectacular-sidecar==2025.12.1",
|
||||
"drf-spectacular-sidecar==2026.3.1",
|
||||
"freezegun==1.5.5",
|
||||
"ipdb==0.13.13",
|
||||
"ipython==9.8.0",
|
||||
"pyfakefs==6.0.0",
|
||||
"pylint-django==2.6.1",
|
||||
"ipython==9.10.0",
|
||||
"pyfakefs==6.1.3",
|
||||
"pylint-django==2.7.0",
|
||||
"pylint<4.0.0",
|
||||
"pytest-cov==7.0.0",
|
||||
"pytest-django==4.11.1",
|
||||
"pytest-django==4.12.0",
|
||||
"pytest==9.0.2",
|
||||
"pytest-icdiff==0.9",
|
||||
"pytest-xdist==3.8.0",
|
||||
"responses==0.25.8",
|
||||
"ruff==0.14.10",
|
||||
"types-requests==2.32.4.20250913",
|
||||
"responses==0.26.0",
|
||||
"ruff==0.15.4",
|
||||
"types-requests==2.32.4.20260107",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
ARG FRONTEND_IMAGE=frontend-build-output
|
||||
|
||||
FROM node:24-alpine AS frontend-deps
|
||||
|
||||
# Upgrade system packages to install security updates
|
||||
@@ -32,7 +34,7 @@ EXPOSE 3000
|
||||
|
||||
CMD [ "yarn", "dev"]
|
||||
|
||||
# Tilt will rebuild impress target so, we dissociate impress and impress-builder
|
||||
# Tilt will rebuild impress target so, we dissociate impress and impress-builder
|
||||
# to avoid rebuilding the app at every changes.
|
||||
FROM impress AS impress-builder
|
||||
|
||||
@@ -49,6 +51,14 @@ ENV NEXT_PUBLIC_PUBLISH_AS_MIT=${PUBLISH_AS_MIT}
|
||||
|
||||
RUN yarn build
|
||||
|
||||
# Normalize output path to /app (matches the runtime-prod layout)
|
||||
FROM scratch AS frontend-build-output
|
||||
COPY --from=impress-builder /home/frontend/apps/impress/out /app
|
||||
|
||||
# When FRONTEND_IMAGE is set to an external image, BuildKit skips
|
||||
# frontend-deps,impress-builder, and frontend-build-output entirely
|
||||
FROM ${FRONTEND_IMAGE} AS frontend-source
|
||||
|
||||
# ---- Front-end image ----
|
||||
FROM nginxinc/nginx-unprivileged:alpine3.22 AS frontend-production
|
||||
|
||||
@@ -62,9 +72,7 @@ RUN apk update && \
|
||||
ARG DOCKER_USER
|
||||
USER ${DOCKER_USER}
|
||||
|
||||
COPY --from=impress-builder \
|
||||
/home/frontend/apps/impress/out \
|
||||
/usr/share/nginx/html
|
||||
COPY --from=frontend-source /app /app
|
||||
|
||||
COPY ./src/frontend/apps/impress/conf/default.conf /etc/nginx/conf.d
|
||||
COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
|
||||
|
||||
@@ -192,10 +192,10 @@ endobj
|
||||
(react-pdf)
|
||||
endobj
|
||||
55 0 obj
|
||||
(D:20260128100716Z)
|
||||
(D:20260210135720Z)
|
||||
endobj
|
||||
56 0 obj
|
||||
(chromium-8039-0-doc-export-override-content)
|
||||
(chromium-4728-0-doc-export-override-content)
|
||||
endobj
|
||||
52 0 obj
|
||||
<<
|
||||
@@ -216,7 +216,7 @@ endobj
|
||||
58 0 obj
|
||||
<<
|
||||
/Type /FontDescriptor
|
||||
/FontName /FDAZSC+Inter18pt-Regular
|
||||
/FontName /XWNEXS+Inter18pt-Regular
|
||||
/Flags 4
|
||||
/FontBBox [-742.1875 -323.242187 2579.589844 1109.375]
|
||||
/ItalicAngle 0
|
||||
@@ -232,7 +232,7 @@ endobj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /CIDFontType2
|
||||
/BaseFont /FDAZSC+Inter18pt-Regular
|
||||
/BaseFont /XWNEXS+Inter18pt-Regular
|
||||
/CIDSystemInfo <<
|
||||
/Registry (Adobe)
|
||||
/Ordering (Identity)
|
||||
@@ -247,7 +247,7 @@ endobj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /Type0
|
||||
/BaseFont /FDAZSC+Inter18pt-Regular
|
||||
/BaseFont /XWNEXS+Inter18pt-Regular
|
||||
/Encoding /Identity-H
|
||||
/DescendantFonts [59 0 R]
|
||||
/ToUnicode 60 0 R
|
||||
@@ -256,7 +256,7 @@ endobj
|
||||
62 0 obj
|
||||
<<
|
||||
/Type /FontDescriptor
|
||||
/FontName /UEJHFC+Inter18pt-Bold
|
||||
/FontName /QGXPNV+Inter18pt-Bold
|
||||
/Flags 4
|
||||
/FontBBox [-790.527344 -334.472656 2580.566406 1114.746094]
|
||||
/ItalicAngle 0
|
||||
@@ -272,7 +272,7 @@ endobj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /CIDFontType2
|
||||
/BaseFont /UEJHFC+Inter18pt-Bold
|
||||
/BaseFont /QGXPNV+Inter18pt-Bold
|
||||
/CIDSystemInfo <<
|
||||
/Registry (Adobe)
|
||||
/Ordering (Identity)
|
||||
@@ -287,7 +287,7 @@ endobj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /Type0
|
||||
/BaseFont /UEJHFC+Inter18pt-Bold
|
||||
/BaseFont /QGXPNV+Inter18pt-Bold
|
||||
/Encoding /Identity-H
|
||||
/DescendantFonts [63 0 R]
|
||||
/ToUnicode 64 0 R
|
||||
@@ -296,7 +296,7 @@ endobj
|
||||
66 0 obj
|
||||
<<
|
||||
/Type /FontDescriptor
|
||||
/FontName /EUMTON+Inter18pt-Italic
|
||||
/FontName /SLYFFZ+Inter18pt-Italic
|
||||
/Flags 68
|
||||
/FontBBox [-747.558594 -323.242187 2595.703125 1109.375]
|
||||
/ItalicAngle -9.398804
|
||||
@@ -312,7 +312,7 @@ endobj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /CIDFontType2
|
||||
/BaseFont /EUMTON+Inter18pt-Italic
|
||||
/BaseFont /SLYFFZ+Inter18pt-Italic
|
||||
/CIDSystemInfo <<
|
||||
/Registry (Adobe)
|
||||
/Ordering (Identity)
|
||||
@@ -327,7 +327,7 @@ endobj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /Type0
|
||||
/BaseFont /EUMTON+Inter18pt-Italic
|
||||
/BaseFont /SLYFFZ+Inter18pt-Italic
|
||||
/Encoding /Identity-H
|
||||
/DescendantFonts [67 0 R]
|
||||
/ToUnicode 68 0 R
|
||||
@@ -336,7 +336,7 @@ endobj
|
||||
70 0 obj
|
||||
<<
|
||||
/Type /FontDescriptor
|
||||
/FontName /HIJACG+GeistMono-Regular
|
||||
/FontName /GPERZO+GeistMono-Regular
|
||||
/Flags 5
|
||||
/FontBBox [-1738 -247 654 1012]
|
||||
/ItalicAngle 0
|
||||
@@ -352,7 +352,7 @@ endobj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /CIDFontType2
|
||||
/BaseFont /HIJACG+GeistMono-Regular
|
||||
/BaseFont /GPERZO+GeistMono-Regular
|
||||
/CIDSystemInfo <<
|
||||
/Registry (Adobe)
|
||||
/Ordering (Identity)
|
||||
@@ -367,7 +367,7 @@ endobj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /Type0
|
||||
/BaseFont /HIJACG+GeistMono-Regular
|
||||
/BaseFont /GPERZO+GeistMono-Regular
|
||||
/Encoding /Identity-H
|
||||
/DescendantFonts [71 0 R]
|
||||
/ToUnicode 72 0 R
|
||||
@@ -376,7 +376,7 @@ endobj
|
||||
74 0 obj
|
||||
<<
|
||||
/Type /FontDescriptor
|
||||
/FontName /IKVFNP+Inter18pt-BoldItalic
|
||||
/FontName /CNJFYA+Inter18pt-BoldItalic
|
||||
/Flags 68
|
||||
/FontBBox [-795.898437 -334.472656 2596.191406 1114.746094]
|
||||
/ItalicAngle -9.398804
|
||||
@@ -392,7 +392,7 @@ endobj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /CIDFontType2
|
||||
/BaseFont /IKVFNP+Inter18pt-BoldItalic
|
||||
/BaseFont /CNJFYA+Inter18pt-BoldItalic
|
||||
/CIDSystemInfo <<
|
||||
/Registry (Adobe)
|
||||
/Ordering (Identity)
|
||||
@@ -407,7 +407,7 @@ endobj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /Type0
|
||||
/BaseFont /IKVFNP+Inter18pt-BoldItalic
|
||||
/BaseFont /CNJFYA+Inter18pt-BoldItalic
|
||||
/Encoding /Identity-H
|
||||
/DescendantFonts [75 0 R]
|
||||
/ToUnicode 76 0 R
|
||||
@@ -1403,7 +1403,7 @@ trailer
|
||||
/Size 87
|
||||
/Root 3 0 R
|
||||
/Info 52 0 R
|
||||
/ID [<2f4ec8da7e87471807031f721b6c9ac2> <2f4ec8da7e87471807031f721b6c9ac2>]
|
||||
/ID [<4d0627755c809232c991979db9766911> <4d0627755c809232c991979db9766911>]
|
||||
>>
|
||||
startxref
|
||||
101726
|
||||
|
||||
@@ -76,28 +76,6 @@ test.describe('Config', () => {
|
||||
expect(webSocket.url()).toContain('ws://localhost:4444/collaboration/ws/');
|
||||
});
|
||||
|
||||
test('it checks the AI feature flag from config endpoint', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await overrideConfig(page, {
|
||||
AI_FEATURE_ENABLED: false,
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
await createDoc(page, 'doc-ai-feature', browserName, 1);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('Anything');
|
||||
await page.getByText('Anything').selectText();
|
||||
expect(
|
||||
await page.locator('button[data-test="convertMarkdown"]').count(),
|
||||
).toBe(1);
|
||||
expect(await page.locator('button[data-test="ai-actions"]').count()).toBe(
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
test('it checks that Crisp is trying to init from config endpoint', async ({
|
||||
page,
|
||||
}) => {
|
||||
@@ -140,26 +118,6 @@ test.describe('Config', () => {
|
||||
).toBeAttached();
|
||||
});
|
||||
|
||||
test('it checks theme_customization.translations config', async ({
|
||||
page,
|
||||
}) => {
|
||||
await overrideConfig(page, {
|
||||
theme_customization: {
|
||||
translations: {
|
||||
en: {
|
||||
translation: {
|
||||
Docs: 'MyCustomDocs',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page.getByText('MyCustomDocs')).toBeAttached();
|
||||
});
|
||||
|
||||
test('it checks the config api is called', async ({ page }) => {
|
||||
const responsePromise = page.waitForResponse(
|
||||
(response) =>
|
||||
@@ -172,11 +130,7 @@ test.describe('Config', () => {
|
||||
expect(response.ok()).toBeTruthy();
|
||||
|
||||
const json = (await response.json()) as typeof CONFIG;
|
||||
const { theme_customization, ...configApi } = json;
|
||||
expect(theme_customization).toBeDefined();
|
||||
const { theme_customization: _, ...CONFIG_LEFT } = CONFIG;
|
||||
|
||||
expect(configApi).toStrictEqual(CONFIG_LEFT);
|
||||
expect(json).toStrictEqual(CONFIG);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -186,14 +140,24 @@ test.describe('Config: Not logged', () => {
|
||||
test('it checks that theme is configured from config endpoint', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto('/');
|
||||
|
||||
await expect(
|
||||
page.getByText('Collaborative writing, Simplified.'),
|
||||
).toHaveCSS('font-family', /Roboto/i, {
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
await overrideConfig(page, {
|
||||
FRONTEND_THEME: 'dsfr',
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const header = page.locator('header').first();
|
||||
// alt 'Gouvernement Logo' comes from the theme
|
||||
await expect(header.getByAltText('Gouvernement Logo')).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Collaborative writing, Simplified.'),
|
||||
).toHaveCSS('font-family', /Marianne/i, {
|
||||
timeout: 10000,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
339
src/frontend/apps/e2e/__tests__/app-impress/doc-ai.spec.ts
Normal file
@@ -0,0 +1,339 @@
|
||||
/* eslint-disable playwright/no-conditional-expect */
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
createDoc,
|
||||
mockedDocument,
|
||||
overrideConfig,
|
||||
verifyDocName,
|
||||
} from './utils-common';
|
||||
import {
|
||||
mockAIResponse,
|
||||
openSuggestionMenu,
|
||||
writeInEditor,
|
||||
} from './utils-editor';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test.describe('Doc AI feature', () => {
|
||||
[
|
||||
{
|
||||
AI_FEATURE_ENABLED: false,
|
||||
selector: 'Ask AI',
|
||||
},
|
||||
{
|
||||
AI_FEATURE_ENABLED: true,
|
||||
AI_FEATURE_BLOCKNOTE_ENABLED: false,
|
||||
selector: 'Ask AI',
|
||||
},
|
||||
{
|
||||
AI_FEATURE_ENABLED: true,
|
||||
AI_FEATURE_LEGACY_ENABLED: false,
|
||||
selector: 'AI',
|
||||
},
|
||||
].forEach((config) => {
|
||||
test(`it checks the AI feature flag from config endpoint: ${JSON.stringify(config)}`, async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await overrideConfig(page, config);
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
await createDoc(page, 'doc-ai-feature', browserName, 1);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('Anything');
|
||||
await page.getByText('Anything').selectText();
|
||||
expect(
|
||||
await page.locator('button[data-test="convertMarkdown"]').count(),
|
||||
).toBe(1);
|
||||
await expect(
|
||||
page.getByRole('button', { name: config.selector, exact: true }),
|
||||
).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test('it checks the AI feature and accepts changes', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await overrideConfig(page, {
|
||||
AI_BOT: {
|
||||
name: 'Albert AI',
|
||||
color: '#8bc6ff',
|
||||
},
|
||||
});
|
||||
|
||||
await mockAIResponse(page);
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
await createDoc(page, 'doc-ai', browserName, 1);
|
||||
|
||||
await openSuggestionMenu({ page });
|
||||
await page.getByText('Ask AI').click();
|
||||
await expect(
|
||||
page.getByRole('option', { name: 'Continue Writing' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('option', { name: 'Summarize' })).toBeVisible();
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
const editor = await writeInEditor({ page, text: 'Hello World' });
|
||||
await editor.getByText('Hello World').selectText();
|
||||
|
||||
// Check from toolbar
|
||||
await page.getByRole('button', { name: 'Ask AI' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('option', { name: 'Improve Writing' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('option', { name: 'Fix Spelling' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('option', { name: 'Translate' })).toBeVisible();
|
||||
|
||||
await page.getByRole('option', { name: 'Translate' }).click();
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Ask anything...' })
|
||||
.fill('Translate into french');
|
||||
await page.getByRole('textbox', { name: 'Ask anything...' }).press('Enter');
|
||||
await expect(editor.getByText('Albert AI')).toBeVisible();
|
||||
await page
|
||||
.locator('p.bn-mt-suggestion-menu-item-title')
|
||||
.getByText('Accept')
|
||||
.click();
|
||||
|
||||
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
|
||||
|
||||
// Check Suggestion menu
|
||||
await page.locator('.bn-block-outer').last().fill('/');
|
||||
await expect(page.getByText('Write with AI')).toBeVisible();
|
||||
|
||||
// Reload the page to check that the AI change is still there
|
||||
await page.goto(page.url());
|
||||
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
|
||||
});
|
||||
|
||||
test('it reverts with the AI feature', async ({ page, browserName }) => {
|
||||
await overrideConfig(page, {
|
||||
AI_BOT: {
|
||||
name: 'Albert AI',
|
||||
color: '#8bc6ff',
|
||||
},
|
||||
});
|
||||
|
||||
await mockAIResponse(page);
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
await createDoc(page, 'doc-ai', browserName, 1);
|
||||
|
||||
const editor = await writeInEditor({ page, text: 'Hello World' });
|
||||
await editor.getByText('Hello World').selectText();
|
||||
|
||||
// Check from toolbar
|
||||
await page.getByRole('button', { name: 'Ask AI' }).click();
|
||||
|
||||
await page.getByRole('option', { name: 'Translate' }).click();
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Ask anything...' })
|
||||
.fill('Translate into french');
|
||||
await page.getByRole('textbox', { name: 'Ask anything...' }).press('Enter');
|
||||
await expect(editor.getByText('Albert AI')).toBeVisible();
|
||||
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
|
||||
await page
|
||||
.locator('p.bn-mt-suggestion-menu-item-title')
|
||||
.getByText('Revert')
|
||||
.click();
|
||||
|
||||
await expect(editor.getByText('Hello World')).toBeVisible();
|
||||
});
|
||||
|
||||
test('it checks the AI buttons feature legacy', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await page.route(/.*\/ai-translate\//, async (route) => {
|
||||
const request = route.request();
|
||||
if (request.method().includes('POST')) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
answer: 'Hallo Welt',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
await createDoc(page, 'doc-ai', browserName, 1);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('Hello World');
|
||||
|
||||
const editor = page.locator('.ProseMirror');
|
||||
await editor.getByText('Hello').selectText();
|
||||
|
||||
await page.getByRole('button', { name: 'AI', exact: true }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Rephrase' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Summarize' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('menuitem', { name: 'Correct' })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Language' }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Language' }).hover();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'English', exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'French', exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'German', exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'German', exact: true }).click();
|
||||
|
||||
await expect(editor.getByText('Hallo Welt')).toBeVisible();
|
||||
});
|
||||
|
||||
[
|
||||
{ ai_transform: false, ai_translate: false },
|
||||
{ ai_transform: true, ai_translate: false },
|
||||
{ ai_transform: false, ai_translate: true },
|
||||
].forEach(({ ai_transform, ai_translate }) => {
|
||||
test(`it checks AI buttons when can transform is at "${ai_transform}" and can translate is at "${ai_translate}"`, async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await mockedDocument(page, {
|
||||
accesses: [
|
||||
{
|
||||
id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
|
||||
role: 'owner',
|
||||
user: {
|
||||
email: 'super@owner.com',
|
||||
full_name: 'Super Owner',
|
||||
},
|
||||
},
|
||||
],
|
||||
abilities: {
|
||||
destroy: true, // Means owner
|
||||
link_configuration: true,
|
||||
ai_transform,
|
||||
ai_translate,
|
||||
accesses_manage: true,
|
||||
accesses_view: true,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
},
|
||||
link_reach: 'restricted',
|
||||
link_role: 'editor',
|
||||
created_at: '2021-09-01T09:00:00Z',
|
||||
title: '',
|
||||
});
|
||||
|
||||
const [randomDoc] = await createDoc(
|
||||
page,
|
||||
'doc-editor-ai',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
|
||||
await verifyDocName(page, randomDoc);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('Hello World');
|
||||
|
||||
const editor = page.locator('.ProseMirror');
|
||||
await editor.getByText('Hello').selectText();
|
||||
|
||||
if (!ai_transform && !ai_translate) {
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'AI', exact: true }),
|
||||
).toBeHidden();
|
||||
return;
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'AI', exact: true }).click();
|
||||
|
||||
if (ai_transform) {
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||
).toBeVisible();
|
||||
} else {
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||
).toBeHidden();
|
||||
}
|
||||
|
||||
if (ai_translate) {
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Language' }),
|
||||
).toBeVisible();
|
||||
} else {
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Language' }),
|
||||
).toBeHidden();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test(`it checks ai_proxy ability`, async ({ page, browserName }) => {
|
||||
await mockedDocument(page, {
|
||||
accesses: [
|
||||
{
|
||||
id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
|
||||
role: 'owner',
|
||||
user: {
|
||||
email: 'super@owner.com',
|
||||
full_name: 'Super Owner',
|
||||
},
|
||||
},
|
||||
],
|
||||
abilities: {
|
||||
destroy: true, // Means owner
|
||||
link_configuration: true,
|
||||
ai_proxy: false,
|
||||
accesses_manage: true,
|
||||
accesses_view: true,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
},
|
||||
link_reach: 'restricted',
|
||||
link_role: 'editor',
|
||||
created_at: '2021-09-01T09:00:00Z',
|
||||
title: '',
|
||||
});
|
||||
|
||||
const [randomDoc] = await createDoc(
|
||||
page,
|
||||
'doc-editor-ai-proxy',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
|
||||
await verifyDocName(page, randomDoc);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('Hello World');
|
||||
|
||||
const editor = page.locator('.ProseMirror');
|
||||
await editor.getByText('Hello').selectText();
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Ask AI' })).toBeHidden();
|
||||
await page.locator('.bn-block-outer').last().fill('/');
|
||||
await expect(page.getByText('Write with AI')).toBeHidden();
|
||||
});
|
||||
});
|
||||
@@ -41,7 +41,7 @@ test.describe('Doc Comments', () => {
|
||||
// We add a comment with the first user
|
||||
const editor = await writeInEditor({ page, text: 'Hello World' });
|
||||
await editor.getByText('Hello').selectText();
|
||||
await page.getByRole('button', { name: 'Comment' }).click();
|
||||
await page.getByRole('button', { name: 'Add comment' }).click();
|
||||
|
||||
const thread = page.locator('.bn-thread');
|
||||
await thread.getByRole('paragraph').first().fill('This is a comment');
|
||||
@@ -124,7 +124,7 @@ test.describe('Doc Comments', () => {
|
||||
// Checks add react reaction
|
||||
const editor = await writeInEditor({ page, text: 'Hello' });
|
||||
await editor.getByText('Hello').selectText();
|
||||
await page.getByRole('button', { name: 'Comment' }).click();
|
||||
await page.getByRole('button', { name: 'Add comment' }).click();
|
||||
|
||||
const thread = page.locator('.bn-thread');
|
||||
await thread.getByRole('paragraph').first().fill('This is a comment');
|
||||
@@ -191,7 +191,7 @@ test.describe('Doc Comments', () => {
|
||||
|
||||
/* Delete the last comment remove the thread */
|
||||
await editor.getByText('Hello').selectText();
|
||||
await page.getByRole('button', { name: 'Comment' }).click();
|
||||
await page.getByRole('button', { name: 'Add comment' }).click();
|
||||
|
||||
await thread.getByRole('paragraph').first().fill('This is a new comment');
|
||||
await thread.locator('[data-test="save"]').click();
|
||||
@@ -249,7 +249,7 @@ test.describe('Doc Comments', () => {
|
||||
editor.getByText('Hello, I can edit the document'),
|
||||
).toBeVisible();
|
||||
await otherEditor.getByText('Hello').selectText();
|
||||
await otherPage.getByRole('button', { name: 'Comment' }).click();
|
||||
await otherPage.getByRole('button', { name: 'Add comment' }).click();
|
||||
const otherThread = otherPage.locator('.bn-thread');
|
||||
await otherThread
|
||||
.getByRole('paragraph')
|
||||
@@ -280,7 +280,7 @@ test.describe('Doc Comments', () => {
|
||||
await expect(otherThread).toBeHidden();
|
||||
await otherEditor.getByText('Hello').selectText();
|
||||
await expect(
|
||||
otherPage.getByRole('button', { name: 'Comment' }),
|
||||
otherPage.getByRole('button', { name: 'Add comment' }),
|
||||
).toBeHidden();
|
||||
|
||||
await otherPage.reload();
|
||||
@@ -334,7 +334,7 @@ test.describe('Doc Comments', () => {
|
||||
// We add a comment in the first document
|
||||
const editor1 = await writeInEditor({ page, text: 'Document One' });
|
||||
await editor1.getByText('Document One').selectText();
|
||||
await page.getByRole('button', { name: 'Comment' }).click();
|
||||
await page.getByRole('button', { name: 'Add comment' }).click();
|
||||
|
||||
const thread1 = page.locator('.bn-thread');
|
||||
await thread1.getByRole('paragraph').first().fill('Comment in Doc One');
|
||||
@@ -388,7 +388,7 @@ test.describe('Doc Comments mobile', () => {
|
||||
// Checks add react reaction
|
||||
const editor = await writeInEditor({ page, text: 'Hello' });
|
||||
await editor.getByText('Hello').selectText();
|
||||
await page.getByRole('button', { name: 'Comment' }).click();
|
||||
await page.getByRole('button', { name: 'Add comment' }).click();
|
||||
|
||||
const thread = page.locator('.bn-thread');
|
||||
await thread.getByRole('paragraph').first().fill('This is a comment');
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable playwright/no-conditional-expect */
|
||||
import path from 'path';
|
||||
|
||||
import { expect, test } from '@playwright/test';
|
||||
@@ -7,7 +6,6 @@ import cs from 'convert-stream';
|
||||
import {
|
||||
createDoc,
|
||||
goToGridDoc,
|
||||
mockedDocument,
|
||||
overrideConfig,
|
||||
verifyDocName,
|
||||
} from './utils-common';
|
||||
@@ -39,6 +37,7 @@ test.describe('Doc Editor', () => {
|
||||
.selectText();
|
||||
|
||||
const toolbar = page.locator('.bn-formatting-toolbar');
|
||||
await expect(toolbar.getByRole('button', { name: 'Ask AI' })).toBeVisible();
|
||||
await expect(
|
||||
toolbar.locator('button[data-test="comment-toolbar-button"]'),
|
||||
).toBeVisible();
|
||||
@@ -64,9 +63,6 @@ test.describe('Doc Editor', () => {
|
||||
await expect(
|
||||
toolbar.locator('button[data-test="createLink"]'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
toolbar.locator('button[data-test="ai-actions"]'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
toolbar.locator('button[data-test="convertMarkdown"]'),
|
||||
).toBeVisible();
|
||||
@@ -93,14 +89,12 @@ test.describe('Doc Editor', () => {
|
||||
|
||||
await expect(image).toHaveAttribute('role', 'presentation');
|
||||
|
||||
await image.dblclick();
|
||||
await image.click();
|
||||
|
||||
await expect(toolbar.getByRole('button', { name: 'Ask AI' })).toBeHidden();
|
||||
await expect(
|
||||
toolbar.locator('button[data-test="comment-toolbar-button"]'),
|
||||
).toBeHidden();
|
||||
await expect(
|
||||
toolbar.locator('button[data-test="ai-actions"]'),
|
||||
).toBeHidden();
|
||||
await expect(
|
||||
toolbar.locator('button[data-test="convertMarkdown"]'),
|
||||
).toBeHidden();
|
||||
@@ -389,139 +383,6 @@ test.describe('Doc Editor', () => {
|
||||
await expect(image).toHaveAttribute('aria-hidden', 'true');
|
||||
});
|
||||
|
||||
test('it checks the AI buttons', async ({ page, browserName }) => {
|
||||
await page.route(/.*\/ai-translate\//, async (route) => {
|
||||
const request = route.request();
|
||||
if (request.method().includes('POST')) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
answer: 'Bonjour le monde',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
await createDoc(page, 'doc-ai', browserName, 1);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('Hello World');
|
||||
|
||||
const editor = page.locator('.ProseMirror');
|
||||
await editor.getByText('Hello').selectText();
|
||||
|
||||
await page.getByRole('button', { name: 'AI' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Rephrase' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Summarize' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('menuitem', { name: 'Correct' })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Language' }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Language' }).hover();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'English', exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'French', exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'German', exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'English', exact: true }).click();
|
||||
|
||||
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
|
||||
});
|
||||
|
||||
[
|
||||
{ ai_transform: false, ai_translate: false },
|
||||
{ ai_transform: true, ai_translate: false },
|
||||
{ ai_transform: false, ai_translate: true },
|
||||
].forEach(({ ai_transform, ai_translate }) => {
|
||||
test(`it checks AI buttons when can transform is at "${ai_transform}" and can translate is at "${ai_translate}"`, async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await mockedDocument(page, {
|
||||
accesses: [
|
||||
{
|
||||
id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
|
||||
role: 'owner',
|
||||
user: {
|
||||
email: 'super@owner.com',
|
||||
full_name: 'Super Owner',
|
||||
},
|
||||
},
|
||||
],
|
||||
abilities: {
|
||||
destroy: true, // Means owner
|
||||
link_configuration: true,
|
||||
ai_transform,
|
||||
ai_translate,
|
||||
accesses_manage: true,
|
||||
accesses_view: true,
|
||||
update: true,
|
||||
partial_update: true,
|
||||
retrieve: true,
|
||||
},
|
||||
link_reach: 'restricted',
|
||||
link_role: 'editor',
|
||||
created_at: '2021-09-01T09:00:00Z',
|
||||
title: '',
|
||||
});
|
||||
|
||||
const [randomDoc] = await createDoc(
|
||||
page,
|
||||
'doc-editor-ai',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
|
||||
await verifyDocName(page, randomDoc);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('Hello World');
|
||||
|
||||
const editor = page.locator('.ProseMirror');
|
||||
await editor.getByText('Hello').selectText();
|
||||
|
||||
if (!ai_transform && !ai_translate) {
|
||||
await expect(page.getByRole('button', { name: 'AI' })).toBeHidden();
|
||||
return;
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'AI' }).click();
|
||||
|
||||
if (ai_transform) {
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||
).toBeVisible();
|
||||
} else {
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||
).toBeHidden();
|
||||
}
|
||||
|
||||
if (ai_translate) {
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Language' }),
|
||||
).toBeVisible();
|
||||
} else {
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Language' }),
|
||||
).toBeHidden();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('it downloads unsafe files', async ({ page, browserName }) => {
|
||||
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
|
||||
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, mockedListDocs, toggleHeaderMenu } from './utils-common';
|
||||
import {
|
||||
createDoc,
|
||||
getGridRow,
|
||||
getOtherBrowserName,
|
||||
mockedListDocs,
|
||||
toggleHeaderMenu,
|
||||
verifyDocName,
|
||||
} from './utils-common';
|
||||
import { writeInEditor } from './utils-editor';
|
||||
import {
|
||||
addNewMember,
|
||||
connectOtherUserToDoc,
|
||||
updateShareLink,
|
||||
} from './utils-share';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.describe('Doc grid dnd', () => {
|
||||
test('it creates a doc', async ({ page, browserName }) => {
|
||||
test.describe('Doc grid move', () => {
|
||||
test('it checks drag and drop functionality', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await page.goto('/');
|
||||
const header = page.locator('header').first();
|
||||
await createDoc(page, 'Draggable doc', browserName, 1);
|
||||
@@ -29,7 +45,7 @@ test.describe('Doc grid dnd', () => {
|
||||
await expect(draggableElement).toBeVisible();
|
||||
await expect(dropZone).toBeVisible();
|
||||
|
||||
// Obtenir les positions des éléments
|
||||
// Get the position of the elements
|
||||
const draggableBoundingBox = await draggableElement.boundingBox();
|
||||
const dropZoneBoundingBox = await dropZone.boundingBox();
|
||||
|
||||
@@ -46,7 +62,7 @@ test.describe('Doc grid dnd', () => {
|
||||
);
|
||||
await page.mouse.down();
|
||||
|
||||
// Déplacer vers la zone cible
|
||||
// Move to the target zone
|
||||
await page.mouse.move(
|
||||
dropZoneBoundingBox.x + dropZoneBoundingBox.width / 2,
|
||||
dropZoneBoundingBox.y + dropZoneBoundingBox.height / 2,
|
||||
@@ -161,6 +177,215 @@ test.describe('Doc grid dnd', () => {
|
||||
|
||||
await page.mouse.up();
|
||||
});
|
||||
|
||||
test('it moves a doc from the doc search modal', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
await page.goto('/');
|
||||
|
||||
const [titleDoc1] = await createDoc(page, 'Draggable doc', browserName, 1);
|
||||
|
||||
const otherBrowserName = getOtherBrowserName(browserName);
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
await addNewMember(page, 0, 'Administrator', otherBrowserName);
|
||||
await page
|
||||
.getByRole('dialog')
|
||||
.getByRole('button', { name: 'close' })
|
||||
.click();
|
||||
|
||||
await page.getByRole('button', { name: 'Back to homepage' }).click();
|
||||
|
||||
const [titleDoc2] = await createDoc(page, 'Droppable doc', browserName, 1);
|
||||
await page.getByRole('button', { name: 'Back to homepage' }).click();
|
||||
|
||||
const docsGrid = page.getByTestId('docs-grid');
|
||||
await expect(docsGrid.getByText(titleDoc1)).toBeVisible();
|
||||
await expect(docsGrid.getByText(titleDoc2)).toBeVisible();
|
||||
|
||||
const row = await getGridRow(page, titleDoc1);
|
||||
await row.getByText(`more_horiz`).click();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Move into a doc' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('dialog').getByRole('heading', { name: 'Move' }),
|
||||
).toBeVisible();
|
||||
|
||||
const input = page.getByRole('combobox', { name: 'Quick search input' });
|
||||
await input.click();
|
||||
await input.fill(titleDoc2);
|
||||
|
||||
await expect(
|
||||
page.getByRole('option').first().getByText(titleDoc2),
|
||||
).toBeVisible();
|
||||
|
||||
// Select the first result
|
||||
await page.keyboard.press('Enter');
|
||||
// The CTA should get the focus
|
||||
await page.keyboard.press('Tab');
|
||||
// Validate the move action
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
await expect(
|
||||
page
|
||||
.getByRole('dialog')
|
||||
.getByText('it will lose its current access rights'),
|
||||
).toBeVisible();
|
||||
|
||||
await page
|
||||
.getByRole('dialog')
|
||||
.getByRole('button', { name: 'Move', exact: true })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
await expect(docsGrid.getByText(titleDoc1)).toBeHidden();
|
||||
await docsGrid
|
||||
.getByRole('link', { name: `Open document ${titleDoc2}` })
|
||||
.click();
|
||||
|
||||
await verifyDocName(page, titleDoc2);
|
||||
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
await expect(docTree.getByText(titleDoc1)).toBeVisible();
|
||||
});
|
||||
|
||||
test('it proposes an access request when moving a doc without sufficient permissions', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
test.slow();
|
||||
await page.goto('/');
|
||||
|
||||
const [titleDoc1] = await createDoc(page, 'Move doc', browserName, 1);
|
||||
|
||||
const { otherPage, cleanup } = await connectOtherUserToDoc({
|
||||
docUrl: '/',
|
||||
browserName,
|
||||
});
|
||||
|
||||
// Another user creates a doc
|
||||
const [titleDoc2] = await createDoc(otherPage, 'Drop doc', browserName, 1);
|
||||
await writeInEditor({
|
||||
page: otherPage,
|
||||
text: 'Hello world',
|
||||
});
|
||||
// Make it public
|
||||
await otherPage.getByRole('button', { name: 'Share' }).click();
|
||||
await updateShareLink(otherPage, 'Public');
|
||||
await otherPage
|
||||
.getByRole('dialog')
|
||||
.getByRole('button', { name: 'close' })
|
||||
.click();
|
||||
const otherPageUrl = otherPage.url();
|
||||
|
||||
// The first user visit the doc to have it in his grid list
|
||||
await page.goto(otherPageUrl);
|
||||
await expect(page.getByText('Hello world')).toBeVisible();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.getByRole('button', { name: 'Back to homepage' }).click();
|
||||
|
||||
const docsGrid = page.getByTestId('docs-grid');
|
||||
await expect(docsGrid.getByText(titleDoc1)).toBeVisible();
|
||||
await expect(docsGrid.getByText(titleDoc2)).toBeVisible();
|
||||
|
||||
const row = await getGridRow(page, titleDoc1);
|
||||
await row.getByText(`more_horiz`).click();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Move into a doc' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('dialog').getByRole('heading', { name: 'Move' }),
|
||||
).toBeVisible();
|
||||
|
||||
const input = page.getByRole('combobox', { name: 'Quick search input' });
|
||||
await input.click();
|
||||
await input.fill(titleDoc2);
|
||||
|
||||
await expect(
|
||||
page.getByRole('option').first().getByText(titleDoc2),
|
||||
).toBeVisible();
|
||||
|
||||
// Select the first result
|
||||
await page.keyboard.press('Enter');
|
||||
// The CTA should get the focus
|
||||
await page.keyboard.press('Tab');
|
||||
// Validate the move action
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
// Request access modal should be visible
|
||||
await expect(
|
||||
page
|
||||
.getByRole('dialog')
|
||||
.getByText(
|
||||
'You need edit access to the destination. Request access, then try again.',
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
await page
|
||||
.getByRole('dialog')
|
||||
.getByRole('button', { name: 'Request access', exact: true })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// The other user should receive the access request and be able to approve it
|
||||
await otherPage.getByRole('button', { name: 'Share' }).click();
|
||||
await expect(otherPage.getByText('Access Requests')).toBeVisible();
|
||||
await expect(otherPage.getByText(`E2E ${browserName}`)).toBeVisible();
|
||||
|
||||
const emailRequest = `user.test@${browserName}.test`;
|
||||
await expect(otherPage.getByText(emailRequest)).toBeVisible();
|
||||
const container = otherPage.getByTestId(
|
||||
`doc-share-access-request-row-${emailRequest}`,
|
||||
);
|
||||
await container.getByTestId('doc-role-dropdown').click();
|
||||
await otherPage.getByRole('menuitem', { name: 'Administrator' }).click();
|
||||
await container.getByRole('button', { name: 'Approve' }).click();
|
||||
|
||||
await expect(otherPage.getByText('Access Requests')).toBeHidden();
|
||||
await expect(otherPage.getByText('Share with 2 users')).toBeVisible();
|
||||
await expect(otherPage.getByText(`E2E ${browserName}`)).toBeVisible();
|
||||
|
||||
// The first user should now be able to move the doc
|
||||
await page.reload();
|
||||
await row.getByText(`more_horiz`).click();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Move into a doc' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('dialog').getByRole('heading', { name: 'Move' }),
|
||||
).toBeVisible();
|
||||
|
||||
await input.click();
|
||||
await input.fill(titleDoc2);
|
||||
|
||||
await expect(
|
||||
page.getByRole('option').first().getByText(titleDoc2),
|
||||
).toBeVisible();
|
||||
|
||||
// Select the first result
|
||||
await page.keyboard.press('Enter');
|
||||
// The CTA should get the focus
|
||||
await page.keyboard.press('Tab');
|
||||
// Validate the move action
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
await expect(docsGrid.getByText(titleDoc1)).toBeHidden();
|
||||
await docsGrid
|
||||
.getByRole('link', { name: `Open document ${titleDoc2}` })
|
||||
.click();
|
||||
|
||||
await verifyDocName(page, titleDoc2);
|
||||
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
await expect(docTree.getByText(titleDoc1)).toBeVisible({
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Doc grid dnd mobile', () => {
|
||||
@@ -115,7 +115,7 @@ test.describe('Document grid item options', () => {
|
||||
|
||||
// Pin
|
||||
await row.getByText(`more_horiz`).click();
|
||||
await page.getByText('push_pin').click();
|
||||
await page.getByRole('menuitem', { name: 'Pin' }).click();
|
||||
|
||||
// Check is pinned
|
||||
await expect(row.getByTestId('doc-pinned-icon')).toBeVisible();
|
||||
@@ -264,7 +264,7 @@ test.describe('Documents Grid', () => {
|
||||
test('checks all the elements are visible', async ({ page }) => {
|
||||
void page.goto('/');
|
||||
|
||||
let docs: SmallDoc[] = [];
|
||||
let docs: SmallDoc[];
|
||||
const response = await page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().endsWith('documents/?page=1') &&
|
||||
@@ -290,7 +290,7 @@ test.describe('Documents Grid', () => {
|
||||
});
|
||||
|
||||
test('checks the infinite scroll', async ({ page }) => {
|
||||
let docs: SmallDoc[] = [];
|
||||
let docs: SmallDoc[];
|
||||
const responsePromisePage1 = page.waitForResponse((response) => {
|
||||
return (
|
||||
response.url().endsWith(`/documents/?page=1`) &&
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
mockedDocument,
|
||||
verifyDocName,
|
||||
} from './utils-common';
|
||||
import { writeInEditor } from './utils-editor';
|
||||
import {
|
||||
connectOtherUserToDoc,
|
||||
mockedAccesses,
|
||||
@@ -20,6 +21,46 @@ test.beforeEach(async ({ page }) => {
|
||||
});
|
||||
|
||||
test.describe('Doc Header', () => {
|
||||
test('toggles panel collapse from floating bar button', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
const [docTitle] = await createDoc(
|
||||
page,
|
||||
'doc-floating-bar',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
|
||||
const cardCollapse = page.locator('.--docs--left-panel-collapse-button');
|
||||
const collapseButton = cardCollapse.getByTestId(
|
||||
'floating-bar-toggle-left-panel',
|
||||
);
|
||||
await expect(collapseButton).toBeVisible();
|
||||
|
||||
// Panel open
|
||||
await expect(collapseButton).toHaveAttribute('aria-expanded', 'true');
|
||||
await expect(collapseButton.getByText(docTitle)).toBeHidden();
|
||||
|
||||
// Collapse panel
|
||||
await collapseButton.click();
|
||||
await expect(collapseButton).toHaveAttribute('aria-expanded', 'false');
|
||||
await expect(cardCollapse.getByText(docTitle)).toBeHidden();
|
||||
|
||||
// When the title is not visible in the viewport, the button should show the title
|
||||
const editor = await writeInEditor({ page, text: 'Lorem ipsum' });
|
||||
for (let i = 0; i < 25; i++) {
|
||||
await editor.press('Enter');
|
||||
}
|
||||
await writeInEditor({ page, text: 'Lorem ipsum 2' });
|
||||
await expect(cardCollapse.getByText(docTitle)).toBeVisible();
|
||||
|
||||
// Expand panel and check the title is hidden again
|
||||
await collapseButton.click();
|
||||
await expect(collapseButton).toHaveAttribute('aria-expanded', 'true');
|
||||
await expect(cardCollapse.getByText(docTitle)).toBeHidden();
|
||||
});
|
||||
|
||||
test('it checks the element are correctly displayed', async ({
|
||||
page,
|
||||
browserName,
|
||||
@@ -505,7 +546,7 @@ test.describe('Doc Header', () => {
|
||||
.click();
|
||||
|
||||
// Pin
|
||||
await page.getByText('push_pin').click();
|
||||
await page.getByRole('menuitem', { name: 'Pin' }).click();
|
||||
await page
|
||||
.getByRole('button', { name: 'Open the document options' })
|
||||
.click();
|
||||
@@ -526,11 +567,11 @@ test.describe('Doc Header', () => {
|
||||
.click();
|
||||
|
||||
// Unpin
|
||||
await page.getByText('Unpin').click();
|
||||
await page.getByRole('menuitem', { name: 'Unpin' }).click();
|
||||
await page
|
||||
.getByRole('button', { name: 'Open the document options' })
|
||||
.click();
|
||||
await expect(page.getByText('push_pin')).toBeVisible();
|
||||
await expect(page.getByRole('menuitem', { name: 'Pin' })).toBeVisible();
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
|
||||
@@ -56,14 +56,13 @@ test.describe('Footer', () => {
|
||||
|
||||
test('checks the footer is correctly overrided', async ({ page }) => {
|
||||
await overrideConfig(page, {
|
||||
FRONTEND_THEME: 'dsfr',
|
||||
theme_customization: {
|
||||
footer: {
|
||||
default: {
|
||||
logo: {
|
||||
src: '/assets/logo-gouv.svg',
|
||||
width: '220px',
|
||||
alt: 'Gouvernement Logo',
|
||||
style: { width: '220px', height: 'auto' },
|
||||
},
|
||||
externalLinks: [
|
||||
{
|
||||
|
||||
@@ -34,10 +34,15 @@ test.describe('Header', () => {
|
||||
FRONTEND_THEME: 'dsfr',
|
||||
theme_customization: {
|
||||
header: {
|
||||
logo: {
|
||||
src: '/assets/logo-gouv.svg',
|
||||
width: '220px',
|
||||
alt: 'Gouvernement Logo',
|
||||
icon: {
|
||||
src: '/assets/icon-docs-v2.svg',
|
||||
style: {
|
||||
width: '100px',
|
||||
height: 'auto',
|
||||
},
|
||||
alt: '',
|
||||
withTitle: false,
|
||||
'data-testid': 'custom-testid-docs',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -46,19 +51,11 @@ test.describe('Header', () => {
|
||||
|
||||
const header = page.locator('header').first();
|
||||
|
||||
await expect(header.getByTestId('header-icon-docs')).toBeVisible();
|
||||
await expect(header.locator('h1').getByText('Docs')).toHaveCSS(
|
||||
'font-family',
|
||||
/Marianne/i,
|
||||
await expect(header.getByTestId('custom-testid-docs')).toHaveAttribute(
|
||||
'src',
|
||||
'/assets/icon-docs-v2.svg',
|
||||
);
|
||||
|
||||
await expect(
|
||||
header.getByRole('button', {
|
||||
name: 'Logout',
|
||||
}),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(header.getByText('English')).toBeVisible();
|
||||
await expect(header.locator('h1')).toBeHidden();
|
||||
});
|
||||
|
||||
test('checks a custom waffle', async ({ page }) => {
|
||||
@@ -146,32 +143,6 @@ test.describe('Header', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Header mobile', () => {
|
||||
test.use({ viewport: { width: 500, height: 1200 } });
|
||||
|
||||
test('it checks the header when mobile with DSFR theme', async ({ page }) => {
|
||||
await overrideConfig(page, {
|
||||
FRONTEND_THEME: 'dsfr',
|
||||
theme_customization: {
|
||||
header: {
|
||||
logo: {
|
||||
src: '/assets/logo-gouv.svg',
|
||||
width: '220px',
|
||||
alt: 'Gouvernement Logo',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const header = page.locator('header').first();
|
||||
|
||||
await expect(header.getByLabel('Open the header menu')).toBeVisible();
|
||||
await expect(header.getByTestId('header-icon-docs')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Header: Log out', () => {
|
||||
test.use({ storageState: { cookies: [], origins: [] } });
|
||||
|
||||
|
||||
@@ -91,21 +91,44 @@ test.describe('Home page', () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
header: {
|
||||
logo: {
|
||||
src: '/assets/logo-gouv.svg',
|
||||
alt: 'Gouvernement Logo',
|
||||
style: { width: '110px', height: 'auto' },
|
||||
},
|
||||
icon: {
|
||||
src: '/assets/icon-docs-dsfr-v2.png',
|
||||
style: {
|
||||
width: '100px',
|
||||
height: 'auto',
|
||||
},
|
||||
alt: '',
|
||||
withTitle: false,
|
||||
},
|
||||
},
|
||||
home: {
|
||||
'with-proconnect': true,
|
||||
'icon-banner': {
|
||||
src: '/assets/icon-docs.svg',
|
||||
style: {
|
||||
width: '64px',
|
||||
height: 'auto',
|
||||
},
|
||||
alt: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await page.goto('/docs/');
|
||||
|
||||
// Wait for the page to be fully loaded and responsive store to be initialized
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Wait a bit more for the responsive store to be initialized
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check header content
|
||||
const header = page.locator('header').first();
|
||||
const footer = page.locator('footer').first();
|
||||
await expect(header).toBeVisible();
|
||||
await expect(header).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// Check for language picker - it should be visible on desktop
|
||||
// Use a more flexible selector that works with both Header and HomeHeader
|
||||
@@ -118,7 +141,6 @@ test.describe('Home page', () => {
|
||||
header.getByRole('img', { name: 'Gouvernement Logo' }),
|
||||
).toBeVisible();
|
||||
await expect(header.getByTestId('header-icon-docs')).toBeVisible();
|
||||
await expect(header.getByRole('heading', { name: 'Docs' })).toBeVisible();
|
||||
|
||||
// Check the titles
|
||||
const h2 = page.locator('h2');
|
||||
|
||||
@@ -13,6 +13,32 @@ test.describe('Language', () => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('it checks theme_customization.translations config', async ({
|
||||
page,
|
||||
}) => {
|
||||
await overrideConfig(page, {
|
||||
theme_customization: {
|
||||
translations: {
|
||||
en: {
|
||||
translation: {
|
||||
Docs: 'MyCustomDocs',
|
||||
},
|
||||
},
|
||||
},
|
||||
header: {
|
||||
logo: {},
|
||||
icon: {
|
||||
withTitle: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page.getByText('MyCustomDocs')).toBeAttached();
|
||||
});
|
||||
|
||||
test('checks language switching', async ({ page }) => {
|
||||
const header = page.locator('header').first();
|
||||
const languagePicker = header.locator('.--docs--language-picker-text');
|
||||
|
||||
@@ -3,11 +3,19 @@ import path from 'path';
|
||||
|
||||
import { Locator, Page, TestInfo, expect } from '@playwright/test';
|
||||
|
||||
import theme_customization from '../../../../../backend/impress/configuration/theme/default.json';
|
||||
|
||||
export type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
||||
export const BROWSERS: BrowserName[] = ['chromium', 'webkit', 'firefox'];
|
||||
|
||||
export const CONFIG = {
|
||||
AI_BOT: {
|
||||
name: 'Docs AI',
|
||||
color: '#8bc6ff',
|
||||
},
|
||||
AI_FEATURE_ENABLED: true,
|
||||
AI_FEATURE_BLOCKNOTE_ENABLED: true,
|
||||
AI_FEATURE_LEGACY_ENABLED: true,
|
||||
API_USERS_SEARCH_QUERY_MIN_LENGTH: 3,
|
||||
CRISP_WEBSITE_ID: null,
|
||||
COLLABORATION_WS_URL: 'ws://localhost:4444/collaboration/ws/',
|
||||
@@ -32,7 +40,7 @@ export const CONFIG = {
|
||||
POSTHOG_KEY: {},
|
||||
SENTRY_DSN: null,
|
||||
TRASHBIN_CUTOFF_DAYS: 30,
|
||||
theme_customization: {},
|
||||
theme_customization,
|
||||
} as const;
|
||||
|
||||
export const overrideConfig = async (
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { Page } from '@playwright/test';
|
||||
|
||||
export const getEditor = async ({ page }: { page: Page }) => {
|
||||
@@ -30,3 +32,55 @@ export const writeInEditor = async ({
|
||||
.fill(text);
|
||||
return editor;
|
||||
};
|
||||
|
||||
export const mockAIResponse = async (page: Page) => {
|
||||
await page.route(/.*\/ai-proxy\//, async (route) => {
|
||||
const req = route.request();
|
||||
|
||||
if (req.method() !== 'POST') {
|
||||
return route.continue();
|
||||
}
|
||||
|
||||
// Extract the block ID from the request's selectedBlocks
|
||||
const requestData = req.postDataJSON();
|
||||
const messages = requestData?.messages || [];
|
||||
const userMessage = messages.find((msg: any) => msg.role === 'user');
|
||||
const documentState = userMessage?.metadata?.documentState;
|
||||
const selectedBlocks = documentState?.selectedBlocks || [];
|
||||
const blockId = selectedBlocks[0]?.id || 'initialBlockId$';
|
||||
|
||||
const sse = [
|
||||
`data: {"type":"start"}\n\n`,
|
||||
`data: {"type":"start-step"}\n\n`,
|
||||
`data: ${JSON.stringify({
|
||||
type: 'tool-input-available',
|
||||
toolCallId: 'chatcmpl-mock-0',
|
||||
toolName: 'applyDocumentOperations',
|
||||
input: {
|
||||
operations: [
|
||||
{
|
||||
type: 'update',
|
||||
id: blockId,
|
||||
block: '<p>Bonjour le monde</p>',
|
||||
},
|
||||
],
|
||||
},
|
||||
})}\n\n`,
|
||||
`data: {"type":"finish-step"}\n\n`,
|
||||
`data: {"type":"finish","finishReason":"tool-calls"}\n\n`,
|
||||
`data: [DONE]\n\n`,
|
||||
].join('');
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream; charset=utf-8',
|
||||
'Cache-Control': 'no-cache, no-transform',
|
||||
'x-vercel-ai-data-stream': 'v1',
|
||||
'x-accel-buffering': 'no',
|
||||
Connection: 'keep-alive',
|
||||
},
|
||||
body: sse,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-e2e",
|
||||
"version": "4.5.0",
|
||||
"version": "4.7.0",
|
||||
"repository": "https://github.com/suitenumerique/docs",
|
||||
"author": "DINUM",
|
||||
"license": "MIT",
|
||||
@@ -15,7 +15,7 @@
|
||||
"test:ui::chromium": "yarn test:ui --project=chromium"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.57.0",
|
||||
"@playwright/test": "1.58.2",
|
||||
"@types/node": "*",
|
||||
"@types/pdf-parse": "1.1.5",
|
||||
"eslint-plugin-docs": "*",
|
||||
|
||||
@@ -3,7 +3,7 @@ server {
|
||||
listen 3000;
|
||||
server_name localhost;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
root /app;
|
||||
|
||||
location / {
|
||||
try_files $uri index.html $uri/index.html =404;
|
||||
|
||||
@@ -19,24 +19,6 @@ const themeWhiteLabelLight = getUIKitThemesFromGlobals(whiteLabelGlobals, {
|
||||
'2xs': '0.375rem',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
logo: {
|
||||
src: '',
|
||||
alt: '',
|
||||
widthHeader: '',
|
||||
widthFooter: '',
|
||||
},
|
||||
'home-proconnect': false,
|
||||
icon: {
|
||||
src: '/assets/icon-docs.svg',
|
||||
width: '32px',
|
||||
height: 'auto',
|
||||
},
|
||||
favicon: {
|
||||
'png-light': '/assets/favicon-light.png',
|
||||
'png-dark': '/assets/favicon-dark.png',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -56,25 +38,6 @@ const themesDSFRLight = getUIKitThemesFromGlobals(dsfrGlobals, {
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
logo: {
|
||||
src: '/assets/logo-gouv.svg',
|
||||
widthHeader: '110px',
|
||||
widthFooter: '220px',
|
||||
alt: 'Gouvernement Logo',
|
||||
},
|
||||
'home-proconnect': true,
|
||||
icon: {
|
||||
src: '/assets/icon-docs-dsfr.svg',
|
||||
width: '32px',
|
||||
height: 'auto',
|
||||
},
|
||||
favicon: {
|
||||
ico: '/assets/favicon-dsfr.ico',
|
||||
'png-light': '/assets/favicon-dsfr.png',
|
||||
'png-dark': '/assets/favicon-dark-dsfr.png',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-impress",
|
||||
"version": "4.5.0",
|
||||
"version": "4.7.0",
|
||||
"repository": "https://github.com/suitenumerique/docs",
|
||||
"author": "DINUM",
|
||||
"license": "MIT",
|
||||
@@ -19,31 +19,34 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ag-media/react-pdf-table": "2.0.3",
|
||||
"@blocknote/code-block": "0.46.2",
|
||||
"@blocknote/core": "0.46.2",
|
||||
"@blocknote/mantine": "0.46.2",
|
||||
"@blocknote/react": "0.46.2",
|
||||
"@blocknote/xl-docx-exporter": "0.46.2",
|
||||
"@blocknote/xl-multi-column": "0.46.2",
|
||||
"@blocknote/xl-odt-exporter": "0.46.2",
|
||||
"@blocknote/xl-pdf-exporter": "0.46.2",
|
||||
"@ai-sdk/openai": "3.0.19",
|
||||
"@blocknote/code-block": "0.47.1",
|
||||
"@blocknote/core": "0.47.1",
|
||||
"@blocknote/mantine": "0.47.1",
|
||||
"@blocknote/react": "0.47.1",
|
||||
"@blocknote/xl-ai": "0.47.1",
|
||||
"@blocknote/xl-docx-exporter": "0.47.1",
|
||||
"@blocknote/xl-multi-column": "0.47.1",
|
||||
"@blocknote/xl-odt-exporter": "0.47.1",
|
||||
"@blocknote/xl-pdf-exporter": "0.47.1",
|
||||
"@dnd-kit/core": "6.3.1",
|
||||
"@dnd-kit/modifiers": "9.0.0",
|
||||
"@emoji-mart/data": "1.2.1",
|
||||
"@emoji-mart/react": "1.1.1",
|
||||
"@fontsource-variable/inter": "5.2.8",
|
||||
"@fontsource-variable/material-symbols-outlined": "5.2.31",
|
||||
"@fontsource-variable/material-symbols-outlined": "5.2.35",
|
||||
"@fontsource/material-icons": "5.2.7",
|
||||
"@gouvfr-lasuite/cunningham-react": "4.1.0",
|
||||
"@gouvfr-lasuite/cunningham-react": "4.2.0",
|
||||
"@gouvfr-lasuite/integration": "1.0.3",
|
||||
"@gouvfr-lasuite/ui-kit": "0.18.7",
|
||||
"@hocuspocus/provider": "3.4.3",
|
||||
"@mantine/core": "8.3.12",
|
||||
"@mantine/hooks": "8.3.12",
|
||||
"@gouvfr-lasuite/ui-kit": "0.19.6",
|
||||
"@hocuspocus/provider": "3.4.4",
|
||||
"@mantine/core": "8.3.14",
|
||||
"@mantine/hooks": "8.3.14",
|
||||
"@react-pdf/renderer": "4.3.1",
|
||||
"@sentry/nextjs": "10.34.0",
|
||||
"@tanstack/react-query": "5.90.18",
|
||||
"@sentry/nextjs": "10.38.0",
|
||||
"@tanstack/react-query": "5.90.21",
|
||||
"@tiptap/extensions": "*",
|
||||
"ai": "6.0.49",
|
||||
"canvg": "4.0.3",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
@@ -51,56 +54,57 @@
|
||||
"emoji-datasource-apple": "16.0.0",
|
||||
"emoji-mart": "5.6.0",
|
||||
"emoji-regex": "10.6.0",
|
||||
"i18next": "25.7.4",
|
||||
"i18next-browser-languagedetector": "8.2.0",
|
||||
"i18next": "25.8.12",
|
||||
"i18next-browser-languagedetector": "8.2.1",
|
||||
"idb": "8.0.3",
|
||||
"lodash": "4.17.23",
|
||||
"luxon": "3.7.2",
|
||||
"next": "15.5.9",
|
||||
"posthog-js": "1.325.0",
|
||||
"next": "15.5.10",
|
||||
"posthog-js": "1.347.2",
|
||||
"react": "*",
|
||||
"react-aria-components": "1.14.0",
|
||||
"react-aria-components": "1.15.1",
|
||||
"react-dom": "*",
|
||||
"react-dropzone": "14.3.8",
|
||||
"react-i18next": "16.5.3",
|
||||
"react-dropzone": "15.0.0",
|
||||
"react-i18next": "16.5.4",
|
||||
"react-intersection-observer": "10.0.2",
|
||||
"react-resizable-panels": "3.0.6",
|
||||
"react-select": "5.10.2",
|
||||
"styled-components": "6.3.8",
|
||||
"styled-components": "6.3.9",
|
||||
"use-debounce": "10.1.0",
|
||||
"uuid": "13.0.0",
|
||||
"y-protocols": "1.0.7",
|
||||
"yjs": "*",
|
||||
"zustand": "5.0.10"
|
||||
"zod": "3.25.28",
|
||||
"zustand": "5.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/webpack": "8.1.0",
|
||||
"@tanstack/react-query-devtools": "5.91.2",
|
||||
"@tanstack/react-query-devtools": "5.91.3",
|
||||
"@testing-library/dom": "10.4.1",
|
||||
"@testing-library/jest-dom": "6.9.1",
|
||||
"@testing-library/react": "16.3.1",
|
||||
"@testing-library/react": "16.3.2",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/lodash": "4.17.23",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"@vitejs/plugin-react": "5.1.2",
|
||||
"@vitejs/plugin-react": "5.1.4",
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
"cross-env": "10.1.0",
|
||||
"dotenv": "17.2.3",
|
||||
"dotenv": "17.3.1",
|
||||
"eslint-plugin-docs": "*",
|
||||
"fetch-mock": "9.11.0",
|
||||
"jsdom": "27.4.0",
|
||||
"jsdom": "28.1.0",
|
||||
"node-fetch": "2.7.0",
|
||||
"prettier": "3.8.0",
|
||||
"prettier": "3.8.1",
|
||||
"stylelint": "16.26.1",
|
||||
"stylelint-config-standard": "39.0.1",
|
||||
"stylelint-prettier": "5.0.3",
|
||||
"typescript": "*",
|
||||
"vite-tsconfig-paths": "6.0.4",
|
||||
"vitest": "4.0.17",
|
||||
"webpack": "5.104.1",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "4.0.18",
|
||||
"webpack": "5.105.2",
|
||||
"workbox-webpack-plugin": "7.1.0"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22"
|
||||
|
||||
BIN
src/frontend/apps/impress/public/assets/icon-docs-dsfr-v2.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
@@ -0,0 +1,34 @@
|
||||
<svg
|
||||
width="100"
|
||||
height="40"
|
||||
viewBox="0 0 100 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M25.6305 32.8312C26.7983 32.5038 27.9166 31.9062 28.6505 30.8503C29.3749 29.8163 29.5789 28.5047 29.5789 27.2425V8.75099C29.5789 8.42358 29.5611 8.09557 29.5216 7.77148C30.1016 7.99961 30.5486 8.37658 30.8626 8.90239C31.2331 9.50024 31.4184 10.2876 31.4184 11.2643V30.0464C31.4184 31.3684 31.0942 32.3578 30.4458 33.0146C29.7974 33.6714 28.8207 33.9998 27.5155 33.9998H20.4209C20.5889 33.9704 20.7574 33.9401 20.9262 33.909C22.4067 33.6444 23.9713 33.2854 25.6185 32.8346L25.6305 32.8312Z"
|
||||
fill="#C83F49"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.58203 29.655V10.8477C8.58203 9.70251 8.88938 8.83519 9.50408 8.24575C10.1272 7.65631 10.9524 7.33212 11.9797 7.27318C13.4954 7.18055 14.9311 7.05425 16.2868 6.89425C17.6425 6.72584 18.9393 6.53217 20.1771 6.31324C21.4234 6.0943 22.6359 5.85011 23.8148 5.58065C25.0274 5.29435 25.9578 5.4375 26.6062 6.0101C27.2546 6.58269 27.5788 7.49632 27.5788 8.75099V27.2425C27.5788 28.3456 27.3893 29.1666 27.0104 29.7055C26.6315 30.2529 25.9915 30.6528 25.0905 30.9054C23.4906 31.3433 21.9833 31.6886 20.5687 31.9412C19.154 32.2022 17.7731 32.4001 16.4258 32.5348C15.0785 32.6696 13.6975 32.7748 12.2829 32.8506C11.1124 32.918 10.203 32.6738 9.5546 32.118C8.90622 31.5707 8.58203 30.7497 8.58203 29.655ZM13.2087 14.2624C15.0635 14.1444 16.7632 13.9631 18.3075 13.7183C18.6822 13.6572 19.0564 13.5936 19.4291 13.5276C19.8192 13.4585 20.1013 13.1186 20.1013 12.7234C20.1013 12.2115 19.638 11.8261 19.135 11.9119C18.846 11.9612 18.5555 12.0091 18.2635 12.0556C16.7346 12.2992 15.0452 12.48 13.1952 12.5977C12.9182 12.6156 12.6978 12.7019 12.5561 12.8739C12.4221 13.0366 12.3564 13.2323 12.3564 13.4553C12.3564 13.6821 12.433 13.8795 12.5857 14.0418L12.5878 14.0439C12.7534 14.2095 12.9637 14.2811 13.2087 14.2624ZM13.208 18.456C15.0631 18.338 16.763 18.1566 18.3075 17.9119C19.8588 17.6589 21.3936 17.3638 22.9112 17.0266C23.2191 16.9581 23.4498 16.8503 23.5652 16.683C23.6786 16.5221 23.7347 16.3376 23.7347 16.1332C23.7347 15.9026 23.6469 15.704 23.476 15.5426C23.2921 15.3689 23.0348 15.3284 22.7304 15.3911L22.7285 15.3915C21.2823 15.7194 19.794 16.0053 18.2635 16.2492C16.7346 16.4928 15.0452 16.6735 13.1952 16.7913C12.9182 16.8091 12.6978 16.8954 12.5561 17.0675C12.4228 17.2294 12.3564 17.4205 12.3564 17.6363C12.3564 17.8703 12.4321 18.0723 12.5856 18.2354L12.59 18.2396C12.755 18.3949 12.9632 18.4655 13.2055 18.4562L13.208 18.456ZM13.2085 22.6494C15.0634 22.5229 16.7631 22.3374 18.3075 22.0927C19.8589 21.8482 21.3934 21.5573 22.9112 21.22C23.2199 21.1514 23.4508 21.0391 23.566 20.8627C23.6783 20.7029 23.7347 20.5233 23.7347 20.3266C23.7347 20.0961 23.6469 19.8974 23.476 19.7361C23.2921 19.5623 23.0348 19.5218 22.7304 19.5845L22.729 19.5848C21.2827 19.9043 19.7942 20.1861 18.2635 20.43C16.7345 20.6736 15.045 20.8586 13.1949 20.9847C12.918 21.0026 12.6977 21.0889 12.5561 21.2609C12.4228 21.4228 12.3564 21.6139 12.3564 21.8297C12.3564 22.0637 12.4321 22.2658 12.5856 22.4289L12.59 22.433C12.755 22.5883 12.9632 22.6589 13.2055 22.6496L13.2085 22.6494ZM18.3075 26.257C16.7632 26.5018 15.0635 26.6831 13.2087 26.8012C12.9637 26.8198 12.7534 26.7482 12.5878 26.5826L12.5857 26.5805C12.433 26.4182 12.3564 26.2208 12.3564 25.9941C12.3564 25.771 12.4221 25.5753 12.5561 25.4126C12.6978 25.2406 12.9183 25.1543 13.1953 25.1364C15.0453 25.0187 16.7346 24.838 18.2635 24.5943C18.5555 24.5478 18.846 24.4999 19.135 24.4506C19.638 24.3648 20.1013 24.7503 20.1013 25.2621C20.1013 25.6573 19.8192 25.9972 19.4291 26.0663C19.0564 26.1323 18.6822 26.1959 18.3075 26.257Z"
|
||||
fill="#2845C1"
|
||||
/>
|
||||
<path
|
||||
d="M41.2 27.95C41.0895 27.95 41 27.8605 41 27.75V12.2C41 12.0895 41.0895 12 41.2 12H47.2205C48.4813 12 49.6282 12.2127 50.6611 12.638C51.6941 13.0633 52.5827 13.6482 53.3271 14.3925C54.0866 15.1216 54.6638 15.9647 55.0588 16.9217C55.4689 17.8787 55.674 18.8965 55.674 19.975C55.674 21.0535 55.4689 22.0713 55.0588 23.0283C54.6638 23.9853 54.0866 24.836 53.3271 25.5803C52.5827 26.3094 51.6941 26.8867 50.6611 27.312C49.6282 27.7373 48.4813 27.95 47.2205 27.95H41.2ZM47.2433 14.5292H44.0026C43.8922 14.5292 43.8026 14.6188 43.8026 14.7292V25.2208C43.8026 25.3312 43.8922 25.4208 44.0026 25.4208H47.2433C48.0484 25.4208 48.7851 25.2841 49.4535 25.0106C50.1371 24.722 50.7219 24.3271 51.208 23.8258C51.7093 23.3245 52.0966 22.7473 52.3701 22.0941C52.6435 21.4409 52.7802 20.7345 52.7802 19.975C52.7802 19.2155 52.6435 18.5091 52.3701 17.8559C52.0966 17.1875 51.7093 16.6103 51.208 16.1242C50.7219 15.6229 50.1371 15.2356 49.4535 14.9621C48.7851 14.6735 48.0484 14.5292 47.2433 14.5292Z"
|
||||
fill="#2845C1"
|
||||
/>
|
||||
<path
|
||||
d="M63.3939 25.6031C63.9104 25.6031 64.3889 25.5044 64.8294 25.3069C65.2699 25.0943 65.6497 24.8132 65.9687 24.4639C66.3029 24.1145 66.5611 23.7119 66.7434 23.2562C66.9257 22.7853 67.0168 22.284 67.0168 21.7524C67.0168 21.0232 66.8573 20.37 66.5383 19.7928C66.2193 19.2155 65.7864 18.7598 65.2395 18.4256C64.6927 18.0763 64.0775 17.9016 63.3939 17.9016C62.8622 17.9016 62.3686 18.0003 61.9128 18.1978C61.4723 18.3953 61.085 18.6687 60.7508 19.0181C60.4318 19.3674 60.1811 19.7776 59.9988 20.2485C59.8166 20.7194 59.7254 21.2207 59.7254 21.7524C59.7254 22.4663 59.8849 23.1195 60.2039 23.7119C60.5229 24.2892 60.9558 24.7525 61.5027 25.1019C62.0647 25.436 62.6951 25.6031 63.3939 25.6031ZM63.3711 15.5546C64.2977 15.5546 65.1408 15.7141 65.9003 16.0331C66.675 16.3521 67.3358 16.8003 67.8827 17.3775C68.4447 17.9395 68.8701 18.6003 69.1587 19.3599C69.4625 20.1042 69.6144 20.9017 69.6144 21.7524C69.6144 22.603 69.4625 23.4081 69.1587 24.1676C68.8701 24.912 68.4447 25.5728 67.8827 26.15C67.3358 26.712 66.675 27.1526 65.9003 27.4716C65.1408 27.7906 64.2977 27.9501 63.3711 27.9501C62.4445 27.9501 61.5938 27.7906 60.8191 27.4716C60.0596 27.1526 59.3988 26.712 58.8368 26.15C58.2747 25.5728 57.8418 24.912 57.538 24.1676C57.2494 23.4081 57.1051 22.603 57.1051 21.7524C57.1051 20.9017 57.2494 20.1042 57.538 19.3599C57.8418 18.6003 58.2747 17.9395 58.8368 17.3775C59.3988 16.8003 60.0596 16.3521 60.8191 16.0331C61.5938 15.7141 62.4445 15.5546 63.3711 15.5546Z"
|
||||
fill="#2845C1"
|
||||
/>
|
||||
<path
|
||||
d="M77.2852 15.5546C78.3333 15.5546 79.2675 15.7673 80.0878 16.1926C80.8611 16.5723 81.5043 17.082 82.0173 17.7219C82.086 17.8076 82.0681 17.9322 81.9806 17.9986L80.3269 19.2531C80.2346 19.3232 80.1027 19.2999 80.0326 19.2076C79.7582 18.8466 79.4045 18.548 78.9713 18.3117C78.4852 18.0383 77.9156 17.9016 77.2624 17.9016C76.7307 17.9016 76.237 18.0003 75.7813 18.1978C75.3408 18.3953 74.961 18.6687 74.642 19.0181C74.323 19.3674 74.0724 19.7776 73.8901 20.2485C73.7078 20.7042 73.6167 21.2055 73.6167 21.7524C73.6167 22.4815 73.7762 23.1423 74.0952 23.7347C74.4142 24.3119 74.8471 24.7677 75.394 25.1019C75.9408 25.436 76.5788 25.6031 77.308 25.6031C77.9308 25.6031 78.4852 25.4664 78.9713 25.193C79.4045 24.9567 79.7582 24.6581 80.0326 24.2971C80.1027 24.2048 80.2346 24.1815 80.3269 24.2516L81.979 25.5049C82.0671 25.5717 82.0845 25.6976 82.0145 25.7831C81.5019 26.4095 80.8597 26.9191 80.0878 27.3121C79.2675 27.7374 78.3333 27.9501 77.2852 27.9501C75.994 27.9501 74.8775 27.669 73.9357 27.107C72.9939 26.5298 72.2647 25.7702 71.7483 24.8284C71.247 23.8866 70.9963 22.8613 70.9963 21.7524C70.9963 20.9169 71.1406 20.127 71.4293 19.3826C71.7179 18.6383 72.1356 17.9775 72.6825 17.4003C73.2293 16.823 73.8901 16.3749 74.6648 16.0559C75.4395 15.7217 76.313 15.5546 77.2852 15.5546Z"
|
||||
fill="#2845C1"
|
||||
/>
|
||||
<path
|
||||
d="M85.9175 18.9041C85.9175 19.2687 86.0466 19.5725 86.3049 19.8156C86.5783 20.0434 86.9277 20.2409 87.353 20.408C87.7783 20.5751 88.2265 20.7574 88.6974 20.9549C89.1835 21.1371 89.6392 21.365 90.0645 21.6384C90.4898 21.8967 90.8316 22.246 91.0899 22.6866C91.3633 23.1119 91.5 23.6512 91.5 24.3044C91.5 25.0791 91.3101 25.7399 90.9304 26.2867C90.5658 26.8184 90.0797 27.2285 89.4721 27.5171C88.8644 27.8058 88.1961 27.9501 87.4669 27.9501C86.4795 27.9501 85.6213 27.7678 84.8921 27.4032C84.2203 27.0533 83.6259 26.5938 83.1088 26.0246C83.0352 25.9436 83.0442 25.8185 83.1263 25.7462L84.5297 24.5112C84.6154 24.4358 84.7464 24.4475 84.821 24.534C85.1512 24.917 85.5168 25.2354 85.9175 25.4892C86.3732 25.7778 86.8821 25.9221 87.4441 25.9221C87.9758 25.9221 88.3784 25.7854 88.6518 25.512C88.9404 25.2234 89.0847 24.874 89.0847 24.4639C89.0847 24.0841 88.948 23.7803 88.6746 23.5524C88.4163 23.3246 88.0745 23.1271 87.6492 22.96C87.2239 22.7929 86.7682 22.6182 86.2821 22.4359C85.8112 22.2384 85.363 22.003 84.9377 21.7296C84.5124 21.4561 84.163 21.1068 83.8896 20.6814C83.6313 20.2409 83.5022 19.6789 83.5022 18.9953C83.5022 18.3573 83.6693 17.78 84.0035 17.2636C84.3377 16.7319 84.801 16.3142 85.3934 16.0104C86.001 15.7065 86.6922 15.5546 87.4669 15.5546C88.3024 15.5546 89.0771 15.7369 89.7911 16.1015C90.4556 16.4338 90.9877 16.8481 91.3872 17.3444C91.4528 17.4258 91.4385 17.5439 91.359 17.6117L89.9551 18.8097C89.8666 18.8853 89.7328 18.8692 89.6595 18.7789C89.4001 18.4596 89.1022 18.19 88.7657 17.9699C88.3708 17.7117 87.9378 17.5826 87.4669 17.5826C87.1327 17.5826 86.8441 17.6433 86.6011 17.7649C86.3732 17.8864 86.1985 18.0459 86.077 18.2434C85.9707 18.4408 85.9175 18.6611 85.9175 18.9041Z"
|
||||
fill="#2845C1"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.1 KiB |
@@ -1,248 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 311.6 186.5"
|
||||
style="enable-background: new 0 0 311.6 186.5"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<style type="text/css">
|
||||
.st0 {
|
||||
fill: #ffffff;
|
||||
}
|
||||
.st1 {
|
||||
fill: #000091;
|
||||
}
|
||||
.st2 {
|
||||
fill: #e1000f;
|
||||
}
|
||||
.st3 {
|
||||
fill: #9c9b9b;
|
||||
}
|
||||
</style>
|
||||
<g id="Fond">
|
||||
<rect x="0" class="st0" width="311.6" height="186.5" />
|
||||
</g>
|
||||
<g id="Calque_1">
|
||||
<path
|
||||
id="Devise_Républicaine_1_"
|
||||
d="M100.5,150.8c0.6,0,1.1,0.4,0.8,1.5l-2.7,0.6C99.1,151.8,99.9,150.8,100.5,150.8 M102,155.2
|
||||
h-0.5c-0.7,0.8-1.4,1.4-2.1,1.4c-0.7,0-1.1-0.4-1.1-1.4c0-0.4,0-0.8,0.1-1.2l4.3-1.4c0.8-2-0.2-2.9-1.4-2.9c-2,0-4.4,3.4-4.4,6.4
|
||||
c0,1.3,0.6,2.1,1.6,2.1C99.8,158.2,101,157,102,155.2 M101.2,148.9l3-2.8v-0.3h-1.6l-1.9,3.2H101.2z M91.9,150.9h1.4l-2.3,6.2
|
||||
c-0.2,0.5,0.1,1.1,0.6,1.1c1.3,0,3.4-1.3,4.1-3h-0.4c-0.6,0.6-1.7,1.4-2.6,1.6l2.1-5.8h2.1l0.3-0.9h-2.1l0.8-2.2h-0.8l-1.5,2.2
|
||||
l-1.8,0.2V150.9z M89.9,150.6c0.2-0.6-0.2-0.9-0.5-0.9c-1.2,0-2.7,1.1-3.3,2.7h0.4c0.4-0.6,1.1-1.2,1.7-1.3l-2.4,6.2
|
||||
c-0.2,0.6,0.2,0.9,0.5,0.9c1.2,0,2.6-1.1,3.2-2.7h-0.4c-0.4,0.6-1.1,1.2-1.7,1.3L89.9,150.6z M90.4,147.5c0.6,0,1-0.5,1-1
|
||||
c0-0.6-0.5-1-1-1c-0.6,0-1,0.5-1,1C89.3,147,89.8,147.5,90.4,147.5 M76.6,157c-0.3,0.7,0,1.2,0.7,1.2c0.4,0,0.6-0.1,0.8-0.6
|
||||
l1.7-4.4c0.8-0.9,2.3-1.9,3-1.9c0.5,0,0.4,0.4,0.1,0.9l-2.5,4.9c-0.2,0.5,0.1,1.1,0.6,1.1c1.2,0,2.7-1.1,3.3-2.7h-0.4
|
||||
c-0.4,0.6-1.1,1.2-1.7,1.3l2.2-4.4c0.3-0.6,0.4-1.1,0.4-1.5c0-0.7-0.4-1.2-1.2-1.2c-1.1,0-2.2,1.2-3.5,2.7v-1.2
|
||||
c0-0.8-0.3-1.6-1-1.6c-0.5,0-0.9,0.4-1.3,1v0.2c0.8,0,1.2,1.2,0.6,2.4L76.6,157z M76.6,151.6c0.3-1,0.1-1.9-0.6-1.9
|
||||
c-0.9,0-1.2,0.7-2.1,2.7v-1.2c0-0.8-0.3-1.6-1-1.6c-0.9,0-1.7,1.4-2.3,2.7H71c0.4-0.6,0.8-1,1.1-1c0.4,0,0.6,0.6,0,1.9l-1.7,3.7
|
||||
c-0.3,0.7,0,1.2,0.7,1.2c0.4,0,0.6-0.1,0.8-0.6l1.7-4.4c0.5-0.6,0.9-1.1,1.4-1.6H76.6z M67,150.8c0.6,0,1.1,0.4,0.8,1.5l-2.7,0.6
|
||||
C65.6,151.8,66.4,150.8,67,150.8 M68.5,155.2H68c-0.7,0.8-1.4,1.4-2.1,1.4c-0.7,0-1.1-0.4-1.1-1.4c0-0.4,0-0.8,0.1-1.2l4.3-1.4
|
||||
c0.8-2-0.2-2.9-1.4-2.9c-2,0-4.4,3.4-4.4,6.4c0,1.3,0.6,2.1,1.6,2.1C66.3,158.2,67.5,157,68.5,155.2 M58.3,150.9h1.4l-2.3,6.2
|
||||
c-0.2,0.5,0.1,1.1,0.6,1.1c1.3,0,3.4-1.3,4.1-3h-0.4c-0.6,0.6-1.7,1.4-2.6,1.6l2.1-5.8h2.1l0.3-0.9h-2.1l0.8-2.2h-0.8l-1.5,2.2
|
||||
l-1.8,0.2V150.9z M50.5,155.7c0-1.9,2.1-4.5,3.3-4.5c0.3,0,0.5,0,0.7,0.1l-1.2,3.3c-0.7,0.9-1.8,1.9-2.3,1.9
|
||||
C50.7,156.6,50.5,156.3,50.5,155.7 M57,149.3l-0.7,0l-0.7,0.7h-0.2c-3.6,0-6.6,4-6.6,6.9c0,0.8,0.5,1.3,1.3,1.3
|
||||
c0.9,0,1.8-1.3,2.8-2.7l0,0.5c-0.1,1.4,0.3,2.2,1.1,2.2c0.9,0,1.7-1.4,2.3-2.7h-0.4c-0.4,0.6-0.8,1-1.1,1c-0.3,0-0.6-0.6,0-1.9
|
||||
L57,149.3z M49.5,151.6c0.3-1,0.1-1.9-0.6-1.9c-0.9,0-1.2,0.7-2.1,2.7v-1.2c0-0.8-0.3-1.6-1-1.6c-0.9,0-1.7,1.4-2.3,2.7h0.4
|
||||
c0.4-0.6,0.8-1,1.1-1c0.4,0,0.6,0.6,0,1.9l-1.7,3.7c-0.3,0.7,0,1.2,0.7,1.2c0.4,0,0.6-0.1,0.8-0.6l1.7-4.4c0.5-0.6,0.9-1.1,1.4-1.6
|
||||
H49.5z M37.5,157.9l0.2-0.5c-2.1-0.4-2.3-0.4-1.5-2.6l0.8-2.3h2.3c1,0,1,0.4,0.9,1.5h0.6l1.4-3.8h-0.6c-0.5,0.9-0.9,1.5-2,1.5h-2.3
|
||||
l1.2-3.3c0.4-1,0.6-1.3,2-1.3h1c1.4,0,1.6,0.4,1.6,1.9h0.6l0.5-2.6h-8.6l-0.2,0.5c1.7,0.3,1.8,0.5,1,2.6l-1.9,5.1
|
||||
c-0.8,2.1-1.1,2.3-3,2.6l-0.1,0.5H37.5z M79.7,131.4c0.6,0,1.1,0.4,0.8,1.5l-2.7,0.6C78.3,132.3,79.1,131.4,79.7,131.4 M81.2,135.7
|
||||
h-0.5c-0.7,0.8-1.4,1.4-2.1,1.4c-0.7,0-1.1-0.4-1.1-1.4c0-0.4,0-0.8,0.1-1.2l4.3-1.4c0.8-2-0.2-2.9-1.4-2.9c-2,0-4.4,3.4-4.4,6.4
|
||||
c0,1.3,0.6,2.1,1.6,2.1C79,138.7,80.2,137.6,81.2,135.7 M80.3,129.4l3-2.8v-0.3h-1.6l-1.9,3.2H80.3z M71,131.4h1.4l-2.3,6.2
|
||||
c-0.2,0.5,0.1,1.1,0.6,1.1c1.3,0,3.4-1.3,4.1-3h-0.4c-0.6,0.6-1.7,1.4-2.6,1.6l2.1-5.8h2.1l0.3-0.9h-2.1l0.8-2.2h-0.8l-1.5,2.2
|
||||
l-1.8,0.2V131.4z M69.1,131.1c0.2-0.6-0.2-0.9-0.5-0.9c-1.2,0-2.7,1.1-3.3,2.7h0.4c0.4-0.6,1.1-1.2,1.7-1.3l-2.4,6.2
|
||||
c-0.2,0.6,0.2,0.9,0.5,0.9c1.2,0,2.6-1.1,3.2-2.7h-0.4c-0.4,0.6-1.1,1.2-1.7,1.3L69.1,131.1z M69.5,128c0.6,0,1-0.5,1-1
|
||||
c0-0.6-0.5-1-1-1c-0.6,0-1,0.5-1,1C68.5,127.6,68.9,128,69.5,128 M61.2,137.3l4.3-11.4l-0.1-0.2l-2.7,0.3v0.3l0.5,0.4
|
||||
c0.5,0.4,0.3,0.7-0.1,1.9l-3.4,8.9c-0.2,0.5,0.1,1.1,0.6,1.1c1.2,0,2.6-1.1,3.2-2.7h-0.4C62.7,136.6,61.8,137.2,61.2,137.3
|
||||
M53,136.3c0-1.9,2.1-4.5,3.3-4.5c0.3,0,0.5,0,0.7,0.1l-1.2,3.3c-0.7,0.9-1.8,1.9-2.3,1.9C53.1,137.1,53,136.8,53,136.3
|
||||
M59.5,129.9l-0.7,0l-0.7,0.7H58c-3.6,0-6.6,4-6.6,6.9c0,0.8,0.5,1.3,1.3,1.3c0.9,0,1.8-1.3,2.8-2.7l0,0.5
|
||||
c-0.1,1.4,0.3,2.2,1.1,2.2c0.9,0,1.7-1.4,2.3-2.7h-0.4c-0.4,0.6-0.8,1-1.1,1c-0.3,0-0.6-0.6,0-1.9L59.5,129.9z M43.7,140.2
|
||||
c0-0.8,0.8-1.3,1.9-1.8c0.4,0.2,0.9,0.4,1.7,0.6c1.2,0.4,1.6,0.5,1.6,0.9c0,0.8-1.3,1.3-3.1,1.3C44.4,141.3,43.7,141,43.7,140.2
|
||||
M46.9,135.2c-0.5,0-0.7-0.4-0.7-0.9c0-1.4,0.7-3.4,1.9-3.4c0.5,0,0.7,0.4,0.7,0.9C48.8,133.1,48,135.2,46.9,135.2 M50.3,139.4
|
||||
c0-1-0.9-1.4-2.3-1.8c-1.2-0.4-1.8-0.5-1.8-0.9c0-0.3,0.3-0.7,0.8-1c2-0.1,3.4-1.9,3.4-3.5c0-0.3-0.1-0.6-0.2-0.9h1.7l0.3-0.9h-2.7
|
||||
c-0.3-0.2-0.7-0.3-1.1-0.3c-2.2,0-3.6,1.9-3.6,3.4c0,1.2,0.7,1.9,1.7,2.1c-1,0.5-1.6,1-1.6,1.6c0,0.4,0.1,0.6,0.4,0.8
|
||||
c-2.3,0.7-3.3,1.5-3.3,2.6c0,1.1,1.4,1.5,3.1,1.5C47.9,142.3,50.3,140.7,50.3,139.4 M39.4,132.8c1,0,1,0.4,0.9,1.5h0.6l1.4-3.8
|
||||
h-0.6c-0.5,0.9-0.9,1.5-2,1.5h-2.3l1.1-3.1c0.4-1,0.6-1.2,2-1.2h1c1.4,0,1.6,0.4,1.6,1.8h0.6l0.5-2.6h-8.6l-0.2,0.5
|
||||
c1.7,0.3,1.8,0.5,1,2.6l-1.9,5.1c-0.8,2.1-1.1,2.3-3,2.6l-0.1,0.5H41l1.7-2.7H42c-1.1,1-2.5,2-4.4,2c-2.5,0-2.3-0.1-1.5-2.4
|
||||
l0.9-2.5H39.4z M40.6,126.2l3-2.1v-0.3h-1.8l-1.7,2.4H40.6z M78.7,111.9c0.6,0,1.1,0.4,0.8,1.5l-2.7,0.6
|
||||
C77.3,112.8,78,111.9,78.7,111.9 M80.2,116.2h-0.5c-0.7,0.8-1.4,1.4-2.1,1.4c-0.7,0-1.1-0.4-1.1-1.4c0-0.4,0-0.8,0.1-1.2l4.3-1.4
|
||||
c0.8-2-0.2-2.9-1.4-2.9c-2,0-4.4,3.4-4.4,6.4c0,1.3,0.6,2.1,1.6,2.1C77.9,119.2,79.2,118.1,80.2,116.2 M79.3,110l3-2.8v-0.3h-1.6
|
||||
l-1.9,3.2H79.3z M70.4,111.9h1.1l-2.3,6.2c-0.2,0.5,0.1,1.1,0.6,1.1c1.3,0,3.4-1.3,4.1-3h-0.4c-0.6,0.6-1.7,1.4-2.6,1.6l2.1-5.8
|
||||
h2.1l0.3-0.9h-2.1l0.8-2.2h-0.8l-1.5,2.2l-1.5,0.2V111.9z M69.2,112.6c0.3-1,0.1-1.9-0.6-1.9c-0.9,0-1.2,0.7-2.1,2.7v-1.2
|
||||
c0-0.8-0.3-1.6-1-1.6c-0.9,0-1.7,1.4-2.3,2.7h0.4c0.4-0.6,0.8-1,1.1-1c0.4,0,0.6,0.6,0,1.9l-1.7,3.7c-0.3,0.7,0,1.2,0.7,1.2
|
||||
c0.4,0,0.6-0.1,0.8-0.6l1.7-4.4c0.5-0.6,0.9-1.1,1.4-1.6H69.2z M59.7,111.9c0.6,0,1.1,0.4,0.8,1.5l-2.7,0.6
|
||||
C58.3,112.8,59.1,111.9,59.7,111.9 M61.2,116.2h-0.5c-0.7,0.8-1.4,1.4-2.1,1.4c-0.7,0-1.1-0.4-1.1-1.4c0-0.4,0-0.8,0.1-1.2l4.3-1.4
|
||||
c0.8-2-0.2-2.9-1.4-2.9c-2,0-4.4,3.4-4.4,6.4c0,1.3,0.6,2.1,1.6,2.1C59,119.2,60.2,118.1,61.2,116.2 M50.6,118c-0.4,0-1-0.4-1-0.7
|
||||
c0-0.1,0.2-0.6,0.4-1.2l0.7-1.9c0.7-0.9,1.9-1.8,2.5-1.8c0.4,0,0.7,0.2,0.7,0.8C53.9,114.9,52.3,118,50.6,118 M55.5,112.5
|
||||
c0-1.3-0.5-1.7-1.3-1.7c-1.1,0-2.1,1.2-3.1,2.6l2.6-6.8l-0.1-0.2l-2.7,0.3v0.3l0.5,0.4c0.5,0.4,0.3,0.8-0.1,1.9l-2.8,7.2
|
||||
c-0.2,0.5-0.5,1.2-0.5,1.3c0,0.7,1,1.4,1.9,1.4C52,119.2,55.5,115.4,55.5,112.5 M47,111.6c0.2-0.6-0.2-0.9-0.5-0.9
|
||||
c-1.2,0-2.7,1.1-3.3,2.7h0.4c0.4-0.6,1.1-1.2,1.7-1.3l-2.4,6.2c-0.2,0.6,0.2,0.9,0.5,0.9c1.2,0,2.6-1.1,3.2-2.7h-0.4
|
||||
c-0.4,0.6-1.1,1.2-1.7,1.3L47,111.6z M47.5,108.5c0.6,0,1-0.5,1-1c0-0.6-0.5-1-1-1c-0.6,0-1,0.5-1,1
|
||||
C46.4,108.1,46.9,108.5,47.5,108.5 M41.2,107.5h-5.7l-0.2,0.5c1.7,0.3,1.8,0.5,1,2.6l-1.8,5.1c-0.8,2.1-1.1,2.3-3,2.6l-0.1,0.5H40
|
||||
l1.9-3.3h-0.7c-1.1,1.2-2.3,2.6-4.2,2.6c-1.4,0-1.6-0.2-0.8-2.4l1.8-5.1c0.8-2.1,1.1-2.3,3-2.6L41.2,107.5z"
|
||||
/>
|
||||
<path
|
||||
d="M40.9,88.2c1,0,1.9-0.2,2.7-0.5c0.8-0.3,1.5-0.8,2.1-1.3v-3.5h-5.3v-3.8h9.4v8.8c-1,1.3-2.2,2.3-3.8,3.1s-3.3,1.2-5.2,1.2
|
||||
c-1.7,0-3.2-0.3-4.6-0.9c-1.4-0.6-2.6-1.4-3.6-2.4c-1-1-1.8-2.1-2.3-3.5c-0.6-1.3-0.8-2.7-0.8-4.2c0-1.5,0.3-2.9,0.8-4.2
|
||||
c0.5-1.3,1.3-2.5,2.2-3.5c1-1,2.1-1.8,3.5-2.4s2.8-0.9,4.5-0.9c1.9,0,3.5,0.4,5,1.2c1.5,0.8,2.7,1.8,3.7,3L46,77
|
||||
c-0.6-0.8-1.3-1.5-2.3-2.1s-2-0.8-3.1-0.8c-1,0-1.9,0.2-2.7,0.5c-0.8,0.4-1.5,0.9-2.1,1.5c-0.6,0.6-1,1.4-1.4,2.2
|
||||
c-0.3,0.9-0.5,1.8-0.5,2.8s0.2,1.9,0.5,2.8c0.3,0.9,0.8,1.6,1.4,2.2c0.6,0.6,1.4,1.1,2.2,1.5C39,88,39.9,88.2,40.9,88.2z M64,70.3
|
||||
c1.6,0,3.1,0.3,4.4,0.9s2.5,1.4,3.5,2.4c1,1,1.7,2.1,2.2,3.5c0.5,1.3,0.8,2.7,0.8,4.2c0,1.5-0.3,2.9-0.8,4.2
|
||||
c-0.5,1.3-1.3,2.5-2.2,3.5c-1,1-2.1,1.8-3.5,2.4s-2.8,0.9-4.4,0.9c-1.6,0-3.1-0.3-4.5-0.9s-2.5-1.4-3.5-2.4c-1-1-1.7-2.1-2.2-3.5
|
||||
c-0.5-1.3-0.8-2.7-0.8-4.2c0-1.5,0.3-2.9,0.8-4.2c0.5-1.3,1.3-2.5,2.2-3.5c1-1,2.1-1.8,3.5-2.4S62.4,70.3,64,70.3z M64,88.2
|
||||
c1,0,1.9-0.2,2.7-0.5c0.8-0.4,1.5-0.9,2.1-1.5c0.6-0.6,1-1.4,1.4-2.2s0.5-1.8,0.5-2.8s-0.2-1.9-0.5-2.8c-0.3-0.9-0.8-1.6-1.4-2.2
|
||||
c-0.6-0.6-1.3-1.1-2.1-1.5c-0.8-0.4-1.7-0.5-2.7-0.5c-1,0-1.9,0.2-2.7,0.5c-0.8,0.4-1.5,0.9-2.1,1.5c-0.6,0.6-1,1.4-1.4,2.2
|
||||
c-0.3,0.9-0.5,1.8-0.5,2.8s0.2,1.9,0.5,2.8c0.3,0.9,0.8,1.6,1.4,2.2c0.6,0.6,1.3,1.1,2.1,1.5C62.1,88,63,88.2,64,88.2z M91.1,83.8
|
||||
V70.9h4.2v12.6c0,2.7-0.8,4.8-2.3,6.4c-1.5,1.5-3.5,2.3-6.1,2.3c-2.6,0-4.6-0.8-6.1-2.3s-2.2-3.7-2.2-6.4V70.9h4.2v12.9
|
||||
c0,1.4,0.4,2.5,1.1,3.2c0.7,0.8,1.8,1.2,3.1,1.2c1.3,0,2.3-0.4,3-1.2C90.8,86.3,91.1,85.2,91.1,83.8z M97.9,70.9h4.5l6.1,16.1
|
||||
l6.1-16.1h4.5l-7.8,20.7h-5.5L97.9,70.9z M122.2,91.5V70.9h12v3.6h-7.8v4.8h6.7v3.6h-6.7V88h7.8v3.6H122.2z M139.1,91.5V70.9h6.3
|
||||
c2.3,0,4.1,0.6,5.4,1.7c1.3,1.1,2,2.6,2,4.5c0,1.2-0.3,2.3-0.9,3.2c-0.6,0.9-1.4,1.6-2.4,2.1l6.5,9.1h-5l-5.5-8.3h-2.2v8.3H139.1z
|
||||
M145.7,74.4h-2.4v5.2h2.4c0.9,0,1.6-0.2,2.1-0.7c0.5-0.5,0.7-1.1,0.7-1.9c0-0.8-0.2-1.4-0.7-1.9C147.2,74.7,146.6,74.4,145.7,74.4
|
||||
z M158.6,91.5V70.9h5.4l9.2,14.8V70.9h4.2v20.7H172l-9.2-14.8v14.8H158.6z M183.1,91.5V70.9h12v3.6h-7.8v4.8h6.7v3.6h-6.7V88h7.8
|
||||
v3.6H183.1z M200,91.5V70.9h5.3l5,8.5l5-8.5h5.3v20.7h-4.2V76.8l-4.6,7.6h-3l-4.6-7.6v14.7H200z M226.3,91.5V70.9h12v3.6h-7.8v4.8
|
||||
h6.7v3.6h-6.7V88h7.8v3.6H226.3z M243.2,91.5V70.9h5.4l9.2,14.8V70.9h4.2v20.7h-5.4l-9.2-14.8v14.8H243.2z M265.7,74.7v-3.8h16.9
|
||||
v3.8h-6.4v16.8H272V74.7H265.7z"
|
||||
/>
|
||||
<g id="Marianne">
|
||||
<path
|
||||
id="Fond_2_"
|
||||
class="st0"
|
||||
d="M63.6,53.4c0.3-0.3,0.6-0.6,0.9-1h0c0.6-0.6,1.1-1.3,1.8-1.8c0.2-0.2,0.4-0.3,0.6-0.5
|
||||
c0.1-0.1,0.1-0.2,0.1-0.2c-0.3,0.1-0.4,0.3-0.7,0.4c-0.1,0-0.1-0.1-0.1-0.1c0.2-0.1,0.4-0.3,0.6-0.4c0,0,0,0,0,0
|
||||
c-0.1,0-0.1-0.1-0.1-0.1c-0.7-0.1-1.2,0.4-1.7,0.8c-0.1,0.1-0.2-0.1-0.3-0.1c-0.8,0.3-1.4,1-2.2,1.3v-0.1c-0.3,0.1-0.6,0.3-1,0.4
|
||||
c-0.5,0.1-0.9,0.1-1.3,0.1c-0.6,0.1-1.3,0.2-1.9,0.3c0,0,0,0-0.1,0c-0.3,0.1-0.7,0.2-1,0.4c0,0,0,0,0,0c0,0-0.1,0.1-0.1,0.1
|
||||
c-0.1,0.1-0.2,0.2-0.4,0.3c-0.3,0.2-0.6,0.5-0.9,0.7c0,0-0.1,0-0.1,0c-0.3,0.3-0.6,0.6-0.9,0.8c0,0-0.1,0-0.2,0c0,0,0,0,0,0
|
||||
c0,0,0,0,0-0.1c0-0.1,0.1-0.2,0.1-0.2c0.1-0.1,0.1-0.2,0.2-0.2c0.1-0.1,0.1-0.2,0.2-0.3c0,0,0-0.1,0-0.1c0,0,0,0-0.1,0
|
||||
c0.3-0.3,0.6-0.5,0.9-0.7v0c0,0-0.1,0-0.1-0.1c0,0,0.1-0.1,0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0.1-0.2,0.1-0.3,0.2
|
||||
c-0.1,0.1-0.2,0.4-0.4,0.3c0,0-0.1,0-0.1,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
|
||||
c0,0,0,0,0,0c0,0,0,0,0-0.1c0,0,0,0,0,0c0,0,0-0.1,0.1-0.1c0,0,0,0,0-0.1c0,0,0-0.1,0.1-0.1c0,0,0-0.1,0-0.1
|
||||
c0.1-0.1,0.2-0.2,0.4-0.3h0c0.2-0.1,0.4-0.2,0.6-0.3c0,0,0.1-0.1,0.1-0.1c-0.3,0.1-0.6,0.2-0.9,0.4c0,0-0.1,0-0.1,0
|
||||
c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0.1-0.1,0.2-0.2,0.3-0.3c0.1,0,0.1,0,0.1,0.1c1.7-1.3,4.1-1,6.1-1.7c0.2-0.1,0.3-0.2,0.5-0.3
|
||||
c0.3-0.1,0.5-0.4,0.8-0.5c0.4-0.3,0.7-0.7,0.9-1.2c0-0.1-0.1-0.1-0.1-0.1c-0.7,0.8-1.5,1.3-2.4,1.8c-1.1,0.6-2.4,0.5-3.5,0.6
|
||||
c0.1-0.1,0.2-0.1,0.3-0.1c0-0.2,0.1-0.2,0.2-0.3H59c0.1,0,0.1-0.1,0.1-0.1c0.1,0,0.3-0.1,0.2-0.1c-0.2-0.2-0.5,0.2-0.8,0
|
||||
c0.1-0.1,0.1-0.3,0.2-0.3h0.2c0-0.1,0.1-0.2,0.1-0.2c0.8-0.5,1.6-0.9,2.3-1.3c-0.2,0-0.3,0.2-0.4,0.1c0.1,0,0-0.2,0.1-0.2
|
||||
c0.6-0.2,1.1-0.5,1.7-0.7c-0.2,0-0.4,0.2-0.6,0c0.1-0.1,0.2-0.2,0.3-0.2v-0.2c0-0.1,0.1-0.1,0.1-0.1c-0.1,0-0.1-0.1-0.1-0.1
|
||||
c0.1-0.1,0.2-0.1,0.3-0.2c-0.1,0-0.2,0-0.2-0.1c0.2-0.2,0.4-0.3,0.7-0.3c-0.1-0.1-0.2,0-0.2-0.1c0-0.1,0.1-0.1,0.1-0.1H63
|
||||
c-0.1-0.1-0.1-0.2-0.1-0.2c0.3-0.4,0.3-0.9,0.5-1.3c-0.1,0-0.1,0-0.1-0.1c-0.5,0.6-1.4,0.8-2.2,1h-0.3c-0.3,0.1-0.6,0.1-0.9-0.1
|
||||
c-0.2-0.1-0.3-0.3-0.5-0.4c-0.4-0.3-0.9-0.5-1.3-0.6c-1.3-0.4-2.7-0.6-4.1-0.6c0.6-0.3,1.2-0.3,1.9-0.5c0.9-0.3,1.8-0.6,2.7-0.5
|
||||
c-0.2-0.1-0.4,0-0.5,0c-0.8-0.1-1.5,0.2-2.3,0.3c-0.5,0.1-1,0.3-1.6,0.4c-0.3,0.1-0.5,0.4-0.9,0.4v-0.2c0.5-0.6,1.2-1.3,2-1.3
|
||||
c1-0.2,1.9,0,2.8,0.1c0.7,0.1,1.3,0.2,2,0.4c0.3,0,0.3,0.4,0.5,0.5c0.3,0.1,0.6,0,1,0.2c0-0.1-0.1-0.2,0-0.3
|
||||
c0.2-0.2,0.5,0.1,0.7-0.1c0.4-0.3-0.4-0.7-0.6-1.1c0-0.1,0.1-0.1,0.1-0.1c0.4,0.4,0.7,0.8,1.3,1.1c0.3,0.1,0.9,0.3,0.8-0.1
|
||||
c-0.3-0.6-0.8-1.1-1.2-1.6v-0.2c-0.1,0-0.1-0.1-0.2-0.1v-0.2c-0.2-0.1-0.2-0.3-0.3-0.5c-0.2-0.3-0.1-0.6-0.2-1
|
||||
C62.1,39.3,62,39,62,38.7c-0.2-0.9-0.4-1.7-0.5-2.6c-0.1-1,0.6-1.8,1.1-2.7c0.4-0.6,0.8-1.3,1.5-1.7c0.2-0.6,0.6-1.2,1-1.7
|
||||
c0.4-0.5,1.1-0.8,1.7-1.1c0.7-0.3,1.4-0.5,1.4-0.5l10.7,0c0,0,0.1,0,0.3,0.1c0.2,0.1,0.6,0.3,0.8,0.4c0.4,0.2,0.8,0.5,1,0.9
|
||||
c0.1,0.2,0.3,0.5,0.2,0.7c-0.1,0.3-0.2,0.7-0.4,0.8c-0.3,0.2-0.7,0.2-1.1,0.1c-0.2,0-0.4-0.1-0.6-0.1c0.8,0.3,1.6,0.7,2.1,1.4
|
||||
c0.1,0.1,0.3,0.2,0.5,0.2c0.1,0,0.1,0.1,0.1,0.2c-0.1,0.1-0.2,0.2-0.2,0.3h0.2c0.3-0.1,0.2-0.6,0.6-0.5c0.3,0.2,0.4,0.5,0.2,0.8
|
||||
c-0.2,0.2-0.4,0.4-0.6,0.5c-0.1,0.1-0.1,0.3,0,0.4c0.2,0.2,0.2,0.4,0.3,0.6c0.2,0.4,0.2,0.8,0.4,1.2c0.2,0.8,0.4,1.6,0.4,2.4
|
||||
c0,0.4-0.2,0.8-0.1,1.2c0.1,0.4,0.4,0.8,0.6,1.1c0.2,0.3,0.4,0.5,0.6,0.9c0.3,0.5,0.9,1.1,0.6,1.7c-0.2,0.4-0.8,0.3-1.1,0.5
|
||||
c-0.3,0.3-0.1,0.7,0.1,1c0.3,0.5-0.3,0.8-0.7,1c0.1,0.2,0.3,0.1,0.4,0.2c0.1,0.3,0.3,0.4,0.2,0.7c-0.2,0.3-0.9,0.5-0.5,1
|
||||
c0.2,0.4,0.1,0.8-0.1,1.2c-0.2,0.5-0.6,0.7-1,0.8c-0.3,0.1-0.7,0.1-1,0.1c-0.1-0.1-0.2-0.1-0.3-0.1c-0.9-0.1-1.8-0.4-2.7-0.4
|
||||
c-0.3,0.1-0.5,0.1-0.7,0.2c-0.2,0.2-0.5,0.4-0.6,0.6c0,0,0,0,0,0c0,0-0.1,0.1-0.1,0.1c0,0,0,0.1-0.1,0.1c0,0,0,0,0,0.1
|
||||
c-0.2,0.2-0.3,0.4-0.4,0.6c0,0,0,0,0,0c0,0,0,0.1,0,0.1c-0.2,0.3-0.3,0.7-0.4,1c-0.4,1.2-0.2,2.3,0.1,2.5c0.1,0.1,1.8,0.6,2.9,1.1
|
||||
c0.6,0.2,0.9,0.4,1.3,0.6l-22,0c1-0.7,2-1.1,3.5-1.8C61.6,54.6,63.1,53.9,63.6,53.4 M55.4,49.5c-0.1,0-0.3,0.1-0.3-0.1
|
||||
c0.1-0.3,0.4-0.3,0.6-0.4c0.1-0.1,0.3-0.2,0.4-0.1c0.1,0.2,0.3,0.1,0.4,0.2C56.2,49.5,55.8,49.4,55.4,49.5 M47.2,48.3
|
||||
c0,0-0.1-0.1-0.1-0.1c0.7-0.9,1.2-1.8,1.7-2.7c0.7-0.4,1.3-0.9,1.8-1.5c0.9-1,1.9-1.8,3-2.4c0.4-0.2,1-0.1,1.4,0.1
|
||||
c-0.2,0.2-0.4,0.2-0.6,0.3c-0.1,0-0.1,0-0.2-0.1c0.1-0.1,0.1-0.1,0.1-0.2c-0.5,0.6-1.3,0.9-1.7,1.6c-0.3,0.5-0.5,1.2-1.2,1.4
|
||||
c-0.2,0.1,0.1-0.2-0.1-0.1C49.6,45.7,48.5,46.9,47.2,48.3 M51.6,44.8c-0.1,0.1-0.1,0.1-0.2,0.2c-0.1,0.1-0.1,0.2-0.2,0.2
|
||||
c-0.1,0-0.1,0-0.1-0.1c0.1-0.2,0.2-0.4,0.4-0.5C51.6,44.7,51.6,44.8,51.6,44.8 M54.1,52.8c0,0.1-0.1,0.1-0.1,0.2
|
||||
c0.1,0,0.1,0,0.1,0.1c-0.1,0.1-0.2,0.2-0.4,0.3c0,0,0,0-0.1,0c-0.1,0.1-0.1,0.1-0.2,0.2c-0.1,0.1-0.4,0-0.3-0.1
|
||||
c0.1-0.1,0.3-0.3,0.4-0.4c0.1-0.1,0.2-0.1,0.2-0.2c0,0,0.1-0.1,0.1-0.1C53.9,52.8,54.2,52.7,54.1,52.8 M53.2,52.4
|
||||
C53.2,52.4,53.1,52.4,53.2,52.4c-0.2,0.2-0.4,0.3-0.6,0.4c-0.2,0.1-0.5,0.2-0.7,0.3c0,0,0,0,0,0c0,0-0.1,0-0.1,0
|
||||
c-0.2,0.1-0.4,0.3-0.5,0.4c0,0,0,0-0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1,0.1-0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0
|
||||
c0,0-0.1,0.1-0.1,0.1c0,0,0,0.1-0.1,0.1c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0
|
||||
c-0.1,0.1-0.1,0.1-0.2,0.2c-0.1,0.1-0.2,0.2-0.3,0.3c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0.1
|
||||
l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0-0.1,0-0.1l0,0c0,0,0,0,0,0c0.1-0.1,0.1-0.1,0.2-0.2c0,0,0,0,0,0c0,0,0,0,0.1-0.1
|
||||
c0,0,0.1-0.1,0.1-0.1c0,0,0,0,0,0c0.1-0.1,0.1-0.2,0.2-0.2c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1-0.1,0.1-0.1c0,0,0-0.1,0.1-0.1
|
||||
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0-0.1,0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0-0.1c0,0,0,0,0,0c0,0,0-0.1,0-0.1
|
||||
c0,0,0,0,0,0c0.1-0.1,0.1-0.2,0.2-0.3c0,0,0,0,0,0c-0.1,0-0.1,0.1-0.2,0.2c-0.1,0-0.2,0-0.1-0.1c0,0,0.1-0.1,0.1-0.1c0,0,0,0,0,0
|
||||
c0.1-0.1,0.2-0.2,0.3-0.3c0.1,0,0.1-0.1,0.2-0.1c0,0,0,0,0,0c0,0,0.1-0.1,0.1-0.1c0,0,0,0,0,0c0.5-0.5,1.4-0.5,2.1-0.8
|
||||
c0.3-0.1,0.6,0.1,0.9,0c0.2,0,0.3,0,0.5,0.1C54,51.8,53.6,52.1,53.2,52.4 M54.3,48.7c-0.1-0.1,0.2,0,0.2-0.1H54
|
||||
c-0.1,0-0.1-0.1-0.1-0.1c-0.3,0.1-0.6,0.2-0.9,0.2c-0.4,0.1-0.7,0.4-1.1,0.5c-0.6,0.2-1.1,0.7-1.7,0.9c-0.1,0-0.1-0.1-0.1-0.1
|
||||
c0.1-0.2,0.3-0.2,0.4-0.4c0-0.1,0-0.1-0.1-0.1c0.4-0.6,1-0.9,1.6-1.4v-0.2c0.2-0.2,0.4-0.3,0.5-0.6c0.1-0.2,0.3-0.4,0.5-0.5
|
||||
c-0.1-0.1-0.2-0.1-0.2-0.2c-0.2,0-0.4,0.1-0.6-0.1c0.1-0.1,0.2-0.2,0.3-0.2c0,0-0.1,0-0.1-0.1c-0.1-0.1,0.1-0.2,0.3-0.3
|
||||
c0.2-0.1,0.5-0.1,0.6-0.2c-0.4-0.1-0.8,0.1-1.2-0.1c0.3-0.7,0.7-1.3,1.3-1.6c0.1,0,0.2,0,0.2,0.1c0,0.3-0.2,0.5-0.4,0.5
|
||||
c0.4,0.1,0.9,0.1,1.3,0.3c-0.1,0.1-0.2,0.1-0.2,0.1c0.3,0.2,0.6,0.1,0.9,0.3c-0.2,0.2-0.3,0-0.5,0c1.7,0.5,3.4,0.9,4.8,1.9
|
||||
c-1.2,0.6-2.4,0.9-3.7,1.1c-0.2,0-0.3,0-0.4-0.1c0,0.1,0,0.2-0.1,0.2c-0.2,0-0.4,0-0.5,0.1C54.7,48.8,54.4,48.8,54.3,48.7"
|
||||
/>
|
||||
<path
|
||||
id="Rouge_1_"
|
||||
class="st1"
|
||||
d="M63.6,53.4c0.3-0.3,0.6-0.6,0.9-1h0c0.6-0.6,1.1-1.3,1.8-1.8c0.2-0.2,0.4-0.3,0.6-0.5
|
||||
c0.1-0.1,0.1-0.2,0.1-0.2c-0.3,0.1-0.4,0.3-0.7,0.4c-0.1,0-0.1-0.1-0.1-0.1c0.2-0.1,0.4-0.3,0.6-0.4c0,0,0,0,0,0
|
||||
c-0.1,0-0.1-0.1-0.1-0.1c-0.7-0.1-1.2,0.4-1.7,0.8c-0.1,0.1-0.2-0.1-0.3-0.1c-0.8,0.3-1.4,1-2.2,1.3v-0.1c-0.3,0.1-0.6,0.3-1,0.4
|
||||
c-0.5,0.1-0.9,0.1-1.3,0.1c-0.6,0.1-1.3,0.2-1.9,0.3c0,0,0,0-0.1,0c-0.3,0.1-0.7,0.2-1,0.4c0,0,0,0,0,0c0,0-0.1,0.1-0.1,0.1
|
||||
c-0.1,0.1-0.2,0.2-0.4,0.3c-0.3,0.2-0.6,0.5-0.9,0.7c0,0-0.1,0-0.1,0c-0.3,0.3-0.6,0.6-0.9,0.8c0,0-0.1,0-0.2,0c0,0,0,0,0,0
|
||||
c0,0,0,0,0-0.1c0-0.1,0.1-0.2,0.1-0.2c0.1-0.1,0.1-0.2,0.2-0.2c0.1-0.1,0.1-0.2,0.2-0.3c0,0,0-0.1,0-0.1c0,0,0,0-0.1,0
|
||||
c0.3-0.3,0.6-0.5,0.9-0.7v0c0,0-0.1,0-0.1-0.1c0,0,0.1-0.1,0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c-0.1,0.1-0.2,0.1-0.3,0.2
|
||||
c-0.1,0.1-0.2,0.4-0.4,0.3c0,0-0.1,0-0.1,0c0,0,0,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
|
||||
c0,0,0,0,0,0c0,0,0,0,0-0.1c0,0,0,0,0,0c0,0,0-0.1,0.1-0.1c0,0,0,0,0-0.1c0,0,0-0.1,0.1-0.1c0,0,0-0.1,0-0.1
|
||||
c0.1-0.1,0.2-0.2,0.4-0.3h0c0.2-0.1,0.4-0.2,0.6-0.3c0,0,0.1-0.1,0.1-0.1c-0.3,0.1-0.6,0.2-0.9,0.4c0,0-0.1,0-0.1,0
|
||||
c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0.1-0.1,0.2-0.2,0.3-0.3c0.1,0,0.1,0,0.1,0.1c1.7-1.3,4.1-1,6.1-1.7c0.2-0.1,0.3-0.2,0.5-0.3
|
||||
c0.3-0.1,0.5-0.4,0.8-0.5c0.4-0.3,0.7-0.7,0.9-1.2c0-0.1-0.1-0.1-0.1-0.1c-0.7,0.8-1.5,1.3-2.4,1.8c-1.1,0.6-2.4,0.5-3.5,0.6
|
||||
c0.1-0.1,0.2-0.1,0.3-0.1c0-0.2,0.1-0.2,0.2-0.3H59c0.1,0,0.1-0.1,0.1-0.1c0.1,0,0.3-0.1,0.2-0.1c-0.2-0.2-0.5,0.2-0.8,0
|
||||
c0.1-0.1,0.1-0.3,0.2-0.3h0.2c0-0.1,0.1-0.2,0.1-0.2c0.8-0.5,1.6-0.9,2.3-1.3c-0.2,0-0.3,0.2-0.4,0.1c0.1,0,0-0.2,0.1-0.2
|
||||
c0.6-0.2,1.1-0.5,1.7-0.7c-0.2,0-0.4,0.2-0.6,0c0.1-0.1,0.2-0.2,0.3-0.2v-0.2c0-0.1,0.1-0.1,0.1-0.1c-0.1,0-0.1-0.1-0.1-0.1
|
||||
c0.1-0.1,0.2-0.1,0.3-0.2c-0.1,0-0.2,0-0.2-0.1c0.2-0.2,0.4-0.3,0.7-0.3c-0.1-0.1-0.2,0-0.2-0.1c0-0.1,0.1-0.1,0.1-0.1H63
|
||||
c-0.1-0.1-0.1-0.2-0.1-0.2c0.3-0.4,0.3-0.9,0.5-1.3c-0.1,0-0.1,0-0.1-0.1c-0.5,0.6-1.4,0.8-2.2,1h-0.3c-0.3,0.1-0.6,0.1-0.9-0.1
|
||||
c-0.2-0.1-0.3-0.3-0.5-0.4c-0.4-0.3-0.9-0.5-1.3-0.6c-1.3-0.4-2.7-0.6-4.1-0.6c0.6-0.3,1.2-0.3,1.9-0.5c0.9-0.3,1.8-0.6,2.7-0.5
|
||||
c-0.2-0.1-0.4,0-0.5,0c-0.8-0.1-1.5,0.2-2.3,0.3c-0.5,0.1-1,0.3-1.6,0.4c-0.3,0.1-0.5,0.4-0.9,0.4V44c0.5-0.6,1.2-1.3,2-1.3
|
||||
c1-0.2,1.9,0,2.8,0.1c0.7,0.1,1.3,0.2,2,0.4c0.3,0,0.3,0.4,0.5,0.5c0.3,0.1,0.6,0,1,0.2c0-0.1-0.1-0.2,0-0.3
|
||||
c0.2-0.2,0.5,0.1,0.7-0.1c0.4-0.3-0.4-0.7-0.6-1.1c0-0.1,0.1-0.1,0.1-0.1c0.4,0.4,0.7,0.8,1.3,1.1c0.3,0.1,0.9,0.3,0.8-0.1
|
||||
c-0.3-0.6-0.8-1.1-1.2-1.6v-0.2c-0.1,0-0.1-0.1-0.2-0.1v-0.2c-0.2-0.1-0.2-0.3-0.3-0.5c-0.2-0.3-0.1-0.6-0.2-1
|
||||
C62.1,39.3,62,39,62,38.7c-0.2-0.9-0.4-1.7-0.5-2.6c-0.1-1,0.6-1.8,1.1-2.7c0.4-0.6,0.8-1.3,1.5-1.7c0.2-0.6,0.6-1.2,1-1.7
|
||||
c0.4-0.5,1.1-0.8,1.7-1.1c0.7-0.3,1.4-0.5,1.4-0.5H31.4v28.3h26c1-0.7,2-1.1,3.5-1.8C61.6,54.6,63.1,53.9,63.6,53.4 M55.4,49.5
|
||||
c-0.1,0-0.3,0.1-0.3-0.1c0.1-0.3,0.4-0.3,0.6-0.4c0.1-0.1,0.3-0.2,0.4-0.1c0.1,0.2,0.3,0.1,0.4,0.2C56.2,49.5,55.8,49.4,55.4,49.5
|
||||
M47.2,48.3c0,0-0.1-0.1-0.1-0.1c0.7-0.9,1.2-1.8,1.7-2.7c0.7-0.4,1.3-0.9,1.8-1.5c0.9-1,1.9-1.8,3-2.4c0.4-0.2,1-0.1,1.4,0.1
|
||||
c-0.2,0.2-0.4,0.2-0.6,0.3c-0.1,0-0.1,0-0.2-0.1c0.1-0.1,0.1-0.1,0.1-0.2c-0.5,0.6-1.3,0.9-1.7,1.6c-0.3,0.5-0.5,1.2-1.2,1.4
|
||||
c-0.2,0.1,0.1-0.2-0.1-0.1C49.7,45.7,48.5,46.9,47.2,48.3 M51.6,44.8c-0.1,0.1-0.1,0.1-0.2,0.2c-0.1,0.1-0.1,0.2-0.2,0.2
|
||||
c-0.1,0-0.1,0-0.1-0.1c0.1-0.2,0.2-0.4,0.4-0.5C51.6,44.7,51.6,44.8,51.6,44.8 M54.1,52.8c0,0.1-0.1,0.1-0.1,0.2
|
||||
c0.1,0,0.1,0,0.1,0.1c-0.1,0.1-0.2,0.2-0.4,0.3c0,0,0,0-0.1,0c-0.1,0.1-0.1,0.1-0.2,0.2c-0.1,0.1-0.4,0-0.3-0.1
|
||||
c0.1-0.1,0.3-0.3,0.4-0.4c0.1-0.1,0.2-0.1,0.2-0.2c0,0,0.1-0.1,0.1-0.1C53.9,52.8,54.2,52.7,54.1,52.8 M53.2,52.4
|
||||
C53.2,52.4,53.2,52.4,53.2,52.4c-0.2,0.2-0.4,0.3-0.6,0.4c-0.2,0.1-0.5,0.2-0.7,0.3c0,0,0,0,0,0c0,0-0.1,0-0.1,0
|
||||
c-0.2,0.1-0.4,0.3-0.5,0.4c0,0,0,0-0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0-0.1,0.1-0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0
|
||||
c0,0-0.1,0.1-0.1,0.1c0,0,0,0.1-0.1,0.1c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0
|
||||
c-0.1,0.1-0.1,0.1-0.2,0.2c-0.1,0.1-0.2,0.2-0.3,0.3c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0.1
|
||||
l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0-0.1,0-0.1l0,0c0,0,0,0,0,0c0.1-0.1,0.1-0.1,0.2-0.2c0,0,0,0,0,0c0,0,0,0,0.1-0.1
|
||||
c0,0,0.1-0.1,0.1-0.1c0,0,0,0,0,0c0.1-0.1,0.1-0.2,0.2-0.2c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1-0.1,0.1-0.1c0,0,0-0.1,0.1-0.1
|
||||
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0-0.1,0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0-0.1c0,0,0,0,0,0c0,0,0-0.1,0-0.1
|
||||
c0,0,0,0,0,0c0.1-0.1,0.1-0.2,0.2-0.3c0,0,0,0,0,0c-0.1,0-0.1,0.1-0.2,0.2c-0.1,0-0.2,0-0.1-0.1c0,0,0.1-0.1,0.1-0.1c0,0,0,0,0,0
|
||||
c0.1-0.1,0.2-0.2,0.3-0.3c0.1,0,0.1-0.1,0.2-0.1c0,0,0,0,0,0c0,0,0.1-0.1,0.1-0.1c0,0,0,0,0,0c0.5-0.5,1.4-0.5,2.1-0.8
|
||||
c0.3-0.1,0.6,0.1,0.9,0c0.2,0,0.3,0,0.5,0.1C54,51.8,53.6,52.1,53.2,52.4 M54.3,48.7c-0.1-0.1,0.2,0,0.2-0.1H54
|
||||
c-0.1,0-0.1-0.1-0.1-0.1c-0.3,0.1-0.6,0.2-0.9,0.2c-0.4,0.1-0.7,0.4-1.1,0.5c-0.6,0.2-1.1,0.7-1.7,0.9c-0.1,0-0.1-0.1-0.1-0.1
|
||||
c0.1-0.2,0.3-0.2,0.4-0.4c0-0.1,0-0.1-0.1-0.1c0.4-0.6,1-0.9,1.6-1.4v-0.2c0.2-0.2,0.4-0.3,0.5-0.6c0.1-0.2,0.3-0.4,0.5-0.5
|
||||
c-0.1-0.1-0.2-0.1-0.2-0.2c-0.2,0-0.4,0.1-0.6-0.1c0.1-0.1,0.2-0.2,0.3-0.2c0,0-0.1,0-0.1-0.1c-0.1-0.1,0.1-0.2,0.3-0.3
|
||||
c0.2-0.1,0.5-0.1,0.6-0.2c-0.4-0.1-0.8,0.1-1.2-0.1c0.3-0.7,0.7-1.3,1.3-1.6c0.1,0,0.2,0,0.2,0.1c0,0.3-0.2,0.5-0.4,0.5
|
||||
c0.4,0.1,0.9,0.1,1.3,0.3c-0.1,0.1-0.2,0.1-0.2,0.1c0.3,0.2,0.6,0.1,0.9,0.3c-0.2,0.2-0.3,0-0.5,0c1.7,0.5,3.4,0.9,4.8,1.9
|
||||
c-1.2,0.6-2.4,0.9-3.7,1.1c-0.2,0-0.3,0-0.4-0.1c0,0.1,0,0.2-0.1,0.2c-0.2,0-0.4,0-0.5,0.1C54.7,48.8,54.4,48.8,54.3,48.7"
|
||||
/>
|
||||
<path
|
||||
id="Bleu_1_"
|
||||
class="st2"
|
||||
d="M109.3,28.4H78.9c0,0,0.1,0,0.3,0.1c0.2,0.1,0.6,0.3,0.8,0.4c0.4,0.2,0.8,0.5,1,0.9
|
||||
c0.1,0.2,0.3,0.5,0.2,0.7c-0.1,0.3-0.2,0.7-0.4,0.8c-0.3,0.2-0.7,0.2-1.1,0.1c-0.2,0-0.4-0.1-0.6-0.1c0.8,0.3,1.6,0.7,2.1,1.4
|
||||
c0.1,0.1,0.3,0.2,0.5,0.2c0.1,0,0.1,0.1,0.1,0.2c-0.1,0.1-0.2,0.2-0.2,0.3h0.2c0.3-0.1,0.2-0.6,0.6-0.5c0.3,0.2,0.4,0.5,0.2,0.8
|
||||
c-0.2,0.2-0.4,0.4-0.6,0.5c-0.1,0.1-0.1,0.3,0,0.4c0.2,0.2,0.2,0.4,0.3,0.6c0.2,0.4,0.2,0.8,0.4,1.2c0.2,0.8,0.4,1.6,0.4,2.4
|
||||
c0,0.4-0.2,0.8-0.1,1.2c0.1,0.4,0.4,0.8,0.6,1.1c0.2,0.3,0.4,0.5,0.6,0.9c0.3,0.5,0.9,1.1,0.6,1.7c-0.2,0.4-0.8,0.3-1.1,0.5
|
||||
c-0.3,0.3-0.1,0.7,0.1,1c0.3,0.5-0.3,0.8-0.7,1c0.1,0.2,0.3,0.1,0.4,0.2c0.1,0.3,0.3,0.4,0.2,0.7c-0.2,0.3-0.9,0.5-0.5,1
|
||||
c0.2,0.4,0.1,0.8-0.1,1.2c-0.2,0.5-0.6,0.7-1,0.8c-0.3,0.1-0.7,0.1-1,0.1c-0.1-0.1-0.2-0.1-0.3-0.1c-0.9-0.1-1.8-0.4-2.7-0.4
|
||||
c-0.3,0.1-0.5,0.1-0.7,0.2c-0.2,0.2-0.5,0.4-0.6,0.6c0,0,0,0,0,0c0,0-0.1,0.1-0.1,0.1c0,0,0,0.1-0.1,0.1c0,0,0,0,0,0.1
|
||||
c-0.2,0.2-0.3,0.4-0.4,0.6c0,0,0,0,0,0c0,0,0,0.1,0,0.1c-0.2,0.3-0.3,0.7-0.4,1c-0.4,1.2-0.2,2.3,0.1,2.5c0.1,0.1,1.8,0.6,2.9,1.1
|
||||
c0.6,0.2,0.9,0.4,1.3,0.6h29.9V28.4z"
|
||||
/>
|
||||
<path
|
||||
id="Yeux_1_"
|
||||
class="st3"
|
||||
d="M80.7,38.7c0.2,0.1,0.5,0.1,0.5,0.2c-0.1,0.4-0.7,0.5-1.1,1H80c-0.2,0.1-0.1,0.4-0.3,0.4
|
||||
c-0.2-0.1-0.3,0-0.5,0.1c0.2,0.2,0.5,0.4,0.8,0.3c0.1,0,0.2,0.1,0.2,0.2c0,0,0.1,0,0.1-0.1c0.1,0,0.1,0,0.1,0.1V41
|
||||
c-0.2,0.2-0.4,0.1-0.6,0.2c0.4,0.1,0.9,0.1,1.2,0c0.3-0.1,0-0.6,0.2-0.9c-0.1,0,0-0.2-0.1-0.2c0.1-0.1,0.2-0.3,0.3-0.3
|
||||
c0.1,0,0.3-0.1,0.3-0.2c0-0.1-0.2-0.2-0.2-0.3c0.3-0.2,0.6-0.5,0.5-0.9c-0.1-0.2-0.5-0.2-0.8-0.3c-0.3-0.1-0.6,0-0.9,0.1
|
||||
c-0.3,0-0.5,0.2-0.8,0.2c-0.4,0.1-0.7,0.3-1,0.5c0.4-0.2,0.8-0.2,1.2-0.3C80.1,38.7,80.3,38.6,80.7,38.7"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM11.751 7.14453C11.7159 7.14453 11.6808 7.15341 11.6504 7.1709L8.87988 8.7666C8.77836 8.82519 8.74931 8.95855 8.81641 9.05469L9.5166 10.0576C9.57558 10.1417 9.68874 10.1675 9.77832 10.1172L11.3301 9.24512C11.4633 9.17057 11.6279 9.26713 11.6279 9.41992V16.6553C11.6279 16.7657 11.7177 16.8555 11.8281 16.8555H13.2988C13.4093 16.8555 13.499 16.7657 13.499 16.6553V7.34473C13.499 7.23427 13.4093 7.14453 13.2988 7.14453H11.751Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 708 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.7373 8.7207C15.1389 8.72072 15.465 8.85912 15.7148 9.13574C15.9735 9.4034 16.1651 9.78292 16.29 10.2734C16.4238 10.764 16.4912 11.34 16.4912 12C16.4912 12.4995 16.4552 12.9511 16.3838 13.3525C16.3124 13.7447 16.2013 14.0886 16.0498 14.3828C15.9071 14.6682 15.7239 14.8922 15.501 15.0527C15.2869 15.2043 15.0317 15.2803 14.7373 15.2803C14.3449 15.2802 14.0147 15.1454 13.7471 14.8779C13.4885 14.6015 13.292 14.2178 13.1582 13.7275C13.0333 13.2368 12.9707 12.6602 12.9707 12C12.9707 11.5007 13.0068 11.0545 13.0781 10.6621C13.1495 10.2607 13.2567 9.91636 13.3994 9.63086C13.551 9.33673 13.7381 9.11355 13.9609 8.96191C14.184 8.80134 14.4429 8.72076 14.7373 8.7207Z" fill="#222631"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM14.7373 7.04785C14.1217 7.04789 13.5859 7.17302 13.1309 7.42285C12.685 7.67265 12.3101 8.02473 12.0068 8.47949C11.7124 8.92563 11.4896 9.45288 11.3379 10.0596C11.1952 10.6572 11.1231 11.3043 11.123 12C11.1231 12.9278 11.253 13.7672 11.5117 14.5166C11.7794 15.266 12.1768 15.8597 12.7031 16.2969C13.2385 16.7339 13.9166 16.9531 14.7373 16.9531C15.353 16.9531 15.8887 16.8279 16.3438 16.5781C16.7988 16.3283 17.1743 15.9802 17.4688 15.5342C17.763 15.0793 17.9813 14.5525 18.124 13.9551C18.2757 13.3484 18.3515 12.6959 18.3516 12C18.3515 11.0633 18.2225 10.2239 17.9639 9.4834C17.7051 8.73407 17.3077 8.14031 16.7725 7.70312C16.237 7.2659 15.5582 7.04786 14.7373 7.04785ZM8.05957 7.30176C8.02452 7.30176 7.98935 7.31063 7.95898 7.32812L5.29004 8.86621C5.18837 8.92479 5.15837 9.05812 5.22559 9.1543L5.89453 10.1104C5.95344 10.1946 6.06657 10.2212 6.15625 10.1709L7.64062 9.33789C7.77387 9.26312 7.93829 9.35899 7.93848 9.51172V16.4854C7.93863 16.5957 8.02831 16.6855 8.13867 16.6855H9.5459C9.65626 16.6855 9.74594 16.5957 9.74609 16.4854V7.50098C9.74594 7.39065 9.65626 7.30176 9.5459 7.30176H8.05957Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM9.14453 7.30859C9.10959 7.30859 9.07522 7.31757 9.04492 7.33496L6.375 8.87305C6.27335 8.93161 6.24338 9.06495 6.31055 9.16113L6.97949 10.1182C7.03838 10.2025 7.15152 10.229 7.24121 10.1787L8.72559 9.34473C8.85879 9.26992 9.02315 9.36588 9.02344 9.51855V16.4922C9.0236 16.6025 9.11327 16.6924 9.22363 16.6924H10.6309C10.7412 16.6924 10.8309 16.6025 10.8311 16.4922V7.50781C10.8309 7.39749 10.7412 7.30859 10.6309 7.30859H9.14453ZM14.4648 7.30859C14.4299 7.30859 14.3955 7.31756 14.3652 7.33496L11.6953 8.87305C11.594 8.93169 11.5648 9.06507 11.6318 9.16113L12.2998 10.1182C12.3587 10.2025 12.4718 10.229 12.5615 10.1787L14.0469 9.34473C14.1801 9.26994 14.3445 9.36585 14.3447 9.51855V16.4922C14.3449 16.6025 14.4346 16.6924 14.5449 16.6924H15.9512C16.0615 16.6924 16.1512 16.6025 16.1514 16.4922V7.50781C16.1512 7.39749 16.0615 7.30859 15.9512 7.30859H14.4648Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM8.02539 7.43555C7.99046 7.4356 7.95605 7.44447 7.92578 7.46191L5.25586 9C5.15434 9.05863 5.12521 9.19197 5.19238 9.28809L5.86035 10.2441C5.91917 10.3283 6.03246 10.3547 6.12207 10.3047L7.60742 9.47168C7.74063 9.39712 7.90509 9.49286 7.90527 9.64551V16.6191C7.90543 16.7293 7.99429 16.8191 8.10449 16.8193H9.5127C9.62295 16.8192 9.71273 16.7294 9.71289 16.6191V7.63477C9.71273 7.52452 9.62295 7.43567 9.5127 7.43555H8.02539ZM14.6924 7.18164C13.8714 7.18165 13.1661 7.3684 12.5771 7.74316C12.0492 8.0843 11.5948 8.53661 11.2148 9.09961C11.153 9.19172 11.1827 9.31604 11.2773 9.37402L12.4043 10.0605C12.4993 10.1183 12.6232 10.0875 12.6836 9.99414C12.9037 9.65412 13.1405 9.37686 13.3936 9.16211C13.697 8.91225 14.0724 8.78809 14.5186 8.78809C14.7682 8.78816 14.9875 8.83648 15.1748 8.93457C15.3708 9.0327 15.5229 9.17138 15.6299 9.34961C15.7457 9.52799 15.8037 9.74732 15.8037 10.0059C15.8037 10.202 15.7724 10.3853 15.71 10.5547C15.6564 10.7151 15.5757 10.8757 15.4688 11.0361C15.3706 11.1878 15.2454 11.3443 15.0938 11.5049C14.951 11.6655 14.7907 11.84 14.6123 12.0273L11.4717 15.127C11.4337 15.1645 11.4131 15.2161 11.4131 15.2695V16.6191C11.4132 16.7293 11.5021 16.8191 11.6123 16.8193H17.6777C17.788 16.8192 17.8778 16.7294 17.8779 16.6191V15.3857C17.8778 15.2755 17.788 15.1867 17.6777 15.1865H14.1045C13.9246 15.1865 13.8354 14.9667 13.9648 14.8418L15.8701 13.0039C16.1824 12.7005 16.4688 12.3886 16.7275 12.0674C16.9951 11.7374 17.2133 11.3891 17.3828 11.0234C17.5523 10.6578 17.6376 10.2601 17.6377 9.83203C17.6377 9.29683 17.5028 8.832 17.2354 8.43945C16.9767 8.03826 16.6244 7.72972 16.1787 7.51562C15.7415 7.29258 15.2455 7.1817 14.6924 7.18164Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM14.5801 7.04785C13.8216 7.04786 13.1741 7.20427 12.6387 7.5166C12.1678 7.79609 11.7676 8.16773 11.4395 8.63281C11.3754 8.72359 11.404 8.84816 11.498 8.90723L12.624 9.61426C12.7199 9.67443 12.8464 9.64211 12.9092 9.54785C13.0792 9.29246 13.2705 9.08809 13.4824 8.93457C13.7501 8.74725 14.0716 8.6543 14.4463 8.6543C14.6961 8.65431 14.9152 8.69886 15.1025 8.78809C15.2895 8.86836 15.437 8.99292 15.5439 9.16211C15.6597 9.32261 15.7177 9.52402 15.7178 9.76465C15.7177 10.1214 15.5888 10.408 15.3301 10.6221C15.0713 10.8272 14.7451 10.9297 14.3525 10.9297H13.1338C13.0234 10.9297 12.9338 11.0186 12.9336 11.1289V12.3359C12.9338 12.4463 13.0234 12.5361 13.1338 12.5361H14.4727C14.8116 12.5361 15.1019 12.594 15.3428 12.71C15.5925 12.817 15.7851 12.9774 15.9189 13.1914C16.0616 13.3966 16.1328 13.6471 16.1328 13.9414C16.1327 14.2178 16.0616 14.4637 15.9189 14.6777C15.7851 14.8918 15.5973 15.0569 15.3564 15.1729C15.1245 15.2888 14.8522 15.3467 14.54 15.3467C14.0761 15.3467 13.6788 15.2262 13.3486 14.9854C13.0683 14.7751 12.808 14.4628 12.5684 14.0488C12.5103 13.9485 12.3808 13.9127 12.2822 13.9736L11.0498 14.7354C10.9582 14.7921 10.9284 14.9124 10.9854 15.0039C11.3525 15.5939 11.8055 16.0612 12.3447 16.4043C12.9337 16.7701 13.6704 16.9531 14.5537 16.9531C15.2049 16.9531 15.7854 16.8326 16.2939 16.5918C16.8111 16.351 17.2173 16.0073 17.5117 15.5615C17.815 15.1156 17.9666 14.5974 17.9668 14.0088C17.9668 13.5718 17.8727 13.1742 17.6855 12.8174C17.5071 12.4516 17.2434 12.1434 16.8955 11.8936C16.8457 11.8565 16.7944 11.8201 16.7412 11.7861C16.635 11.7183 16.6252 11.5596 16.7217 11.4785C16.9981 11.2378 17.2078 10.9519 17.3506 10.6221C17.4933 10.292 17.5654 9.93433 17.5654 9.55078C17.5654 9.04228 17.4354 8.60031 17.1768 8.22559C16.9181 7.85097 16.5613 7.56069 16.1064 7.35547C15.6603 7.15022 15.1512 7.04786 14.5801 7.04785ZM8.19043 7.30176C8.15538 7.30176 8.12021 7.31063 8.08984 7.32812L5.4209 8.86621C5.31923 8.92479 5.28923 9.05812 5.35645 9.1543L6.02539 10.1104C6.0843 10.1946 6.19743 10.2212 6.28711 10.1709L7.77148 9.33789C7.90473 9.26312 8.06915 9.35899 8.06934 9.51172V16.4854C8.06949 16.5957 8.15917 16.6855 8.26953 16.6855H9.67676C9.78712 16.6855 9.8768 16.5957 9.87695 16.4854V7.50098C9.87679 7.39065 9.78712 7.30176 9.67676 7.30176H8.19043Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.8242 9.86914C14.9412 9.71897 15.1816 9.80183 15.1816 9.99219V12.3691C15.1815 12.4793 15.0926 12.5691 14.9824 12.5693H13.1309C12.9644 12.5693 12.8705 12.3774 12.9727 12.2461L14.8242 9.86914Z" fill="#222631"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM8.01465 7.30859C7.97968 7.30863 7.94534 7.3175 7.91504 7.33496L5.24512 8.87305C5.1435 8.93162 5.11351 9.06497 5.18066 9.16113L5.84961 10.1182C5.90842 10.2024 6.02169 10.2288 6.11133 10.1787L7.5957 9.34473C7.7289 9.26991 7.89326 9.36588 7.89355 9.51855V16.4922C7.89371 16.6024 7.9835 16.6923 8.09375 16.6924H9.50195C9.61215 16.6922 9.70101 16.6024 9.70117 16.4922V7.50781C9.70101 7.39761 9.61215 7.30878 9.50195 7.30859H8.01465ZM14.9873 7.32227C14.925 7.32227 14.866 7.35095 14.8281 7.40039L10.9131 12.5146C10.8864 12.5495 10.8711 12.5928 10.8711 12.6367V14.002C10.8713 14.1122 10.961 14.202 11.0713 14.2021H14.9824C15.0926 14.2023 15.1815 14.2912 15.1816 14.4014V16.4922C15.1818 16.6024 15.2716 16.6923 15.3818 16.6924H16.79C16.9002 16.6922 16.9891 16.6024 16.9893 16.4922V14.4014C16.9894 14.2911 17.0792 14.2023 17.1895 14.2021H17.7402C17.8504 14.202 17.9393 14.1122 17.9395 14.002V12.7686C17.9393 12.6583 17.8504 12.5695 17.7402 12.5693H17.1895C17.0792 12.5692 16.9894 12.4794 16.9893 12.3691V7.52148C16.9891 7.41128 16.9002 7.32245 16.79 7.32227H14.9873Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM12.0986 7.43457C11.9922 7.43457 11.9039 7.51869 11.8984 7.625L11.6514 12.4316C11.6455 12.546 11.7361 12.6416 11.8506 12.6416H14.1699C14.5621 12.6416 14.8926 12.6997 15.1602 12.8154C15.4277 12.9224 15.6336 13.0839 15.7764 13.2979C15.919 13.503 15.9902 13.7574 15.9902 14.0605C15.9902 14.3371 15.9238 14.5827 15.79 14.7969C15.6562 15.011 15.4685 15.176 15.2275 15.292C14.9956 15.4079 14.7233 15.4658 14.4111 15.4658C13.9562 15.4657 13.5587 15.3501 13.2197 15.1182C12.9316 14.9078 12.6673 14.592 12.4268 14.1709C12.369 14.0698 12.2387 14.0335 12.1396 14.0947L10.9072 14.8564C10.8159 14.913 10.7855 15.0316 10.8418 15.123C11.2099 15.721 11.6678 16.188 12.2158 16.5234C12.8136 16.8892 13.5457 17.0722 14.4111 17.0723C15.0535 17.0722 15.6339 16.9471 16.1514 16.6973C16.6687 16.4474 17.0747 16.0908 17.3691 15.627C17.6725 15.163 17.8242 14.614 17.8242 13.9805C17.8242 13.3738 17.699 12.8513 17.4492 12.4141C17.1993 11.9771 16.8114 11.6373 16.2852 11.3965C15.7676 11.1557 15.0886 11.0352 14.25 11.0352H13.7393C13.6245 11.0352 13.5328 10.9388 13.5391 10.8242L13.625 9.25684C13.6308 9.1509 13.7182 9.06757 13.8242 9.06738H17.291C17.4012 9.06719 17.49 8.97831 17.4902 8.86816V7.63477C17.4901 7.5245 17.4013 7.43476 17.291 7.43457H12.0986ZM7.96875 7.4209C7.93378 7.42094 7.89945 7.43078 7.86914 7.44824L5.19922 8.98633C5.09765 9.04492 5.06762 9.17826 5.13477 9.27441L5.80371 10.2314C5.86261 10.3158 5.97573 10.3414 6.06543 10.291L7.5498 9.45801C7.68304 9.38318 7.84745 9.47909 7.84766 9.63184V16.6055C7.84788 16.7157 7.93764 16.8046 8.04785 16.8047H9.45605C9.56621 16.8045 9.65505 16.7156 9.65527 16.6055V7.62109C9.65518 7.51083 9.56629 7.42109 9.45605 7.4209H7.96875Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.7344 12.2812C15.0288 12.2812 15.2877 12.3486 15.5107 12.4824C15.7426 12.6073 15.921 12.781 16.0459 13.0039C16.1797 13.227 16.2471 13.4868 16.2471 13.7812C16.2469 14.0753 16.1796 14.3386 16.0459 14.5703C15.921 14.8021 15.7378 14.9854 15.4971 15.1191C15.2652 15.2527 14.984 15.3203 14.6543 15.3203C14.2709 15.3202 13.9358 15.213 13.6504 14.999C13.3649 14.7849 13.1458 14.4405 12.9941 13.9678C12.9311 13.7673 12.8816 13.5383 12.8447 13.2822C12.8373 13.23 12.8514 13.1766 12.8848 13.1357C13.0974 12.8752 13.344 12.6758 13.623 12.5361C13.962 12.3666 14.333 12.2813 14.7344 12.2812Z" fill="#222631"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM14.9756 7.04785C14.2619 7.04791 13.6454 7.17304 13.1279 7.42285C12.6197 7.66375 12.2001 8.00726 11.8701 8.45312C11.5402 8.89018 11.2944 9.41693 11.1338 10.0322C10.9822 10.6389 10.9062 11.3095 10.9062 12.041C10.9063 13.0846 11.0533 13.9726 11.3477 14.7041C11.6421 15.4357 12.0707 15.9942 12.6328 16.3779C13.1949 16.7615 13.8825 16.953 14.6943 16.9531C15.4079 16.9531 16.0198 16.806 16.5283 16.5117C17.0366 16.2174 17.4247 15.8243 17.6924 15.334C17.9689 14.8433 18.1073 14.3026 18.1074 13.7139C18.1074 13.1428 17.9822 12.6299 17.7324 12.1748C17.4915 11.7197 17.1398 11.3576 16.6758 11.0898C16.2208 10.8133 15.6849 10.6748 15.0693 10.6748C14.5878 10.6749 14.1455 10.7555 13.7441 10.916C13.5406 10.9947 13.348 11.0928 13.166 11.21C13.0169 11.3059 12.8065 11.1973 12.8281 11.0215C12.8474 10.8674 12.8708 10.7202 12.9004 10.5811C12.9896 10.1621 13.1235 9.81357 13.3018 9.53711C13.489 9.25177 13.7217 9.03731 13.998 8.89453C14.2745 8.75183 14.5963 8.68073 14.9619 8.68066C15.3455 8.68066 15.6756 8.77465 15.9521 8.96191C16.1874 9.10892 16.4082 9.30675 16.6133 9.55664C16.686 9.6453 16.8174 9.66326 16.9062 9.59082L17.9873 8.70703C18.07 8.6395 18.0854 8.51887 18.0176 8.43652C17.8366 8.21749 17.6251 8.01359 17.3848 7.82422C17.0993 7.59228 16.7559 7.40448 16.3545 7.26172C15.962 7.11902 15.5019 7.04787 14.9756 7.04785ZM7.81543 7.30176C7.78048 7.3018 7.74611 7.31067 7.71582 7.32812L5.0459 8.86621C4.94435 8.92483 4.91524 9.05817 4.98242 9.1543L5.65039 10.1104C5.70922 10.1945 5.82249 10.221 5.91211 10.1709L7.39648 9.33789C7.52973 9.26312 7.69415 9.35899 7.69434 9.51172V16.4854C7.69449 16.5956 7.78431 16.6854 7.89453 16.6855H9.30273C9.41296 16.6854 9.50179 16.5956 9.50195 16.4854V7.50098C9.50179 7.39075 9.41296 7.30192 9.30273 7.30176H7.81543Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM8.41504 7.30859C8.38009 7.30864 8.34572 7.31751 8.31543 7.33496L5.64551 8.87305C5.54393 8.93163 5.51391 9.06498 5.58105 9.16113L6.25 10.1182C6.3088 10.2024 6.42209 10.2287 6.51172 10.1787L7.99609 9.34473C8.12929 9.26991 8.29463 9.36588 8.29492 9.51855V16.4922C8.29508 16.6024 8.38392 16.6922 8.49414 16.6924H9.90234C10.0126 16.6922 10.1024 16.6024 10.1025 16.4922V7.50781C10.1024 7.39759 10.0126 7.30875 9.90234 7.30859H8.41504ZM11.832 7.32227C11.7218 7.32243 11.633 7.41126 11.6328 7.52148V8.75488C11.633 8.86511 11.7218 8.95492 11.832 8.95508H16.0918C16.2478 8.95508 16.3438 9.12551 16.2627 9.25879L11.9248 16.3877C11.8437 16.521 11.9397 16.6924 12.0957 16.6924H13.7129C13.7839 16.6924 13.8498 16.654 13.8857 16.5928L18.3379 9.00098C18.3558 8.97038 18.3662 8.93584 18.3662 8.90039V7.52148C18.3661 7.41126 18.2762 7.32242 18.166 7.32227H11.832Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.6377 12.4824C14.9945 12.4825 15.2981 12.545 15.5479 12.6699C15.8062 12.7948 16.003 12.9686 16.1367 13.1914C16.2792 13.4054 16.3515 13.6648 16.3516 13.9678C16.3515 14.2531 16.2794 14.5037 16.1367 14.7178C16.0029 14.9318 15.8065 15.0969 15.5479 15.2129C15.2981 15.3289 14.9945 15.3867 14.6377 15.3867C14.281 15.3867 13.9678 15.3287 13.7002 15.2129C13.4419 15.097 13.2413 14.9315 13.0986 14.7178C12.9648 14.5037 12.8975 14.2531 12.8975 13.9678C12.8975 13.6646 12.9649 13.4055 13.0986 13.1914C13.2413 12.9686 13.4418 12.7948 13.7002 12.6699C13.9679 12.545 14.2808 12.4824 14.6377 12.4824Z" fill="#222631"/>
|
||||
<path d="M14.6514 8.60059C15.0437 8.60072 15.356 8.70783 15.5879 8.92188C15.8195 9.13593 15.9365 9.417 15.9365 9.76465C15.9365 10.0054 15.8824 10.2162 15.7754 10.3945C15.6683 10.5638 15.5164 10.6978 15.3203 10.7959C15.1331 10.885 14.9098 10.9296 14.6514 10.9297C14.2322 10.9297 13.9011 10.827 13.6602 10.6221C13.4193 10.408 13.2989 10.1214 13.2988 9.76465C13.2989 9.53283 13.353 9.33156 13.46 9.16211C13.567 8.98386 13.7189 8.84612 13.915 8.74805C14.1203 8.64994 14.3659 8.60059 14.6514 8.60059Z" fill="#222631"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM14.624 7.04785C13.9283 7.04788 13.3439 7.16839 12.8711 7.40918C12.4072 7.64113 12.0544 7.95322 11.8135 8.3457C11.5727 8.72927 11.4522 9.14009 11.4521 9.57715C11.4522 10.041 11.5726 10.4565 11.8135 10.8223C11.9522 11.0329 12.1281 11.2186 12.3408 11.3789C12.4761 11.4812 12.4633 11.7101 12.3164 11.7949C12.1886 11.8687 12.0703 11.951 11.9609 12.041C11.6576 12.2819 11.4253 12.5759 11.2646 12.9238C11.1131 13.2628 11.0372 13.6423 11.0371 14.0615C11.0372 14.6057 11.1756 15.097 11.4521 15.5342C11.7288 15.9711 12.1348 16.3195 12.6699 16.5781C13.2052 16.8278 13.8569 16.9531 14.624 16.9531C15.2039 16.9531 15.718 16.8773 16.1641 16.7256C16.6189 16.5828 16.9937 16.3817 17.2881 16.123C17.5914 15.8643 17.819 15.5608 17.9707 15.2129C18.1312 14.8561 18.2118 14.4718 18.2119 14.0615C18.2119 13.4997 18.0732 13.0128 17.7969 12.6025C17.5204 12.1834 17.114 11.8579 16.5791 11.626C16.5648 11.6197 16.5634 11.6002 16.5771 11.5928C16.9508 11.391 17.2375 11.1345 17.4355 10.8223C17.6763 10.4566 17.7968 10.0409 17.7969 9.57715C17.7968 9.14023 17.6762 8.72916 17.4355 8.3457C17.2036 7.95331 16.8505 7.64114 16.3779 7.40918C15.914 7.16828 15.3288 7.0479 14.624 7.04785ZM7.98438 7.30176C7.94944 7.30182 7.91504 7.31068 7.88477 7.32812L5.21484 8.86621C5.11333 8.92484 5.0842 9.05818 5.15137 9.1543L5.81934 10.1104C5.87816 10.1945 5.99144 10.2209 6.08105 10.1709L7.56641 9.33789C7.69961 9.26333 7.86407 9.35907 7.86426 9.51172V16.4854C7.86442 16.5956 7.95328 16.6854 8.06348 16.6855H9.47168C9.58193 16.6854 9.67172 16.5956 9.67188 16.4854V7.50098C9.67172 7.39073 9.58193 7.30188 9.47168 7.30176H7.98438Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.7549 8.68066C15.1474 8.68072 15.4822 8.78785 15.7588 9.00195C16.0442 9.2161 16.2624 9.56423 16.4141 10.0459C16.4761 10.2395 16.525 10.461 16.5615 10.71C16.5689 10.7602 16.5562 10.8122 16.5254 10.8525C16.3221 11.1185 16.0756 11.3233 15.7852 11.4648C15.4462 11.6342 15.0752 11.7197 14.6738 11.7197C14.3798 11.7196 14.1165 11.6569 13.8848 11.5322C13.6618 11.3984 13.4825 11.2191 13.3486 10.9961C13.2239 10.7643 13.1622 10.5049 13.1621 10.2197C13.1622 9.9258 13.2241 9.66235 13.3486 9.43066C13.4823 9.19897 13.6659 9.01565 13.8975 8.88184C14.1383 8.74803 14.4249 8.68072 14.7549 8.68066Z" fill="#222631"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM14.7148 7.04785C14.0102 7.04791 13.3983 7.19493 12.8809 7.48926C12.3725 7.7837 11.9787 8.17637 11.7021 8.66699C11.4346 9.14875 11.3008 9.68943 11.3008 10.2871C11.3009 10.849 11.4261 11.3623 11.6758 11.8262C11.9256 12.2812 12.2784 12.6472 12.7334 12.9238C13.1884 13.1915 13.7243 13.3251 14.3398 13.3252C14.8303 13.3252 15.2726 13.2492 15.665 13.0977C15.8699 13.0138 16.0623 12.9116 16.2422 12.793C16.3907 12.695 16.6022 12.803 16.5801 12.9795C16.5609 13.132 16.537 13.2789 16.5078 13.4189C16.4186 13.8381 16.2801 14.1911 16.0928 14.4766C15.9143 14.7529 15.6866 14.9638 15.4102 15.1064C15.1337 15.2489 14.8117 15.3203 14.4463 15.3203C14.0719 15.3202 13.7414 15.2311 13.4561 15.0527C13.2208 14.8983 13.001 14.6947 12.7959 14.4434C12.7234 14.3545 12.5918 14.3368 12.5029 14.4092L11.4229 15.293C11.3402 15.3606 11.3248 15.4819 11.3926 15.5645C11.5721 15.7832 11.778 15.9876 12.0107 16.1768C12.3052 16.4087 12.6533 16.5965 13.0547 16.7393C13.4561 16.882 13.9162 16.9531 14.4336 16.9531C15.1471 16.9531 15.7591 16.8325 16.2676 16.5918C16.7848 16.3421 17.209 15.9984 17.5391 15.5615C17.8692 15.1155 18.11 14.5833 18.2617 13.9678C18.4222 13.3523 18.5029 12.6825 18.5029 11.96C18.5029 10.9162 18.3549 10.0285 18.0605 9.29688C17.7661 8.56511 17.3376 8.00678 16.7754 7.62305C16.2134 7.23959 15.5265 7.04792 14.7148 7.04785ZM8.27734 7.30176C8.24237 7.30179 8.20804 7.31066 8.17773 7.32812L5.50781 8.86621C5.40623 8.92481 5.37618 9.05815 5.44336 9.1543L6.1123 10.1104C6.17114 10.1945 6.28439 10.221 6.37402 10.1709L7.8584 9.33789C7.99164 9.26312 8.15606 9.35899 8.15625 9.51172V16.4854C8.15641 16.5956 8.24619 16.6854 8.35645 16.6855H9.76465C9.87484 16.6854 9.96371 16.5956 9.96387 16.4854V7.50098C9.96371 7.39077 9.87484 7.30195 9.76465 7.30176H8.27734Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM12.0586 6.52539C11.2089 6.52539 10.4787 6.71953 9.86914 7.10742C9.32104 7.46159 8.85015 7.93144 8.45605 8.5166C8.39387 8.60895 8.42349 8.73404 8.51855 8.79199L9.69629 9.50977C9.79128 9.56767 9.91525 9.53683 9.97559 9.44336C10.2044 9.08885 10.4506 8.79964 10.7139 8.57617C11.0279 8.3176 11.4162 8.1885 11.8779 8.18848C12.1365 8.18848 12.3627 8.23923 12.5566 8.34082C12.7598 8.4424 12.9175 8.58484 13.0283 8.76953C13.1483 8.95414 13.2079 9.18061 13.208 9.44824C13.208 9.65136 13.1759 9.84118 13.1113 10.0166C13.0559 10.1828 12.9722 10.3494 12.8613 10.5156C12.7598 10.6725 12.6305 10.8339 12.4736 11C12.3259 11.1662 12.1593 11.3471 11.9746 11.541L8.72363 14.751C8.68559 14.7886 8.66406 14.8401 8.66406 14.8936V16.2998C8.66406 16.4103 8.7538 16.5 8.86426 16.5H15.1553C15.2657 16.5 15.3555 16.4103 15.3555 16.2998V15.0098C15.3555 14.8993 15.2657 14.8096 15.1553 14.8096H11.4326C11.2529 14.8094 11.1646 14.5907 11.2939 14.4658L13.2773 12.5518C13.6005 12.2378 13.8963 11.9145 14.1641 11.582C14.441 11.2404 14.6673 10.8804 14.8428 10.502C15.0182 10.1233 15.1055 9.71186 15.1055 9.26855C15.1055 8.71456 14.9674 8.23442 14.6904 7.82812C14.4226 7.41253 14.0575 7.09372 13.5957 6.87207C13.1433 6.64127 12.631 6.52545 12.0586 6.52539Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.7803 8.7207C16.1818 8.72072 16.508 8.85912 16.7578 9.13574C17.0164 9.40341 17.2081 9.78292 17.333 10.2734C17.4668 10.7641 17.5342 11.34 17.5342 12C17.5342 12.4995 17.4981 12.9511 17.4268 13.3525C17.3554 13.7449 17.2434 14.0885 17.0918 14.3828C16.949 14.6682 16.766 14.8922 16.543 15.0527C16.3289 15.2041 16.0744 15.2803 15.7803 15.2803C15.3878 15.2802 15.0577 15.1455 14.79 14.8779C14.5314 14.6014 14.334 14.218 14.2002 13.7275C14.0753 13.2368 14.0137 12.6602 14.0137 12C14.0137 11.5008 14.0488 11.0545 14.1201 10.6621C14.1915 10.2607 14.2987 9.91634 14.4414 9.63086C14.593 9.33658 14.781 9.1136 15.0039 8.96191C15.227 8.80132 15.4859 8.72073 15.7803 8.7207Z" fill="#222631"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM15.7803 7.04785C15.1646 7.04787 14.6289 7.173 14.1738 7.42285C13.728 7.67262 13.353 8.02477 13.0498 8.47949C12.7554 8.9256 12.5315 9.45293 12.3799 10.0596C12.2372 10.6572 12.166 11.3043 12.166 12C12.1661 12.9277 12.2951 13.7672 12.5537 14.5166C12.8214 15.2658 13.2189 15.8597 13.7451 16.2969C14.2805 16.734 14.9594 16.9531 15.7803 16.9531C16.3958 16.9531 16.9317 16.8279 17.3867 16.5781C17.8416 16.3283 18.2164 15.9801 18.5107 15.5342C18.8051 15.0792 19.0242 14.5527 19.167 13.9551C19.3187 13.3484 19.3945 12.6959 19.3945 12C19.3945 11.0633 19.2645 10.2239 19.0059 9.4834C18.7471 8.73409 18.3497 8.14029 17.8145 7.70312C17.2791 7.2661 16.601 7.04787 15.7803 7.04785ZM8.61035 7.04785C7.78948 7.0479 7.08402 7.23462 6.49512 7.60938C5.96734 7.95049 5.51371 8.40294 5.13379 8.96582C5.07169 9.05804 5.1014 9.18229 5.19629 9.24023L6.32324 9.92676C6.41808 9.98426 6.54109 9.95335 6.60156 9.86035C6.82182 9.52045 7.0595 9.24305 7.3125 9.02832C7.6158 8.77873 7.99062 8.65433 8.43652 8.6543C8.68639 8.6543 8.90538 8.70262 9.09277 8.80078C9.28897 8.89892 9.4408 9.03745 9.54785 9.21582C9.66375 9.39425 9.72168 9.6134 9.72168 9.87207C9.72163 10.0682 9.69034 10.2515 9.62793 10.4209C9.57441 10.5813 9.49363 10.7419 9.38672 10.9023C9.2886 11.0539 9.16329 11.2106 9.01172 11.3711C8.86903 11.5316 8.70859 11.7063 8.53027 11.8936L5.39062 14.9932C5.35259 15.0307 5.33108 15.0823 5.33105 15.1357V16.4854C5.33121 16.5957 5.42089 16.6855 5.53125 16.6855H11.5957C11.7061 16.6855 11.7957 16.5957 11.7959 16.4854V15.252C11.7957 15.1416 11.7061 15.0527 11.5957 15.0527H8.02246C7.84268 15.0526 7.75441 14.8329 7.88379 14.708L9.78809 12.8701C10.1003 12.5668 10.3868 12.2547 10.6455 11.9336C10.913 11.6037 11.1313 11.2552 11.3008 10.8896C11.4702 10.524 11.5556 10.1263 11.5557 9.69824C11.5557 9.16294 11.4219 8.69826 11.1543 8.30566C10.8955 7.90413 10.5428 7.59601 10.0967 7.38184C9.6594 7.15874 9.16364 7.04785 8.61035 7.04785Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM12.1592 6.87402C11.3742 6.87405 10.7045 7.03616 10.1504 7.35938C9.6056 7.68261 9.1531 8.12617 8.79297 8.68945L10.3164 9.64453C10.5195 9.29373 10.7557 9.02105 11.0234 8.82715C11.3004 8.63343 11.6329 8.53715 12.0205 8.53711C12.2789 8.53711 12.5054 8.58259 12.6992 8.6748C12.8931 8.75791 13.0454 8.88804 13.1562 9.06348C13.2763 9.2297 13.3369 9.43721 13.3369 9.68652C13.3369 10.0559 13.2024 10.3516 12.9346 10.5732C12.6668 10.7854 12.3299 10.8916 11.9238 10.8916H10.4551V12.5537H12.0479C12.3987 12.5537 12.6999 12.6144 12.9492 12.7344C13.2076 12.8452 13.4065 13.0119 13.5449 13.2334C13.6925 13.4457 13.7656 13.7043 13.7656 14.0088C13.7656 14.2949 13.6925 14.5489 13.5449 14.7705C13.4064 14.9921 13.2121 15.1632 12.9629 15.2832C12.7228 15.4032 12.4404 15.4629 12.1172 15.4629C11.6372 15.4628 11.2263 15.3381 10.8848 15.0889C10.5524 14.8395 10.2477 14.4519 9.9707 13.9258L8.33594 14.9365C8.73304 15.6291 9.23621 16.1697 9.8457 16.5576C10.4552 16.9362 11.2176 17.126 12.1318 17.126C12.8058 17.1259 13.4063 17.0012 13.9326 16.752C14.4682 16.5026 14.8886 16.1463 15.1934 15.6846C15.5071 15.2229 15.664 14.6874 15.6641 14.0781C15.6641 13.6256 15.567 13.2141 15.373 12.8447C15.1884 12.4663 14.9157 12.1481 14.5557 11.8896C14.4278 11.7946 14.2894 11.7091 14.1406 11.6338C14.2229 11.5787 14.3023 11.5218 14.376 11.46C14.6622 11.2107 14.8786 10.9148 15.0264 10.5732C15.1741 10.2315 15.248 9.86197 15.248 9.46484C15.248 8.93854 15.1144 8.48159 14.8467 8.09375C14.5789 7.70592 14.2092 7.40576 13.7383 7.19336C13.2766 6.98099 12.7502 6.87403 12.1592 6.87402Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.8652 9.5459C11.9822 9.39576 12.2236 9.47861 12.2236 9.66895V12.1562C12.2236 12.2666 12.1337 12.3563 12.0234 12.3564H10.085C9.91847 12.3564 9.82447 12.1645 9.92676 12.0332L11.8652 9.5459Z" fill="#222631"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM12.0176 6.92578C11.9553 6.92578 11.8962 6.95448 11.8584 7.00391L7.80371 12.3027C7.77706 12.3376 7.76272 12.38 7.7627 12.4238V13.8457C7.76272 13.956 7.85161 14.0457 7.96191 14.0459H12.0234C12.1338 14.0461 12.2236 14.1357 12.2236 14.2461V16.4229C12.2237 16.5332 12.3125 16.6229 12.4229 16.623H13.8936C14.0039 16.6229 14.0937 16.5332 14.0938 16.4229V14.2461C14.0938 14.1357 14.1826 14.0461 14.293 14.0459H14.877C14.9873 14.0457 15.0771 13.956 15.0771 13.8457V12.5566C15.0771 12.4463 14.9873 12.3566 14.877 12.3564H14.293C14.1827 12.3563 14.0938 12.2666 14.0938 12.1562V7.12598C14.0938 7.01562 14.0039 6.92594 13.8936 6.92578H12.0176Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
src/frontend/apps/impress/src/assets/icons/ui-kit/4k.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 15H14.5V12.75L16.25 15H18.075L15.75 12L18.075 9H16.25L14.5 11.25V9H13V15ZM9.5 15H11V13.5H12V12H11V9H9.5V12H8V9H6.5V13.5H9.5V15ZM5 21C4.45 21 3.97917 20.8042 3.5875 20.4125C3.19583 20.0208 3 19.55 3 19V5C3 4.45 3.19583 3.97917 3.5875 3.5875C3.97917 3.19583 4.45 3 5 3H19C19.55 3 20.0208 3.19583 20.4125 3.5875C20.8042 3.97917 21 4.45 21 5V19C21 19.55 20.8042 20.0208 20.4125 20.4125C20.0208 20.8042 19.55 21 19 21H5ZM5 19H19V5H5V19Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 566 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2ZM9.66211 7.0127C9.5559 7.01288 9.46849 7.09608 9.46289 7.20215L9.20605 12.1914C9.20036 12.3055 9.29109 12.4011 9.40527 12.4014H11.8135C12.2198 12.4014 12.5618 12.462 12.8389 12.582C13.1156 12.6928 13.3279 12.8587 13.4756 13.0801C13.6233 13.2925 13.6973 13.5561 13.6973 13.8701C13.6972 14.1562 13.6286 14.4103 13.4902 14.6318C13.3518 14.8534 13.1574 15.0245 12.9082 15.1445C12.6681 15.2646 12.3857 15.3242 12.0625 15.3242C11.5918 15.3242 11.1808 15.2047 10.8301 14.9648C10.5301 14.7459 10.2542 14.4162 10.0039 13.9766C9.94623 13.8757 9.81669 13.8394 9.71777 13.9004L8.42969 14.6963C8.33839 14.7528 8.30807 14.8723 8.36426 14.9639C8.74594 15.5856 9.22193 16.0707 9.79102 16.4189C10.4097 16.7975 11.1669 16.9872 12.0625 16.9873C12.7274 16.9873 13.3286 16.8572 13.8643 16.5986C14.3994 16.3401 14.8194 15.971 15.124 15.4912C15.438 15.011 15.5957 14.4427 15.5957 13.7871C15.5957 13.1592 15.4665 12.6185 15.208 12.166C14.9494 11.7135 14.5468 11.3626 14.002 11.1133C13.4664 10.864 12.7644 10.7393 11.8965 10.7393H11.3594C11.2447 10.7392 11.1539 10.6429 11.1602 10.5283L11.249 8.89258C11.2548 8.78652 11.343 8.70312 11.4492 8.70312H15.0488C15.1593 8.70312 15.249 8.61339 15.249 8.50293V7.21289C15.249 7.10243 15.1593 7.0127 15.0488 7.0127H9.66211Z" fill="#222631"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |