Compare commits
15 Commits
fix/kan-41
...
fix/migrat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e48f16ade6 | ||
|
|
eeae23a24c | ||
|
|
d81950b8bd | ||
|
|
e530f39360 | ||
|
|
bd25fb33f7 | ||
|
|
93f2816b37 | ||
|
|
2292b972c4 | ||
|
|
af36fb133a | ||
|
|
9cb1fb5218 | ||
|
|
5f190b92de | ||
|
|
48a1f39588 | ||
|
|
c7fce10fa7 | ||
|
|
094e1d139d | ||
|
|
fae169c691 | ||
|
|
91bc319cf1 |
37
.github/workflows/docker-publish.yml
vendored
37
.github/workflows/docker-publish.yml
vendored
@@ -26,6 +26,7 @@ env:
|
||||
REGISTRY: ghcr.io
|
||||
# github.repository as <account>/<repo>
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
MIGRATE_IMAGE_NAME: ${{ github.repository }}-migrate
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -85,6 +86,19 @@ jobs:
|
||||
type=semver,pattern={{major}}
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Extract Docker metadata (migrate)
|
||||
id: meta-migrate
|
||||
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.MIGRATE_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
# Extract version from git tag or ref
|
||||
# Uses git describe to get latest tag + commit hash in SemVer format: 1.2.3+abc1234
|
||||
- name: Extract version
|
||||
@@ -138,6 +152,22 @@ jobs:
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Build and push migrate Docker image
|
||||
id: build-and-push-migrate
|
||||
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
|
||||
with:
|
||||
context: .
|
||||
file: apps/web/Dockerfile
|
||||
target: migrate
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta-migrate.outputs.tags }}
|
||||
labels: ${{ steps.meta-migrate.outputs.labels }}
|
||||
build-args: |
|
||||
APP_VERSION=${{ steps.version.outputs.version }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
# Sign the resulting Docker image digest except on PRs.
|
||||
# This will only write to the public Rekor transparency log when the Docker
|
||||
# repository is public to avoid leaking data. If you would like to publish
|
||||
@@ -152,3 +182,10 @@ jobs:
|
||||
# This step uses the identity token to provision an ephemeral certificate
|
||||
# against the sigstore community Fulcio instance.
|
||||
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
|
||||
|
||||
- name: Sign the published migrate Docker image
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
env:
|
||||
TAGS: ${{ steps.meta-migrate.outputs.tags }}
|
||||
DIGEST: ${{ steps.build-and-push-migrate.outputs.digest }}
|
||||
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
|
||||
|
||||
23
.github/workflows/translate.yml
vendored
23
.github/workflows/translate.yml
vendored
@@ -14,16 +14,24 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --filter @kan/web...
|
||||
|
||||
- name: Extract translations
|
||||
working-directory: apps/web
|
||||
run: npx --yes @lingui/cli extract
|
||||
|
||||
run: pnpm lingui:extract
|
||||
|
||||
- name: Lingo.dev
|
||||
uses: lingodotdev/lingo.dev@main
|
||||
with:
|
||||
@@ -33,11 +41,11 @@ jobs:
|
||||
commit-author-name: "${{ github.actor }}"
|
||||
commit-author-email: "${{ github.actor }}@users.noreply.github.com"
|
||||
parallel: true
|
||||
|
||||
|
||||
- name: Compile translations
|
||||
working-directory: apps/web
|
||||
run: npx --yes @lingui/cli compile --typescript
|
||||
|
||||
run: pnpm lingui:compile
|
||||
|
||||
- name: Commit compiled translations
|
||||
run: |
|
||||
git config --local user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
@@ -46,4 +54,3 @@ jobs:
|
||||
git add apps/web/src/locales/*/messages.ts
|
||||
git diff --staged --quiet || (git commit -m "chore: compile translations" && git push)
|
||||
fi
|
||||
|
||||
|
||||
56
README.md
56
README.md
@@ -57,39 +57,61 @@ The easiest way to deploy Kan is through Railway. We've partnered with Railway t
|
||||
|
||||
### Docker Compose
|
||||
|
||||
Alternatively, you can self-host Kan with Docker Compose. This will set up everything for you including your postgres database.
|
||||
Alternatively, you can self-host Kan with Docker Compose. This will set up everything for you including your postgres database and automatically run migrations.
|
||||
|
||||
1. Create a new file called `docker-compose.yml` and paste the following configuration:
|
||||
1. Create a `.env` file with your environment variables (see [Environment Variables](#environment-variables-) section below)
|
||||
|
||||
2. Use the provided `docker-compose.yml` file or create your own with the following configuration:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
migrate:
|
||||
image: ghcr.io/kanbn/kan-migrate:latest
|
||||
container_name: kan-migrate
|
||||
networks:
|
||||
- kan-network
|
||||
environment:
|
||||
- POSTGRES_URL=${POSTGRES_URL}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
restart: "no"
|
||||
|
||||
web:
|
||||
image: ghcr.io/kanbn/kan:latest
|
||||
container_name: kan-web
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "${WEB_PORT:-3000}:3000"
|
||||
networks:
|
||||
- kan-network
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
NEXT_PUBLIC_BASE_URL: http://localhost:3000
|
||||
BETTER_AUTH_SECRET: your_auth_secret
|
||||
POSTGRES_URL: postgresql://kan:your_postgres_password@postgres:5432/kan_db
|
||||
NEXT_PUBLIC_ALLOW_CREDENTIALS: true
|
||||
- NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL}
|
||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
|
||||
- POSTGRES_URL=${POSTGRES_URL}
|
||||
- NEXT_PUBLIC_ALLOW_CREDENTIALS=true
|
||||
depends_on:
|
||||
- postgres
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: postgres:15
|
||||
container_name: kan-db
|
||||
environment:
|
||||
POSTGRES_DB: kan_db
|
||||
POSTGRES_USER: kan
|
||||
POSTGRES_PASSWORD: your_postgres_password
|
||||
- POSTGRES_DB=kan_db
|
||||
- POSTGRES_USER=kan
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- kan_postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U kan -d kan_db"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- kan-network
|
||||
@@ -101,23 +123,23 @@ volumes:
|
||||
kan_postgres_data:
|
||||
```
|
||||
|
||||
2. Start the containers in detached mode:
|
||||
3. Start the containers in detached mode:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
3. Access Kan at http://localhost:3000
|
||||
The `migrate` service will automatically run database migrations before the web service starts. The application will be available at http://localhost:3000 (or the port specified in `WEB_PORT`).
|
||||
|
||||
The application will be running in the background. You can manage the containers using these commands:
|
||||
**Managing containers:**
|
||||
|
||||
- To stop the containers: `docker compose down`
|
||||
- To view logs: `docker compose logs -f`
|
||||
- To view logs for a specific service: `docker compose logs -f web` or `docker compose logs -f migrate`
|
||||
- To restart the containers: `docker compose restart`
|
||||
- To rebuild after code changes: `docker compose up -d --build`
|
||||
|
||||
For the complete Docker Compose configuration, see [docker-compose.yml](./docker-compose.yml) in the repository.
|
||||
|
||||
> **Note**: The Docker Compose configuration shown above is a minimal example. For a complete setup with all features (email, OAuth, file uploads, etc.), you'll need to create a `.env` file with the required environment variables. See the Environment Variables section below for the full list of available options.
|
||||
For the complete Docker Compose configuration with all optional features, see [docker-compose.yml](./docker-compose.yml) in the repository.
|
||||
|
||||
## Local Development 🧑💻
|
||||
|
||||
|
||||
@@ -81,6 +81,9 @@ checksums:
|
||||
7L01XJ/message: c46571856723b03262fd33f511116298
|
||||
7L01XJ/origin/0/0: feacdaf2791aaddec128f463ffa2e807
|
||||
7L01XJ/translation: c46571856723b03262fd33f511116298
|
||||
F6pfE9/translation: 3e1ec025c4a50830bbb9ad57a176630a
|
||||
F6pfE9/message: 3e1ec025c4a50830bbb9ad57a176630a
|
||||
F6pfE9/origin/0/0: 4b563046ba98abb3777fb1c3560866aa
|
||||
XJOV1Y/message: 1948763de8e531483a798b68195e297e
|
||||
XJOV1Y/origin/0/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
XJOV1Y/origin/1/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
@@ -245,6 +248,12 @@ checksums:
|
||||
yb%2Ffjw/message: 2f5426445800ecd5a3102aac940d459c
|
||||
yb%2Ffjw/origin/0/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
yb%2Ffjw/translation: 2f5426445800ecd5a3102aac940d459c
|
||||
aKJHG%2B/translation: 63de765f7fd7bc5238a3e36532306b6b
|
||||
aKJHG%2B/message: 63de765f7fd7bc5238a3e36532306b6b
|
||||
aKJHG%2B/origin/0/0: 0ad2af981579a62b084b3d41462c5546
|
||||
TdfEV7/translation: cf5127ecfd7e43a35466a1ba5fe16450
|
||||
TdfEV7/message: cf5127ecfd7e43a35466a1ba5fe16450
|
||||
TdfEV7/origin/0/0: 4b563046ba98abb3777fb1c3560866aa
|
||||
lo8xBK/message: f9b67026c5ef86ac4d5944380d571961
|
||||
lo8xBK/placeholders/entityLabel/0: 5745fa1af326b9ac2b140cbbef90cdeb
|
||||
lo8xBK/origin/0/0: 68450df287fb46ea629e818732c1291a
|
||||
@@ -332,6 +341,9 @@ checksums:
|
||||
YhvWXb/origin/0/0: ff4a160eca5f8da4f075fd1dd6061fa6
|
||||
YhvWXb/origin/1/0: dbad0c8d7863cb41f5495264f82e7081
|
||||
YhvWXb/translation: 336a8c70a91adc49c15266167ee84cc0
|
||||
i0ZMQl/translation: f739058266a2cb6a3d7cca14de09c69a
|
||||
i0ZMQl/message: f739058266a2cb6a3d7cca14de09c69a
|
||||
i0ZMQl/origin/0/0: 0ad2af981579a62b084b3d41462c5546
|
||||
wewm3j/message: 985c0177744ec56d8dac84f66ec284ca
|
||||
wewm3j/origin/0/0: bcd95e286f12800a3e7ee0a71feb3ca5
|
||||
wewm3j/translation: 985c0177744ec56d8dac84f66ec284ca
|
||||
@@ -344,6 +356,9 @@ checksums:
|
||||
XNxYxq/message: b0e6a79e1751b734e1060a1034a893d6
|
||||
XNxYxq/origin/0/0: c0b59968b2b234167c488a05cc38710b
|
||||
XNxYxq/translation: b0e6a79e1751b734e1060a1034a893d6
|
||||
tbNcu4/translation: 3817a16b436504bb8d96ca5ee62078cc
|
||||
tbNcu4/message: 3817a16b436504bb8d96ca5ee62078cc
|
||||
tbNcu4/origin/0/0: 0ad2af981579a62b084b3d41462c5546
|
||||
Xid3K6/message: ba018946ffdf85e55d701a9c27595b4d
|
||||
Xid3K6/origin/0/0: 2e937f7560901c3de71a592b4fd80f34
|
||||
Xid3K6/translation: ba018946ffdf85e55d701a9c27595b4d
|
||||
@@ -368,6 +383,9 @@ checksums:
|
||||
8TveY%2B/origin/0/0: 94d3e20c29bb517dd394711d7524e478
|
||||
8TveY%2B/origin/1/0: c0b59968b2b234167c488a05cc38710b
|
||||
8TveY%2B/translation: 8fff51c5872e051beb9b718336665e80
|
||||
0RE1AJ/translation: c9a1699f5037acf87aaa6515d0f16910
|
||||
0RE1AJ/message: c9a1699f5037acf87aaa6515d0f16910
|
||||
0RE1AJ/origin/0/0: f78c0a7870158f5dab5216bcd6092e49
|
||||
ZDo4HN/message: 736332f2e4488609e42d2be8547d296e
|
||||
ZDo4HN/origin/0/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
ZDo4HN/translation: 736332f2e4488609e42d2be8547d296e
|
||||
@@ -1500,6 +1518,9 @@ checksums:
|
||||
"%2FglL9Q/placeholders/0/0": 07bd981d42ffe27ee81d1dffa7dc0d43
|
||||
"%2FglL9Q/origin/0/0": f78c0a7870158f5dab5216bcd6092e49
|
||||
"%2FglL9Q/translation": a7a95e1dfdb4396da9e2be0e27f42323
|
||||
tlhgDC/translation: b96a6f8fa3267ec01e68e61656382b2e
|
||||
tlhgDC/message: b96a6f8fa3267ec01e68e61656382b2e
|
||||
tlhgDC/origin/0/0: f78c0a7870158f5dab5216bcd6092e49
|
||||
Eg99Sc/message: f718c896810da3612202887f9a4374fa
|
||||
Eg99Sc/origin/0/0: d96af8a06c2e38a72238bcd16e426859
|
||||
Eg99Sc/translation: f718c896810da3612202887f9a4374fa
|
||||
@@ -2052,6 +2073,12 @@ checksums:
|
||||
nhMhRr/message: 07edd8c50685a52c0969d711df26d768
|
||||
nhMhRr/origin/0/0: 1fd8ccf586705354bcae6e1186108fc7
|
||||
nhMhRr/translation: 07edd8c50685a52c0969d711df26d768
|
||||
NcFrgC/translation: bd15a7b3d8b4bce18cd42682ea1ec164
|
||||
NcFrgC/message: bd15a7b3d8b4bce18cd42682ea1ec164
|
||||
NcFrgC/origin/0/0: 0ad2af981579a62b084b3d41462c5546
|
||||
C6gv54/translation: 1573c77059bc92df17e661befda8d9b7
|
||||
C6gv54/message: 1573c77059bc92df17e661befda8d9b7
|
||||
C6gv54/origin/0/0: 0ad2af981579a62b084b3d41462c5546
|
||||
jlB2RY/message: 67a76bf346ab4b48563269e9d941a64a
|
||||
jlB2RY/origin/0/0: 0627e0040ca4939c9a57349e98c51797
|
||||
jlB2RY/translation: 67a76bf346ab4b48563269e9d941a64a
|
||||
@@ -2249,6 +2276,9 @@ checksums:
|
||||
TFv5tM/message: 4f2240aeff6f7275feb33ca3f608e48c
|
||||
TFv5tM/origin/0/0: 7493592314bc3b96ad96127a949ba1ba
|
||||
TFv5tM/translation: 4f2240aeff6f7275feb33ca3f608e48c
|
||||
It4%2FFS/translation: ae1527c2765592f13980acb66997e570
|
||||
It4%2FFS/message: ae1527c2765592f13980acb66997e570
|
||||
It4%2FFS/origin/0/0: 0ad2af981579a62b084b3d41462c5546
|
||||
E8zYtd/message: b683cc07092348c1e75dba40b1262b85
|
||||
E8zYtd/placeholders/0/0: 182720128698512d43d1bf43824cd7f8
|
||||
E8zYtd/origin/0/0: 1d6b5f88cdad160a67842f6b65387448
|
||||
|
||||
@@ -463,13 +463,6 @@ export default function Editor({
|
||||
StarterKit.configure({
|
||||
heading: disableHeadings ? false : undefined,
|
||||
}),
|
||||
Markdown,
|
||||
Placeholder.configure({
|
||||
placeholder: readOnly
|
||||
? ""
|
||||
: placeholder ??
|
||||
t`Add description... (type '/' to open commands or '@' to mention)`,
|
||||
}),
|
||||
Link.configure({
|
||||
openOnClick: true,
|
||||
HTMLAttributes: {
|
||||
@@ -479,6 +472,14 @@ export default function Editor({
|
||||
},
|
||||
validate: (href) => /^https?:\/\//.test(href),
|
||||
autolink: true,
|
||||
linkOnPaste: true,
|
||||
}),
|
||||
Markdown,
|
||||
Placeholder.configure({
|
||||
placeholder: readOnly
|
||||
? ""
|
||||
: placeholder ??
|
||||
t`Add description... (type '/' to open commands or '@' to mention)`,
|
||||
}),
|
||||
SlashCommands.configure({
|
||||
commandItems: getCommandItems(disableHeadings),
|
||||
|
||||
@@ -19,6 +19,7 @@ interface UsePermissionsResult {
|
||||
canCreateBoard: boolean;
|
||||
canEditBoard: boolean;
|
||||
canDeleteBoard: boolean;
|
||||
canArchiveBoard: boolean;
|
||||
canViewComment: boolean;
|
||||
canCreateComment: boolean;
|
||||
canEditComment: boolean;
|
||||
@@ -33,7 +34,7 @@ interface UsePermissionsResult {
|
||||
export function usePermissions(): UsePermissionsResult {
|
||||
// Check if WorkspaceProvider is available (for public board views, it may not be)
|
||||
const workspaceContext = useContext(WorkspaceContext);
|
||||
|
||||
|
||||
// If WorkspaceProvider is not available, return safe defaults
|
||||
if (!workspaceContext) {
|
||||
const emptyPermissions: UsePermissionsResult = {
|
||||
@@ -51,6 +52,7 @@ export function usePermissions(): UsePermissionsResult {
|
||||
canCreateBoard: false,
|
||||
canEditBoard: false,
|
||||
canDeleteBoard: false,
|
||||
canArchiveBoard: false,
|
||||
canViewComment: false,
|
||||
canCreateComment: false,
|
||||
canEditComment: false,
|
||||
@@ -95,6 +97,7 @@ export function usePermissions(): UsePermissionsResult {
|
||||
canCreateBoard: hasPermission("board:create"),
|
||||
canEditBoard: hasPermission("board:edit"),
|
||||
canDeleteBoard: hasPermission("board:delete"),
|
||||
canArchiveBoard: hasPermission("board:edit"),
|
||||
canViewComment: hasPermission("comment:view"),
|
||||
canCreateComment: hasPermission("comment:create"),
|
||||
canEditComment: hasPermission("comment:edit"),
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"0": ["isTemplate ? \"Templates\" : \"Boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 40]],
|
||||
"origin": [["src/views/boards/index.tsx", 53]],
|
||||
"translation": "{0}"
|
||||
},
|
||||
"JArWcF": {
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/card/index.tsx", 309]
|
||||
],
|
||||
"translation": "{0} | {1}"
|
||||
@@ -44,7 +44,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 56]],
|
||||
"translation": "{0} wurde zu deinen Favoriten hinzugefügt."
|
||||
},
|
||||
"bDI6VI": {
|
||||
@@ -53,7 +53,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 55]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 57]],
|
||||
"translation": "{0} wurde aus deinen Favoriten entfernt."
|
||||
},
|
||||
"CJukzS": {
|
||||
@@ -185,6 +185,13 @@
|
||||
"origin": [["src/providers/keyboard-shortcuts.tsx", 56]],
|
||||
"translation": "Aktionen"
|
||||
},
|
||||
"F6pfE9": {
|
||||
"message": "Active",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 26]],
|
||||
"translation": "Aktiv"
|
||||
},
|
||||
"XJOV1Y": {
|
||||
"message": "Activity",
|
||||
"placeholders": {},
|
||||
@@ -246,7 +253,7 @@
|
||||
"message": "Add description... (type '/' to open commands or '@' to mention)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/Editor.tsx", 471]],
|
||||
"origin": [["src/components/Editor.tsx", 482]],
|
||||
"translation": "Beschreibung hinzufügen... (‚/' für Befehle oder ‚@' zum Erwähnen eingeben)"
|
||||
},
|
||||
"abUZlY": {
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 93]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 122]],
|
||||
"translation": "Zu Favoriten hinzufügen"
|
||||
},
|
||||
"bmXKoX": {
|
||||
@@ -368,7 +375,7 @@
|
||||
"message": "Added to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 51]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 53]],
|
||||
"translation": "Zu Favoriten hinzugefügt"
|
||||
},
|
||||
"14Xi3Z": {
|
||||
@@ -482,7 +489,7 @@
|
||||
"message": "Already have an account? <0><1>Sign in</1></0>",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 88]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 90]],
|
||||
"translation": "Haben Sie bereits ein Konto? <0><1>Anmelden</1></0>"
|
||||
},
|
||||
"WmPhYW": {
|
||||
@@ -574,6 +581,20 @@
|
||||
"origin": [["src/views/boards/components/TemplateBoards.tsx", 46]],
|
||||
"translation": "Genehmigung"
|
||||
},
|
||||
"aKJHG+": {
|
||||
"message": "Archive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Board archivieren"
|
||||
},
|
||||
"TdfEV7": {
|
||||
"message": "Archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 27]],
|
||||
"translation": "Archiviert"
|
||||
},
|
||||
"lo8xBK": {
|
||||
"message": "Are you sure want to remove {entityLabel}?",
|
||||
"placeholders": {
|
||||
@@ -785,6 +806,13 @@
|
||||
],
|
||||
"translation": "Board-Analyse"
|
||||
},
|
||||
"i0ZMQl": {
|
||||
"message": "Board archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Board archiviert"
|
||||
},
|
||||
"wewm3j": {
|
||||
"message": "Board name cannot exceed 100 characters",
|
||||
"placeholders": {},
|
||||
@@ -815,6 +843,13 @@
|
||||
],
|
||||
"translation": "Board-Vorlagen"
|
||||
},
|
||||
"tbNcu4": {
|
||||
"message": "Board unarchived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Board wiederhergestellt"
|
||||
},
|
||||
"Xid3K6": {
|
||||
"message": "Board URL can only contain letters, numbers, and hyphens",
|
||||
"placeholders": {},
|
||||
@@ -873,6 +908,13 @@
|
||||
],
|
||||
"translation": "Boards"
|
||||
},
|
||||
"0RE1AJ": {
|
||||
"message": "Boards you archive will appear here.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Boards, die du archivierst, werden hier angezeigt."
|
||||
},
|
||||
"ZDo4HN": {
|
||||
"message": "Brainstorming",
|
||||
"placeholders": {},
|
||||
@@ -1237,7 +1279,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 43],
|
||||
["src/views/auth/signup/index.tsx", 69]
|
||||
["src/views/auth/signup/index.tsx", 71]
|
||||
],
|
||||
"translation": "Überprüfen Sie Ihren Posteingang"
|
||||
},
|
||||
@@ -1305,7 +1347,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 48],
|
||||
["src/views/auth/signup/index.tsx", 74]
|
||||
["src/views/auth/signup/index.tsx", 76]
|
||||
],
|
||||
"translation": "Klicken Sie auf den Link, den wir an {magicLinkRecipient} gesendet haben, um sich anzumelden."
|
||||
},
|
||||
@@ -1612,8 +1654,8 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/components/BoardsList.tsx", 79],
|
||||
["src/views/boards/index.tsx", 28]
|
||||
["src/views/boards/components/BoardsList.tsx", 80],
|
||||
["src/views/boards/index.tsx", 41]
|
||||
],
|
||||
"translation": "Neue {0} erstellen"
|
||||
},
|
||||
@@ -1835,7 +1877,7 @@
|
||||
"message": "Delete board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Board löschen"
|
||||
},
|
||||
"nabda1": {
|
||||
@@ -1863,7 +1905,7 @@
|
||||
"message": "Delete template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Vorlage löschen"
|
||||
},
|
||||
"kYu0eF": {
|
||||
@@ -2079,7 +2121,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/BoardDropdown.tsx", 84],
|
||||
["src/views/board/components/BoardDropdown.tsx", 102],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 116]
|
||||
],
|
||||
"translation": "Board-URL bearbeiten"
|
||||
@@ -2622,7 +2664,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 69],
|
||||
["src/views/auth/signup/index.tsx", 71],
|
||||
["src/views/home/components/Cta.tsx", 61],
|
||||
["src/views/home/components/Header.tsx", 106],
|
||||
["src/views/pricing/components/PricingTiers.tsx", 29],
|
||||
@@ -2647,7 +2689,7 @@
|
||||
"0": ["isTemplate ? \"template\" : \"board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 65]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Legen Sie los, indem Sie ein neues {0} erstellen"
|
||||
},
|
||||
"zC8Whw": {
|
||||
@@ -2863,7 +2905,7 @@
|
||||
"message": "Import",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 60]],
|
||||
"origin": [["src/views/boards/index.tsx", 73]],
|
||||
"translation": "Importieren"
|
||||
},
|
||||
"2MPcep": {
|
||||
@@ -3299,7 +3341,7 @@
|
||||
"message": "Make template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 73]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Vorlage erstellen"
|
||||
},
|
||||
"IDvohQ": {
|
||||
@@ -3432,7 +3474,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 82],
|
||||
["src/views/boards/index.tsx", 95],
|
||||
["src/views/home/components/Features.tsx", 73]
|
||||
],
|
||||
"translation": "Neu"
|
||||
@@ -3550,9 +3592,16 @@
|
||||
"0": ["isTemplate ? \"templates\" : \"boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 62]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Keine {0}"
|
||||
},
|
||||
"tlhgDC": {
|
||||
"message": "No archived boards",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Keine archivierten Boards"
|
||||
},
|
||||
"Eg99Sc": {
|
||||
"message": "No authentication methods are currently available",
|
||||
"placeholders": {},
|
||||
@@ -3963,7 +4012,7 @@
|
||||
["src/components/DeleteLabelConfirmation.tsx", 26],
|
||||
["src/components/FeedbackModal.tsx", 41],
|
||||
["src/components/NewWorkspaceForm.tsx", 158],
|
||||
["src/views/board/components/BoardDropdown.tsx", 63],
|
||||
["src/views/board/components/BoardDropdown.tsx", 65],
|
||||
["src/views/board/components/NewCardForm.tsx", 184],
|
||||
["src/views/board/components/NewListForm.tsx", 79],
|
||||
["src/views/board/components/NewTemplateForm.tsx", 61],
|
||||
@@ -4178,7 +4227,7 @@
|
||||
"message": "Remove from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 92]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 121]],
|
||||
"translation": "Aus Favoriten entfernen"
|
||||
},
|
||||
"99VIgC": {
|
||||
@@ -4232,7 +4281,7 @@
|
||||
"message": "Removed from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 52]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"translation": "Aus Favoriten entfernt"
|
||||
},
|
||||
"YVT40D": {
|
||||
@@ -4570,8 +4619,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 34],
|
||||
["src/views/auth/signup/index.tsx", 59]
|
||||
["src/views/auth/signup/index.tsx", 36],
|
||||
["src/views/auth/signup/index.tsx", 61]
|
||||
],
|
||||
"translation": "Registrieren | kan.bn"
|
||||
},
|
||||
@@ -4579,14 +4628,14 @@
|
||||
"message": "Sign up disabled",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 44]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 46]],
|
||||
"translation": "Registrierung deaktiviert"
|
||||
},
|
||||
"s6iE/c": {
|
||||
"message": "Sign up is currently disabled. Please try again later.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 47]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 49]],
|
||||
"translation": "Die Registrierung ist derzeit deaktiviert. Bitte versuchen Sie es später erneut."
|
||||
},
|
||||
"6FDocz": {
|
||||
@@ -4809,6 +4858,20 @@
|
||||
"origin": [["src/components/FeedbackModal.tsx", 34]],
|
||||
"translation": "Vielen Dank für Ihr Feedback!"
|
||||
},
|
||||
"NcFrgC": {
|
||||
"message": "The board has been archived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 46]],
|
||||
"translation": "Das Board wurde archiviert."
|
||||
},
|
||||
"C6gv54": {
|
||||
"message": "The board has been unarchived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Das Board wurde wiederhergestellt."
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5196,7 +5259,7 @@
|
||||
"message": "Unable to update board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 62]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 64]],
|
||||
"translation": "Board konnte nicht aktualisiert werden"
|
||||
},
|
||||
"XpcjLO": {
|
||||
@@ -5292,6 +5355,13 @@
|
||||
"origin": [["src/views/members/index.tsx", 65]],
|
||||
"translation": "Rolle konnte nicht aktualisiert werden"
|
||||
},
|
||||
"It4/FS": {
|
||||
"message": "Unarchive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Board wiederherstellen"
|
||||
},
|
||||
"E8zYtd": {
|
||||
"message": "unassigned <0>{0}</0> from the card",
|
||||
"placeholders": {
|
||||
@@ -5671,7 +5741,7 @@
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 46],
|
||||
["src/views/board/index.tsx", 400],
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
["src/views/public/boards/index.tsx", 65]
|
||||
@@ -5883,9 +5953,9 @@
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 474],
|
||||
["src/views/board/index.tsx", 532],
|
||||
["src/views/boards/components/BoardsList.tsx", 70],
|
||||
["src/views/boards/index.tsx", 46],
|
||||
["src/views/boards/index.tsx", 67]
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
],
|
||||
"translation": "Sie haben keine Berechtigung"
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -34,7 +34,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/boards/index.tsx",
|
||||
40
|
||||
53
|
||||
]
|
||||
],
|
||||
"translation": "{0}"
|
||||
@@ -55,7 +55,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/boards/index.tsx",
|
||||
35
|
||||
48
|
||||
],
|
||||
[
|
||||
"src/views/card/index.tsx",
|
||||
@@ -75,7 +75,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
54
|
||||
56
|
||||
]
|
||||
],
|
||||
"translation": "{0} has been added to your favorites."
|
||||
@@ -91,7 +91,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
55
|
||||
57
|
||||
]
|
||||
],
|
||||
"translation": "{0} has been removed from your favorites."
|
||||
@@ -312,6 +312,18 @@
|
||||
],
|
||||
"translation": "Actions"
|
||||
},
|
||||
"F6pfE9": {
|
||||
"message": "Active",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/boards/index.tsx",
|
||||
26
|
||||
]
|
||||
],
|
||||
"translation": "Active"
|
||||
},
|
||||
"XJOV1Y": {
|
||||
"message": "Activity",
|
||||
"placeholders": {},
|
||||
@@ -411,7 +423,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/components/Editor.tsx",
|
||||
471
|
||||
482
|
||||
]
|
||||
],
|
||||
"translation": "Add description... (type '/' to open commands or '@' to mention)"
|
||||
@@ -467,7 +479,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
93
|
||||
122
|
||||
]
|
||||
],
|
||||
"translation": "Add to favorites"
|
||||
@@ -622,7 +634,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
51
|
||||
53
|
||||
]
|
||||
],
|
||||
"translation": "Added to favorites"
|
||||
@@ -813,7 +825,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/auth/signup/index.tsx",
|
||||
88
|
||||
90
|
||||
]
|
||||
],
|
||||
"translation": "Already have an account? <0><1>Sign in</1></0>"
|
||||
@@ -966,6 +978,30 @@
|
||||
],
|
||||
"translation": "Approval"
|
||||
},
|
||||
"aKJHG+": {
|
||||
"message": "Archive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
111
|
||||
]
|
||||
],
|
||||
"translation": "Archive board"
|
||||
},
|
||||
"TdfEV7": {
|
||||
"message": "Archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/boards/index.tsx",
|
||||
27
|
||||
]
|
||||
],
|
||||
"translation": "Archived"
|
||||
},
|
||||
"lo8xBK": {
|
||||
"message": "Are you sure want to remove {entityLabel}?",
|
||||
"placeholders": {
|
||||
@@ -1314,6 +1350,18 @@
|
||||
],
|
||||
"translation": "Board analytics"
|
||||
},
|
||||
"i0ZMQl": {
|
||||
"message": "Board archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
44
|
||||
]
|
||||
],
|
||||
"translation": "Board archived"
|
||||
},
|
||||
"wewm3j": {
|
||||
"message": "Board name cannot exceed 100 characters",
|
||||
"placeholders": {},
|
||||
@@ -1362,6 +1410,18 @@
|
||||
],
|
||||
"translation": "Board templates"
|
||||
},
|
||||
"tbNcu4": {
|
||||
"message": "Board unarchived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
44
|
||||
]
|
||||
],
|
||||
"translation": "Board unarchived"
|
||||
},
|
||||
"Xid3K6": {
|
||||
"message": "Board URL can only contain letters, numbers, and hyphens",
|
||||
"placeholders": {},
|
||||
@@ -1458,6 +1518,18 @@
|
||||
],
|
||||
"translation": "Boards"
|
||||
},
|
||||
"0RE1AJ": {
|
||||
"message": "Boards you archive will appear here.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/boards/components/BoardsList.tsx",
|
||||
66
|
||||
]
|
||||
],
|
||||
"translation": "Boards you archive will appear here."
|
||||
},
|
||||
"ZDo4HN": {
|
||||
"message": "Brainstorming",
|
||||
"placeholders": {},
|
||||
@@ -2077,7 +2149,7 @@
|
||||
],
|
||||
[
|
||||
"src/views/auth/signup/index.tsx",
|
||||
69
|
||||
71
|
||||
]
|
||||
],
|
||||
"translation": "Check your inbox"
|
||||
@@ -2177,7 +2249,7 @@
|
||||
],
|
||||
[
|
||||
"src/views/auth/signup/index.tsx",
|
||||
74
|
||||
76
|
||||
]
|
||||
],
|
||||
"translation": "Click on the link we've sent to {magicLinkRecipient} to sign in."
|
||||
@@ -2693,11 +2765,11 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/boards/components/BoardsList.tsx",
|
||||
79
|
||||
80
|
||||
],
|
||||
[
|
||||
"src/views/boards/index.tsx",
|
||||
28
|
||||
41
|
||||
]
|
||||
],
|
||||
"translation": "Create new {0}"
|
||||
@@ -3077,7 +3149,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
104
|
||||
133
|
||||
]
|
||||
],
|
||||
"translation": "Delete board"
|
||||
@@ -3125,7 +3197,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
104
|
||||
133
|
||||
]
|
||||
],
|
||||
"translation": "Delete template"
|
||||
@@ -3513,7 +3585,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
84
|
||||
102
|
||||
],
|
||||
[
|
||||
"src/views/board/components/UpdateBoardSlugForm.tsx",
|
||||
@@ -4409,7 +4481,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/auth/signup/index.tsx",
|
||||
69
|
||||
71
|
||||
],
|
||||
[
|
||||
"src/views/home/components/Cta.tsx",
|
||||
@@ -4461,7 +4533,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/boards/components/BoardsList.tsx",
|
||||
65
|
||||
66
|
||||
]
|
||||
],
|
||||
"translation": "Get started by creating a new {0}"
|
||||
@@ -4821,7 +4893,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/boards/index.tsx",
|
||||
60
|
||||
73
|
||||
]
|
||||
],
|
||||
"translation": "Import"
|
||||
@@ -5557,7 +5629,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
73
|
||||
91
|
||||
]
|
||||
],
|
||||
"translation": "Make template"
|
||||
@@ -5792,7 +5864,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/boards/index.tsx",
|
||||
82
|
||||
95
|
||||
],
|
||||
[
|
||||
"src/views/home/components/Features.tsx",
|
||||
@@ -5988,11 +6060,23 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/boards/components/BoardsList.tsx",
|
||||
62
|
||||
63
|
||||
]
|
||||
],
|
||||
"translation": "No {0}"
|
||||
},
|
||||
"tlhgDC": {
|
||||
"message": "No archived boards",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/boards/components/BoardsList.tsx",
|
||||
63
|
||||
]
|
||||
],
|
||||
"translation": "No archived boards"
|
||||
},
|
||||
"Eg99Sc": {
|
||||
"message": "No authentication methods are currently available",
|
||||
"placeholders": {},
|
||||
@@ -6672,7 +6756,7 @@
|
||||
],
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
63
|
||||
65
|
||||
],
|
||||
[
|
||||
"src/views/board/components/NewCardForm.tsx",
|
||||
@@ -7144,7 +7228,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
92
|
||||
121
|
||||
]
|
||||
],
|
||||
"translation": "Remove from favorites"
|
||||
@@ -7239,7 +7323,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
52
|
||||
54
|
||||
]
|
||||
],
|
||||
"translation": "Removed from favorites"
|
||||
@@ -7807,11 +7891,11 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/auth/signup/index.tsx",
|
||||
34
|
||||
36
|
||||
],
|
||||
[
|
||||
"src/views/auth/signup/index.tsx",
|
||||
59
|
||||
61
|
||||
]
|
||||
],
|
||||
"translation": "Sign up | kan.bn"
|
||||
@@ -7823,7 +7907,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/auth/signup/index.tsx",
|
||||
44
|
||||
46
|
||||
]
|
||||
],
|
||||
"translation": "Sign up disabled"
|
||||
@@ -7835,7 +7919,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/auth/signup/index.tsx",
|
||||
47
|
||||
49
|
||||
]
|
||||
],
|
||||
"translation": "Sign up is currently disabled. Please try again later."
|
||||
@@ -8200,6 +8284,30 @@
|
||||
],
|
||||
"translation": "Thank you for your feedback!"
|
||||
},
|
||||
"NcFrgC": {
|
||||
"message": "The board has been archived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
46
|
||||
]
|
||||
],
|
||||
"translation": "The board has been archived."
|
||||
},
|
||||
"C6gv54": {
|
||||
"message": "The board has been unarchived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
47
|
||||
]
|
||||
],
|
||||
"translation": "The board has been unarchived."
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -8827,7 +8935,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
62
|
||||
64
|
||||
]
|
||||
],
|
||||
"translation": "Unable to update board"
|
||||
@@ -8988,6 +9096,18 @@
|
||||
],
|
||||
"translation": "Unable to update role"
|
||||
},
|
||||
"It4/FS": {
|
||||
"message": "Unarchive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/BoardDropdown.tsx",
|
||||
111
|
||||
]
|
||||
],
|
||||
"translation": "Unarchive board"
|
||||
},
|
||||
"E8zYtd": {
|
||||
"message": "unassigned <0>{0}</0> from the card",
|
||||
"placeholders": {
|
||||
@@ -9659,7 +9779,7 @@
|
||||
],
|
||||
[
|
||||
"src/views/boards/index.tsx",
|
||||
35
|
||||
48
|
||||
],
|
||||
[
|
||||
"src/views/members/index.tsx",
|
||||
@@ -10011,15 +10131,15 @@
|
||||
],
|
||||
[
|
||||
"src/views/boards/components/BoardsList.tsx",
|
||||
70
|
||||
71
|
||||
],
|
||||
[
|
||||
"src/views/boards/index.tsx",
|
||||
46
|
||||
59
|
||||
],
|
||||
[
|
||||
"src/views/boards/index.tsx",
|
||||
67
|
||||
80
|
||||
]
|
||||
],
|
||||
"translation": "You don't have permission"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -19,7 +19,7 @@
|
||||
"0": ["isTemplate ? \"Templates\" : \"Boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 40]],
|
||||
"origin": [["src/views/boards/index.tsx", 53]],
|
||||
"translation": "{0}"
|
||||
},
|
||||
"JArWcF": {
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/card/index.tsx", 309]
|
||||
],
|
||||
"translation": "{0} | {1}"
|
||||
@@ -44,7 +44,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 56]],
|
||||
"translation": "{0} se ha añadido a tus favoritos."
|
||||
},
|
||||
"bDI6VI": {
|
||||
@@ -53,7 +53,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 55]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 57]],
|
||||
"translation": "{0} se ha eliminado de tus favoritos."
|
||||
},
|
||||
"CJukzS": {
|
||||
@@ -185,6 +185,13 @@
|
||||
"origin": [["src/providers/keyboard-shortcuts.tsx", 56]],
|
||||
"translation": "Acciones"
|
||||
},
|
||||
"F6pfE9": {
|
||||
"message": "Active",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 26]],
|
||||
"translation": "Activo"
|
||||
},
|
||||
"XJOV1Y": {
|
||||
"message": "Activity",
|
||||
"placeholders": {},
|
||||
@@ -246,7 +253,7 @@
|
||||
"message": "Add description... (type '/' to open commands or '@' to mention)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/Editor.tsx", 471]],
|
||||
"origin": [["src/components/Editor.tsx", 482]],
|
||||
"translation": "Añadir descripción... (escribe '/' para abrir comandos o '@' para mencionar)"
|
||||
},
|
||||
"abUZlY": {
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 93]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 122]],
|
||||
"translation": "Añadir a favoritos"
|
||||
},
|
||||
"bmXKoX": {
|
||||
@@ -368,7 +375,7 @@
|
||||
"message": "Added to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 51]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 53]],
|
||||
"translation": "Añadido a favoritos"
|
||||
},
|
||||
"14Xi3Z": {
|
||||
@@ -482,7 +489,7 @@
|
||||
"message": "Already have an account? <0><1>Sign in</1></0>",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 88]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 90]],
|
||||
"translation": "¿Ya tienes una cuenta? <0><1>Inicia sesión</1></0>"
|
||||
},
|
||||
"WmPhYW": {
|
||||
@@ -574,6 +581,20 @@
|
||||
"origin": [["src/views/boards/components/TemplateBoards.tsx", 46]],
|
||||
"translation": "Aprobación"
|
||||
},
|
||||
"aKJHG+": {
|
||||
"message": "Archive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Archivar tablero"
|
||||
},
|
||||
"TdfEV7": {
|
||||
"message": "Archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 27]],
|
||||
"translation": "Archivado"
|
||||
},
|
||||
"lo8xBK": {
|
||||
"message": "Are you sure want to remove {entityLabel}?",
|
||||
"placeholders": {
|
||||
@@ -785,6 +806,13 @@
|
||||
],
|
||||
"translation": "Analíticas del tablero"
|
||||
},
|
||||
"i0ZMQl": {
|
||||
"message": "Board archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Tablero archivado"
|
||||
},
|
||||
"wewm3j": {
|
||||
"message": "Board name cannot exceed 100 characters",
|
||||
"placeholders": {},
|
||||
@@ -815,6 +843,13 @@
|
||||
],
|
||||
"translation": "Plantillas de tablero"
|
||||
},
|
||||
"tbNcu4": {
|
||||
"message": "Board unarchived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Tablero desarchivado"
|
||||
},
|
||||
"Xid3K6": {
|
||||
"message": "Board URL can only contain letters, numbers, and hyphens",
|
||||
"placeholders": {},
|
||||
@@ -873,6 +908,13 @@
|
||||
],
|
||||
"translation": "Tableros"
|
||||
},
|
||||
"0RE1AJ": {
|
||||
"message": "Boards you archive will appear here.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Los tableros que archives aparecerán aquí."
|
||||
},
|
||||
"ZDo4HN": {
|
||||
"message": "Brainstorming",
|
||||
"placeholders": {},
|
||||
@@ -1237,7 +1279,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 43],
|
||||
["src/views/auth/signup/index.tsx", 69]
|
||||
["src/views/auth/signup/index.tsx", 71]
|
||||
],
|
||||
"translation": "Revisa tu bandeja de entrada"
|
||||
},
|
||||
@@ -1305,7 +1347,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 48],
|
||||
["src/views/auth/signup/index.tsx", 74]
|
||||
["src/views/auth/signup/index.tsx", 76]
|
||||
],
|
||||
"translation": "Haz clic en el enlace que hemos enviado a {magicLinkRecipient} para iniciar sesión."
|
||||
},
|
||||
@@ -1612,8 +1654,8 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/components/BoardsList.tsx", 79],
|
||||
["src/views/boards/index.tsx", 28]
|
||||
["src/views/boards/components/BoardsList.tsx", 80],
|
||||
["src/views/boards/index.tsx", 41]
|
||||
],
|
||||
"translation": "Crear nuevo {0}"
|
||||
},
|
||||
@@ -1835,7 +1877,7 @@
|
||||
"message": "Delete board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Eliminar tablero"
|
||||
},
|
||||
"nabda1": {
|
||||
@@ -1863,7 +1905,7 @@
|
||||
"message": "Delete template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Eliminar plantilla"
|
||||
},
|
||||
"kYu0eF": {
|
||||
@@ -2079,7 +2121,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/BoardDropdown.tsx", 84],
|
||||
["src/views/board/components/BoardDropdown.tsx", 102],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 116]
|
||||
],
|
||||
"translation": "Editar URL del tablero"
|
||||
@@ -2622,7 +2664,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 69],
|
||||
["src/views/auth/signup/index.tsx", 71],
|
||||
["src/views/home/components/Cta.tsx", 61],
|
||||
["src/views/home/components/Header.tsx", 106],
|
||||
["src/views/pricing/components/PricingTiers.tsx", 29],
|
||||
@@ -2647,7 +2689,7 @@
|
||||
"0": ["isTemplate ? \"template\" : \"board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 65]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Comienza creando un nuevo {0}"
|
||||
},
|
||||
"zC8Whw": {
|
||||
@@ -2863,7 +2905,7 @@
|
||||
"message": "Import",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 60]],
|
||||
"origin": [["src/views/boards/index.tsx", 73]],
|
||||
"translation": "Importar"
|
||||
},
|
||||
"2MPcep": {
|
||||
@@ -3299,7 +3341,7 @@
|
||||
"message": "Make template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 73]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Crear plantilla"
|
||||
},
|
||||
"IDvohQ": {
|
||||
@@ -3432,7 +3474,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 82],
|
||||
["src/views/boards/index.tsx", 95],
|
||||
["src/views/home/components/Features.tsx", 73]
|
||||
],
|
||||
"translation": "Nuevo"
|
||||
@@ -3550,9 +3592,16 @@
|
||||
"0": ["isTemplate ? \"templates\" : \"boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 62]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "No hay {0}"
|
||||
},
|
||||
"tlhgDC": {
|
||||
"message": "No archived boards",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "No hay tableros archivados"
|
||||
},
|
||||
"Eg99Sc": {
|
||||
"message": "No authentication methods are currently available",
|
||||
"placeholders": {},
|
||||
@@ -3963,7 +4012,7 @@
|
||||
["src/components/DeleteLabelConfirmation.tsx", 26],
|
||||
["src/components/FeedbackModal.tsx", 41],
|
||||
["src/components/NewWorkspaceForm.tsx", 158],
|
||||
["src/views/board/components/BoardDropdown.tsx", 63],
|
||||
["src/views/board/components/BoardDropdown.tsx", 65],
|
||||
["src/views/board/components/NewCardForm.tsx", 184],
|
||||
["src/views/board/components/NewListForm.tsx", 79],
|
||||
["src/views/board/components/NewTemplateForm.tsx", 61],
|
||||
@@ -4178,7 +4227,7 @@
|
||||
"message": "Remove from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 92]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 121]],
|
||||
"translation": "Eliminar de favoritos"
|
||||
},
|
||||
"99VIgC": {
|
||||
@@ -4232,7 +4281,7 @@
|
||||
"message": "Removed from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 52]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"translation": "Eliminado de favoritos"
|
||||
},
|
||||
"YVT40D": {
|
||||
@@ -4570,8 +4619,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 34],
|
||||
["src/views/auth/signup/index.tsx", 59]
|
||||
["src/views/auth/signup/index.tsx", 36],
|
||||
["src/views/auth/signup/index.tsx", 61]
|
||||
],
|
||||
"translation": "Registrarse | kan.bn"
|
||||
},
|
||||
@@ -4579,14 +4628,14 @@
|
||||
"message": "Sign up disabled",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 44]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 46]],
|
||||
"translation": "Registro deshabilitado"
|
||||
},
|
||||
"s6iE/c": {
|
||||
"message": "Sign up is currently disabled. Please try again later.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 47]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 49]],
|
||||
"translation": "El registro está actualmente deshabilitado. Por favor, inténtalo de nuevo más tarde."
|
||||
},
|
||||
"6FDocz": {
|
||||
@@ -4809,6 +4858,20 @@
|
||||
"origin": [["src/components/FeedbackModal.tsx", 34]],
|
||||
"translation": "¡Gracias por tus comentarios!"
|
||||
},
|
||||
"NcFrgC": {
|
||||
"message": "The board has been archived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 46]],
|
||||
"translation": "El tablero ha sido archivado."
|
||||
},
|
||||
"C6gv54": {
|
||||
"message": "The board has been unarchived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "El tablero ha sido desarchivado."
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5196,7 +5259,7 @@
|
||||
"message": "Unable to update board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 62]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 64]],
|
||||
"translation": "No se pudo actualizar el tablero"
|
||||
},
|
||||
"XpcjLO": {
|
||||
@@ -5292,6 +5355,13 @@
|
||||
"origin": [["src/views/members/index.tsx", 65]],
|
||||
"translation": "No se pudo actualizar el rol"
|
||||
},
|
||||
"It4/FS": {
|
||||
"message": "Unarchive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Desarchivar tablero"
|
||||
},
|
||||
"E8zYtd": {
|
||||
"message": "unassigned <0>{0}</0> from the card",
|
||||
"placeholders": {
|
||||
@@ -5671,7 +5741,7 @@
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 46],
|
||||
["src/views/board/index.tsx", 400],
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
["src/views/public/boards/index.tsx", 65]
|
||||
@@ -5883,9 +5953,9 @@
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 474],
|
||||
["src/views/board/index.tsx", 532],
|
||||
["src/views/boards/components/BoardsList.tsx", 70],
|
||||
["src/views/boards/index.tsx", 46],
|
||||
["src/views/boards/index.tsx", 67]
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
],
|
||||
"translation": "No tienes permiso"
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -19,7 +19,7 @@
|
||||
"0": ["isTemplate ? \"Templates\" : \"Boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 40]],
|
||||
"origin": [["src/views/boards/index.tsx", 53]],
|
||||
"translation": "{0}"
|
||||
},
|
||||
"JArWcF": {
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/card/index.tsx", 309]
|
||||
],
|
||||
"translation": "{0} | {1}"
|
||||
@@ -44,7 +44,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 56]],
|
||||
"translation": "{0} a été ajouté à vos favoris."
|
||||
},
|
||||
"bDI6VI": {
|
||||
@@ -53,7 +53,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 55]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 57]],
|
||||
"translation": "{0} a été retiré de vos favoris."
|
||||
},
|
||||
"CJukzS": {
|
||||
@@ -185,6 +185,13 @@
|
||||
"origin": [["src/providers/keyboard-shortcuts.tsx", 56]],
|
||||
"translation": "Actions"
|
||||
},
|
||||
"F6pfE9": {
|
||||
"message": "Active",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 26]],
|
||||
"translation": "Actif"
|
||||
},
|
||||
"XJOV1Y": {
|
||||
"message": "Activity",
|
||||
"placeholders": {},
|
||||
@@ -246,7 +253,7 @@
|
||||
"message": "Add description... (type '/' to open commands or '@' to mention)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/Editor.tsx", 471]],
|
||||
"origin": [["src/components/Editor.tsx", 482]],
|
||||
"translation": "Ajouter une description... (tapez « / » pour ouvrir les commandes ou « @ » pour mentionner)"
|
||||
},
|
||||
"abUZlY": {
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 93]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 122]],
|
||||
"translation": "Ajouter aux favoris"
|
||||
},
|
||||
"bmXKoX": {
|
||||
@@ -368,7 +375,7 @@
|
||||
"message": "Added to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 51]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 53]],
|
||||
"translation": "Ajouté aux favoris"
|
||||
},
|
||||
"14Xi3Z": {
|
||||
@@ -482,7 +489,7 @@
|
||||
"message": "Already have an account? <0><1>Sign in</1></0>",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 88]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 90]],
|
||||
"translation": "Vous avez déjà un compte ? <0><1>Se connecter</1></0>"
|
||||
},
|
||||
"WmPhYW": {
|
||||
@@ -574,6 +581,20 @@
|
||||
"origin": [["src/views/boards/components/TemplateBoards.tsx", 46]],
|
||||
"translation": "Approbation"
|
||||
},
|
||||
"aKJHG+": {
|
||||
"message": "Archive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Archiver le tableau"
|
||||
},
|
||||
"TdfEV7": {
|
||||
"message": "Archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 27]],
|
||||
"translation": "Archivé"
|
||||
},
|
||||
"lo8xBK": {
|
||||
"message": "Are you sure want to remove {entityLabel}?",
|
||||
"placeholders": {
|
||||
@@ -785,6 +806,13 @@
|
||||
],
|
||||
"translation": "Analytiques du tableau"
|
||||
},
|
||||
"i0ZMQl": {
|
||||
"message": "Board archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Tableau archivé"
|
||||
},
|
||||
"wewm3j": {
|
||||
"message": "Board name cannot exceed 100 characters",
|
||||
"placeholders": {},
|
||||
@@ -815,6 +843,13 @@
|
||||
],
|
||||
"translation": "Modèles de tableau"
|
||||
},
|
||||
"tbNcu4": {
|
||||
"message": "Board unarchived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Tableau désarchivé"
|
||||
},
|
||||
"Xid3K6": {
|
||||
"message": "Board URL can only contain letters, numbers, and hyphens",
|
||||
"placeholders": {},
|
||||
@@ -873,6 +908,13 @@
|
||||
],
|
||||
"translation": "Tableaux"
|
||||
},
|
||||
"0RE1AJ": {
|
||||
"message": "Boards you archive will appear here.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Les tableaux que vous archivez apparaîtront ici."
|
||||
},
|
||||
"ZDo4HN": {
|
||||
"message": "Brainstorming",
|
||||
"placeholders": {},
|
||||
@@ -1237,7 +1279,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 43],
|
||||
["src/views/auth/signup/index.tsx", 69]
|
||||
["src/views/auth/signup/index.tsx", 71]
|
||||
],
|
||||
"translation": "Consultez votre boîte de réception"
|
||||
},
|
||||
@@ -1305,7 +1347,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 48],
|
||||
["src/views/auth/signup/index.tsx", 74]
|
||||
["src/views/auth/signup/index.tsx", 76]
|
||||
],
|
||||
"translation": "Cliquez sur le lien que nous avons envoyé à {magicLinkRecipient} pour vous connecter."
|
||||
},
|
||||
@@ -1612,8 +1654,8 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/components/BoardsList.tsx", 79],
|
||||
["src/views/boards/index.tsx", 28]
|
||||
["src/views/boards/components/BoardsList.tsx", 80],
|
||||
["src/views/boards/index.tsx", 41]
|
||||
],
|
||||
"translation": "Créer un nouveau {0}"
|
||||
},
|
||||
@@ -1835,7 +1877,7 @@
|
||||
"message": "Delete board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Supprimer le tableau"
|
||||
},
|
||||
"nabda1": {
|
||||
@@ -1863,7 +1905,7 @@
|
||||
"message": "Delete template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Supprimer le modèle"
|
||||
},
|
||||
"kYu0eF": {
|
||||
@@ -2079,7 +2121,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/BoardDropdown.tsx", 84],
|
||||
["src/views/board/components/BoardDropdown.tsx", 102],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 116]
|
||||
],
|
||||
"translation": "Modifier l'URL du tableau"
|
||||
@@ -2622,7 +2664,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 69],
|
||||
["src/views/auth/signup/index.tsx", 71],
|
||||
["src/views/home/components/Cta.tsx", 61],
|
||||
["src/views/home/components/Header.tsx", 106],
|
||||
["src/views/pricing/components/PricingTiers.tsx", 29],
|
||||
@@ -2647,7 +2689,7 @@
|
||||
"0": ["isTemplate ? \"template\" : \"board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 65]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Commencez en créant un nouveau {0}"
|
||||
},
|
||||
"zC8Whw": {
|
||||
@@ -2863,7 +2905,7 @@
|
||||
"message": "Import",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 60]],
|
||||
"origin": [["src/views/boards/index.tsx", 73]],
|
||||
"translation": "Importer"
|
||||
},
|
||||
"2MPcep": {
|
||||
@@ -3299,7 +3341,7 @@
|
||||
"message": "Make template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 73]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Créer un modèle"
|
||||
},
|
||||
"IDvohQ": {
|
||||
@@ -3432,7 +3474,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 82],
|
||||
["src/views/boards/index.tsx", 95],
|
||||
["src/views/home/components/Features.tsx", 73]
|
||||
],
|
||||
"translation": "Nouveau"
|
||||
@@ -3550,9 +3592,16 @@
|
||||
"0": ["isTemplate ? \"templates\" : \"boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 62]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Aucun {0}"
|
||||
},
|
||||
"tlhgDC": {
|
||||
"message": "No archived boards",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Aucun tableau archivé"
|
||||
},
|
||||
"Eg99Sc": {
|
||||
"message": "No authentication methods are currently available",
|
||||
"placeholders": {},
|
||||
@@ -3963,7 +4012,7 @@
|
||||
["src/components/DeleteLabelConfirmation.tsx", 26],
|
||||
["src/components/FeedbackModal.tsx", 41],
|
||||
["src/components/NewWorkspaceForm.tsx", 158],
|
||||
["src/views/board/components/BoardDropdown.tsx", 63],
|
||||
["src/views/board/components/BoardDropdown.tsx", 65],
|
||||
["src/views/board/components/NewCardForm.tsx", 184],
|
||||
["src/views/board/components/NewListForm.tsx", 79],
|
||||
["src/views/board/components/NewTemplateForm.tsx", 61],
|
||||
@@ -4178,7 +4227,7 @@
|
||||
"message": "Remove from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 92]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 121]],
|
||||
"translation": "Retirer des favoris"
|
||||
},
|
||||
"99VIgC": {
|
||||
@@ -4232,7 +4281,7 @@
|
||||
"message": "Removed from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 52]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"translation": "Retiré des favoris"
|
||||
},
|
||||
"YVT40D": {
|
||||
@@ -4570,8 +4619,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 34],
|
||||
["src/views/auth/signup/index.tsx", 59]
|
||||
["src/views/auth/signup/index.tsx", 36],
|
||||
["src/views/auth/signup/index.tsx", 61]
|
||||
],
|
||||
"translation": "S'inscrire | kan.bn"
|
||||
},
|
||||
@@ -4579,14 +4628,14 @@
|
||||
"message": "Sign up disabled",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 44]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 46]],
|
||||
"translation": "Inscription désactivée"
|
||||
},
|
||||
"s6iE/c": {
|
||||
"message": "Sign up is currently disabled. Please try again later.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 47]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 49]],
|
||||
"translation": "L'inscription est actuellement désactivée. Veuillez réessayer plus tard."
|
||||
},
|
||||
"6FDocz": {
|
||||
@@ -4809,6 +4858,20 @@
|
||||
"origin": [["src/components/FeedbackModal.tsx", 34]],
|
||||
"translation": "Merci pour votre retour !"
|
||||
},
|
||||
"NcFrgC": {
|
||||
"message": "The board has been archived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 46]],
|
||||
"translation": "Le tableau a été archivé."
|
||||
},
|
||||
"C6gv54": {
|
||||
"message": "The board has been unarchived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Le tableau a été désarchivé."
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5196,7 +5259,7 @@
|
||||
"message": "Unable to update board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 62]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 64]],
|
||||
"translation": "Impossible de mettre à jour le tableau"
|
||||
},
|
||||
"XpcjLO": {
|
||||
@@ -5292,6 +5355,13 @@
|
||||
"origin": [["src/views/members/index.tsx", 65]],
|
||||
"translation": "Impossible de mettre à jour le rôle"
|
||||
},
|
||||
"It4/FS": {
|
||||
"message": "Unarchive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Désarchiver le tableau"
|
||||
},
|
||||
"E8zYtd": {
|
||||
"message": "unassigned <0>{0}</0> from the card",
|
||||
"placeholders": {
|
||||
@@ -5671,7 +5741,7 @@
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 46],
|
||||
["src/views/board/index.tsx", 400],
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
["src/views/public/boards/index.tsx", 65]
|
||||
@@ -5883,9 +5953,9 @@
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 474],
|
||||
["src/views/board/index.tsx", 532],
|
||||
["src/views/boards/components/BoardsList.tsx", 70],
|
||||
["src/views/boards/index.tsx", 46],
|
||||
["src/views/boards/index.tsx", 67]
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
],
|
||||
"translation": "Vous n'avez pas la permission"
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@ export const locales = [
|
||||
"nl",
|
||||
"ru",
|
||||
"pl",
|
||||
"pt-BR"
|
||||
"ptbr"
|
||||
] as const;
|
||||
|
||||
export type Locale = (typeof locales)[number];
|
||||
@@ -23,5 +23,5 @@ export const localeNames: Record<Locale, string> = {
|
||||
nl: "Nederlands",
|
||||
ru: "Русский",
|
||||
pl: "Polski",
|
||||
"pt-BR": "Português",
|
||||
ptbr: "Português",
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"0": ["isTemplate ? \"Templates\" : \"Boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 40]],
|
||||
"origin": [["src/views/boards/index.tsx", 53]],
|
||||
"translation": "{0}"
|
||||
},
|
||||
"JArWcF": {
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/card/index.tsx", 309]
|
||||
],
|
||||
"translation": "{0} | {1}"
|
||||
@@ -44,7 +44,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 56]],
|
||||
"translation": "{0} è stato aggiunto ai tuoi preferiti."
|
||||
},
|
||||
"bDI6VI": {
|
||||
@@ -53,7 +53,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 55]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 57]],
|
||||
"translation": "{0} è stato rimosso dai tuoi preferiti."
|
||||
},
|
||||
"CJukzS": {
|
||||
@@ -185,6 +185,13 @@
|
||||
"origin": [["src/providers/keyboard-shortcuts.tsx", 56]],
|
||||
"translation": "Azioni"
|
||||
},
|
||||
"F6pfE9": {
|
||||
"message": "Active",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 26]],
|
||||
"translation": "Attivo"
|
||||
},
|
||||
"XJOV1Y": {
|
||||
"message": "Activity",
|
||||
"placeholders": {},
|
||||
@@ -246,7 +253,7 @@
|
||||
"message": "Add description... (type '/' to open commands or '@' to mention)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/Editor.tsx", 471]],
|
||||
"origin": [["src/components/Editor.tsx", 482]],
|
||||
"translation": "Aggiungi descrizione... (digita '/' per aprire i comandi o '@' per menzionare)"
|
||||
},
|
||||
"abUZlY": {
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 93]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 122]],
|
||||
"translation": "Aggiungi ai preferiti"
|
||||
},
|
||||
"bmXKoX": {
|
||||
@@ -368,7 +375,7 @@
|
||||
"message": "Added to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 51]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 53]],
|
||||
"translation": "Aggiunto ai preferiti"
|
||||
},
|
||||
"14Xi3Z": {
|
||||
@@ -482,7 +489,7 @@
|
||||
"message": "Already have an account? <0><1>Sign in</1></0>",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 88]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 90]],
|
||||
"translation": "Hai già un account? <0><1>Accedi</1></0>"
|
||||
},
|
||||
"WmPhYW": {
|
||||
@@ -574,6 +581,20 @@
|
||||
"origin": [["src/views/boards/components/TemplateBoards.tsx", 46]],
|
||||
"translation": "Approvazione"
|
||||
},
|
||||
"aKJHG+": {
|
||||
"message": "Archive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Archivia bacheca"
|
||||
},
|
||||
"TdfEV7": {
|
||||
"message": "Archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 27]],
|
||||
"translation": "Archiviato"
|
||||
},
|
||||
"lo8xBK": {
|
||||
"message": "Are you sure want to remove {entityLabel}?",
|
||||
"placeholders": {
|
||||
@@ -785,6 +806,13 @@
|
||||
],
|
||||
"translation": "Analisi della board"
|
||||
},
|
||||
"i0ZMQl": {
|
||||
"message": "Board archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Bacheca archiviata"
|
||||
},
|
||||
"wewm3j": {
|
||||
"message": "Board name cannot exceed 100 characters",
|
||||
"placeholders": {},
|
||||
@@ -815,6 +843,13 @@
|
||||
],
|
||||
"translation": "Template di board"
|
||||
},
|
||||
"tbNcu4": {
|
||||
"message": "Board unarchived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Bacheca ripristinata"
|
||||
},
|
||||
"Xid3K6": {
|
||||
"message": "Board URL can only contain letters, numbers, and hyphens",
|
||||
"placeholders": {},
|
||||
@@ -873,6 +908,13 @@
|
||||
],
|
||||
"translation": "Board"
|
||||
},
|
||||
"0RE1AJ": {
|
||||
"message": "Boards you archive will appear here.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Le bacheche che archivi appariranno qui."
|
||||
},
|
||||
"ZDo4HN": {
|
||||
"message": "Brainstorming",
|
||||
"placeholders": {},
|
||||
@@ -1237,7 +1279,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 43],
|
||||
["src/views/auth/signup/index.tsx", 69]
|
||||
["src/views/auth/signup/index.tsx", 71]
|
||||
],
|
||||
"translation": "Controlla la tua casella di posta"
|
||||
},
|
||||
@@ -1305,7 +1347,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 48],
|
||||
["src/views/auth/signup/index.tsx", 74]
|
||||
["src/views/auth/signup/index.tsx", 76]
|
||||
],
|
||||
"translation": "Clicca sul link che abbiamo inviato a {magicLinkRecipient} per accedere."
|
||||
},
|
||||
@@ -1612,8 +1654,8 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/components/BoardsList.tsx", 79],
|
||||
["src/views/boards/index.tsx", 28]
|
||||
["src/views/boards/components/BoardsList.tsx", 80],
|
||||
["src/views/boards/index.tsx", 41]
|
||||
],
|
||||
"translation": "Crea nuovo {0}"
|
||||
},
|
||||
@@ -1835,7 +1877,7 @@
|
||||
"message": "Delete board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Elimina board"
|
||||
},
|
||||
"nabda1": {
|
||||
@@ -1863,7 +1905,7 @@
|
||||
"message": "Delete template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Elimina template"
|
||||
},
|
||||
"kYu0eF": {
|
||||
@@ -2079,7 +2121,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/BoardDropdown.tsx", 84],
|
||||
["src/views/board/components/BoardDropdown.tsx", 102],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 116]
|
||||
],
|
||||
"translation": "Modifica URL bacheca"
|
||||
@@ -2622,7 +2664,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 69],
|
||||
["src/views/auth/signup/index.tsx", 71],
|
||||
["src/views/home/components/Cta.tsx", 61],
|
||||
["src/views/home/components/Header.tsx", 106],
|
||||
["src/views/pricing/components/PricingTiers.tsx", 29],
|
||||
@@ -2647,7 +2689,7 @@
|
||||
"0": ["isTemplate ? \"template\" : \"board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 65]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Inizia creando un nuovo {0}"
|
||||
},
|
||||
"zC8Whw": {
|
||||
@@ -2863,7 +2905,7 @@
|
||||
"message": "Import",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 60]],
|
||||
"origin": [["src/views/boards/index.tsx", 73]],
|
||||
"translation": "Importa"
|
||||
},
|
||||
"2MPcep": {
|
||||
@@ -3299,7 +3341,7 @@
|
||||
"message": "Make template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 73]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Crea template"
|
||||
},
|
||||
"IDvohQ": {
|
||||
@@ -3432,7 +3474,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 82],
|
||||
["src/views/boards/index.tsx", 95],
|
||||
["src/views/home/components/Features.tsx", 73]
|
||||
],
|
||||
"translation": "Nuovo"
|
||||
@@ -3550,9 +3592,16 @@
|
||||
"0": ["isTemplate ? \"templates\" : \"boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 62]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Nessun {0}"
|
||||
},
|
||||
"tlhgDC": {
|
||||
"message": "No archived boards",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Nessuna bacheca archiviata"
|
||||
},
|
||||
"Eg99Sc": {
|
||||
"message": "No authentication methods are currently available",
|
||||
"placeholders": {},
|
||||
@@ -3963,7 +4012,7 @@
|
||||
["src/components/DeleteLabelConfirmation.tsx", 26],
|
||||
["src/components/FeedbackModal.tsx", 41],
|
||||
["src/components/NewWorkspaceForm.tsx", 158],
|
||||
["src/views/board/components/BoardDropdown.tsx", 63],
|
||||
["src/views/board/components/BoardDropdown.tsx", 65],
|
||||
["src/views/board/components/NewCardForm.tsx", 184],
|
||||
["src/views/board/components/NewListForm.tsx", 79],
|
||||
["src/views/board/components/NewTemplateForm.tsx", 61],
|
||||
@@ -4178,7 +4227,7 @@
|
||||
"message": "Remove from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 92]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 121]],
|
||||
"translation": "Rimuovi dai preferiti"
|
||||
},
|
||||
"99VIgC": {
|
||||
@@ -4232,7 +4281,7 @@
|
||||
"message": "Removed from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 52]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"translation": "Rimosso dai preferiti"
|
||||
},
|
||||
"YVT40D": {
|
||||
@@ -4570,8 +4619,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 34],
|
||||
["src/views/auth/signup/index.tsx", 59]
|
||||
["src/views/auth/signup/index.tsx", 36],
|
||||
["src/views/auth/signup/index.tsx", 61]
|
||||
],
|
||||
"translation": "Registrati | kan.bn"
|
||||
},
|
||||
@@ -4579,14 +4628,14 @@
|
||||
"message": "Sign up disabled",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 44]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 46]],
|
||||
"translation": "Registrazione disabilitata"
|
||||
},
|
||||
"s6iE/c": {
|
||||
"message": "Sign up is currently disabled. Please try again later.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 47]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 49]],
|
||||
"translation": "La registrazione è attualmente disabilitata. Riprova più tardi."
|
||||
},
|
||||
"6FDocz": {
|
||||
@@ -4809,6 +4858,20 @@
|
||||
"origin": [["src/components/FeedbackModal.tsx", 34]],
|
||||
"translation": "Grazie per il tuo feedback!"
|
||||
},
|
||||
"NcFrgC": {
|
||||
"message": "The board has been archived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 46]],
|
||||
"translation": "La bacheca è stata archiviata."
|
||||
},
|
||||
"C6gv54": {
|
||||
"message": "The board has been unarchived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "La bacheca è stata ripristinata."
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5196,7 +5259,7 @@
|
||||
"message": "Unable to update board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 62]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 64]],
|
||||
"translation": "Impossibile aggiornare la board"
|
||||
},
|
||||
"XpcjLO": {
|
||||
@@ -5292,6 +5355,13 @@
|
||||
"origin": [["src/views/members/index.tsx", 65]],
|
||||
"translation": "Impossibile aggiornare il ruolo"
|
||||
},
|
||||
"It4/FS": {
|
||||
"message": "Unarchive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Ripristina bacheca"
|
||||
},
|
||||
"E8zYtd": {
|
||||
"message": "unassigned <0>{0}</0> from the card",
|
||||
"placeholders": {
|
||||
@@ -5671,7 +5741,7 @@
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 46],
|
||||
["src/views/board/index.tsx", 400],
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
["src/views/public/boards/index.tsx", 65]
|
||||
@@ -5883,9 +5953,9 @@
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 474],
|
||||
["src/views/board/index.tsx", 532],
|
||||
["src/views/boards/components/BoardsList.tsx", 70],
|
||||
["src/views/boards/index.tsx", 46],
|
||||
["src/views/boards/index.tsx", 67]
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
],
|
||||
"translation": "Non hai i permessi necessari"
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -19,7 +19,7 @@
|
||||
"0": ["isTemplate ? \"Templates\" : \"Boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 40]],
|
||||
"origin": [["src/views/boards/index.tsx", 53]],
|
||||
"translation": "{0}"
|
||||
},
|
||||
"JArWcF": {
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/card/index.tsx", 309]
|
||||
],
|
||||
"translation": "{0} | {1}"
|
||||
@@ -44,7 +44,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 56]],
|
||||
"translation": "{0} is toegevoegd aan je favorieten."
|
||||
},
|
||||
"bDI6VI": {
|
||||
@@ -53,7 +53,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 55]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 57]],
|
||||
"translation": "{0} is verwijderd uit je favorieten."
|
||||
},
|
||||
"CJukzS": {
|
||||
@@ -185,6 +185,13 @@
|
||||
"origin": [["src/providers/keyboard-shortcuts.tsx", 56]],
|
||||
"translation": "Acties"
|
||||
},
|
||||
"F6pfE9": {
|
||||
"message": "Active",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 26]],
|
||||
"translation": "Actief"
|
||||
},
|
||||
"XJOV1Y": {
|
||||
"message": "Activity",
|
||||
"placeholders": {},
|
||||
@@ -246,7 +253,7 @@
|
||||
"message": "Add description... (type '/' to open commands or '@' to mention)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/Editor.tsx", 471]],
|
||||
"origin": [["src/components/Editor.tsx", 482]],
|
||||
"translation": "Voeg beschrijving toe... (typ '/' om commando's te openen of '@' om te vermelden)"
|
||||
},
|
||||
"abUZlY": {
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 93]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 122]],
|
||||
"translation": "Toevoegen aan favorieten"
|
||||
},
|
||||
"bmXKoX": {
|
||||
@@ -368,7 +375,7 @@
|
||||
"message": "Added to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 51]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 53]],
|
||||
"translation": "Toegevoegd aan favorieten"
|
||||
},
|
||||
"14Xi3Z": {
|
||||
@@ -482,7 +489,7 @@
|
||||
"message": "Already have an account? <0><1>Sign in</1></0>",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 88]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 90]],
|
||||
"translation": "Heb je al een account? <0><1>Inloggen</1></0>"
|
||||
},
|
||||
"WmPhYW": {
|
||||
@@ -574,6 +581,20 @@
|
||||
"origin": [["src/views/boards/components/TemplateBoards.tsx", 46]],
|
||||
"translation": "Goedkeuring"
|
||||
},
|
||||
"aKJHG+": {
|
||||
"message": "Archive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Board archiveren"
|
||||
},
|
||||
"TdfEV7": {
|
||||
"message": "Archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 27]],
|
||||
"translation": "Gearchiveerd"
|
||||
},
|
||||
"lo8xBK": {
|
||||
"message": "Are you sure want to remove {entityLabel}?",
|
||||
"placeholders": {
|
||||
@@ -785,6 +806,13 @@
|
||||
],
|
||||
"translation": "Bordanalyse"
|
||||
},
|
||||
"i0ZMQl": {
|
||||
"message": "Board archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Board gearchiveerd"
|
||||
},
|
||||
"wewm3j": {
|
||||
"message": "Board name cannot exceed 100 characters",
|
||||
"placeholders": {},
|
||||
@@ -815,6 +843,13 @@
|
||||
],
|
||||
"translation": "Bordsjablonen"
|
||||
},
|
||||
"tbNcu4": {
|
||||
"message": "Board unarchived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Board gedearchiveerd"
|
||||
},
|
||||
"Xid3K6": {
|
||||
"message": "Board URL can only contain letters, numbers, and hyphens",
|
||||
"placeholders": {},
|
||||
@@ -873,6 +908,13 @@
|
||||
],
|
||||
"translation": "Boards"
|
||||
},
|
||||
"0RE1AJ": {
|
||||
"message": "Boards you archive will appear here.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Boards die je archiveert verschijnen hier."
|
||||
},
|
||||
"ZDo4HN": {
|
||||
"message": "Brainstorming",
|
||||
"placeholders": {},
|
||||
@@ -1237,7 +1279,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 43],
|
||||
["src/views/auth/signup/index.tsx", 69]
|
||||
["src/views/auth/signup/index.tsx", 71]
|
||||
],
|
||||
"translation": "Controleer je inbox"
|
||||
},
|
||||
@@ -1305,7 +1347,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 48],
|
||||
["src/views/auth/signup/index.tsx", 74]
|
||||
["src/views/auth/signup/index.tsx", 76]
|
||||
],
|
||||
"translation": "Klik op de link die we naar {magicLinkRecipient} hebben gestuurd om in te loggen."
|
||||
},
|
||||
@@ -1612,8 +1654,8 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/components/BoardsList.tsx", 79],
|
||||
["src/views/boards/index.tsx", 28]
|
||||
["src/views/boards/components/BoardsList.tsx", 80],
|
||||
["src/views/boards/index.tsx", 41]
|
||||
],
|
||||
"translation": "Maak nieuwe {0}"
|
||||
},
|
||||
@@ -1835,7 +1877,7 @@
|
||||
"message": "Delete board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Bord verwijderen"
|
||||
},
|
||||
"nabda1": {
|
||||
@@ -1863,7 +1905,7 @@
|
||||
"message": "Delete template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Sjabloon verwijderen"
|
||||
},
|
||||
"kYu0eF": {
|
||||
@@ -2079,7 +2121,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/BoardDropdown.tsx", 84],
|
||||
["src/views/board/components/BoardDropdown.tsx", 102],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 116]
|
||||
],
|
||||
"translation": "Bord-URL bewerken"
|
||||
@@ -2622,7 +2664,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 69],
|
||||
["src/views/auth/signup/index.tsx", 71],
|
||||
["src/views/home/components/Cta.tsx", 61],
|
||||
["src/views/home/components/Header.tsx", 106],
|
||||
["src/views/pricing/components/PricingTiers.tsx", 29],
|
||||
@@ -2647,7 +2689,7 @@
|
||||
"0": ["isTemplate ? \"template\" : \"board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 65]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Ga aan de slag door een nieuwe {0} te maken"
|
||||
},
|
||||
"zC8Whw": {
|
||||
@@ -2863,7 +2905,7 @@
|
||||
"message": "Import",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 60]],
|
||||
"origin": [["src/views/boards/index.tsx", 73]],
|
||||
"translation": "Importeren"
|
||||
},
|
||||
"2MPcep": {
|
||||
@@ -3299,7 +3341,7 @@
|
||||
"message": "Make template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 73]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Sjabloon maken"
|
||||
},
|
||||
"IDvohQ": {
|
||||
@@ -3432,7 +3474,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 82],
|
||||
["src/views/boards/index.tsx", 95],
|
||||
["src/views/home/components/Features.tsx", 73]
|
||||
],
|
||||
"translation": "Nieuw"
|
||||
@@ -3550,9 +3592,16 @@
|
||||
"0": ["isTemplate ? \"templates\" : \"boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 62]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Geen {0}"
|
||||
},
|
||||
"tlhgDC": {
|
||||
"message": "No archived boards",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Geen gearchiveerde boards"
|
||||
},
|
||||
"Eg99Sc": {
|
||||
"message": "No authentication methods are currently available",
|
||||
"placeholders": {},
|
||||
@@ -3963,7 +4012,7 @@
|
||||
["src/components/DeleteLabelConfirmation.tsx", 26],
|
||||
["src/components/FeedbackModal.tsx", 41],
|
||||
["src/components/NewWorkspaceForm.tsx", 158],
|
||||
["src/views/board/components/BoardDropdown.tsx", 63],
|
||||
["src/views/board/components/BoardDropdown.tsx", 65],
|
||||
["src/views/board/components/NewCardForm.tsx", 184],
|
||||
["src/views/board/components/NewListForm.tsx", 79],
|
||||
["src/views/board/components/NewTemplateForm.tsx", 61],
|
||||
@@ -4178,7 +4227,7 @@
|
||||
"message": "Remove from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 92]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 121]],
|
||||
"translation": "Verwijderen uit favorieten"
|
||||
},
|
||||
"99VIgC": {
|
||||
@@ -4232,7 +4281,7 @@
|
||||
"message": "Removed from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 52]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"translation": "Verwijderd uit favorieten"
|
||||
},
|
||||
"YVT40D": {
|
||||
@@ -4570,8 +4619,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 34],
|
||||
["src/views/auth/signup/index.tsx", 59]
|
||||
["src/views/auth/signup/index.tsx", 36],
|
||||
["src/views/auth/signup/index.tsx", 61]
|
||||
],
|
||||
"translation": "Registreren | kan.bn"
|
||||
},
|
||||
@@ -4579,14 +4628,14 @@
|
||||
"message": "Sign up disabled",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 44]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 46]],
|
||||
"translation": "Registratie uitgeschakeld"
|
||||
},
|
||||
"s6iE/c": {
|
||||
"message": "Sign up is currently disabled. Please try again later.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 47]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 49]],
|
||||
"translation": "Registratie is momenteel uitgeschakeld. Probeer het later opnieuw."
|
||||
},
|
||||
"6FDocz": {
|
||||
@@ -4809,6 +4858,20 @@
|
||||
"origin": [["src/components/FeedbackModal.tsx", 34]],
|
||||
"translation": "Bedankt voor je feedback!"
|
||||
},
|
||||
"NcFrgC": {
|
||||
"message": "The board has been archived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 46]],
|
||||
"translation": "Het board is gearchiveerd."
|
||||
},
|
||||
"C6gv54": {
|
||||
"message": "The board has been unarchived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Het board is gedearchiveerd."
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5196,7 +5259,7 @@
|
||||
"message": "Unable to update board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 62]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 64]],
|
||||
"translation": "Kan bord niet bijwerken"
|
||||
},
|
||||
"XpcjLO": {
|
||||
@@ -5292,6 +5355,13 @@
|
||||
"origin": [["src/views/members/index.tsx", 65]],
|
||||
"translation": "Kan rol niet bijwerken"
|
||||
},
|
||||
"It4/FS": {
|
||||
"message": "Unarchive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Board dearchiveren"
|
||||
},
|
||||
"E8zYtd": {
|
||||
"message": "unassigned <0>{0}</0> from the card",
|
||||
"placeholders": {
|
||||
@@ -5671,7 +5741,7 @@
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 46],
|
||||
["src/views/board/index.tsx", 400],
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
["src/views/public/boards/index.tsx", 65]
|
||||
@@ -5883,9 +5953,9 @@
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 474],
|
||||
["src/views/board/index.tsx", 532],
|
||||
["src/views/boards/components/BoardsList.tsx", 70],
|
||||
["src/views/boards/index.tsx", 46],
|
||||
["src/views/boards/index.tsx", 67]
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
],
|
||||
"translation": "Je hebt geen toestemming"
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -19,7 +19,7 @@
|
||||
"0": ["isTemplate ? \"Templates\" : \"Boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 40]],
|
||||
"origin": [["src/views/boards/index.tsx", 53]],
|
||||
"translation": "{0}"
|
||||
},
|
||||
"JArWcF": {
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/card/index.tsx", 309]
|
||||
],
|
||||
"translation": "{0} | {1}"
|
||||
@@ -44,7 +44,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 56]],
|
||||
"translation": "{0} został dodany do ulubionych."
|
||||
},
|
||||
"bDI6VI": {
|
||||
@@ -53,7 +53,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 55]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 57]],
|
||||
"translation": "{0} został usunięty z ulubionych."
|
||||
},
|
||||
"CJukzS": {
|
||||
@@ -185,6 +185,13 @@
|
||||
"origin": [["src/providers/keyboard-shortcuts.tsx", 56]],
|
||||
"translation": "Akcje"
|
||||
},
|
||||
"F6pfE9": {
|
||||
"message": "Active",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 26]],
|
||||
"translation": "Aktywna"
|
||||
},
|
||||
"XJOV1Y": {
|
||||
"message": "Activity",
|
||||
"placeholders": {},
|
||||
@@ -246,7 +253,7 @@
|
||||
"message": "Add description... (type '/' to open commands or '@' to mention)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/Editor.tsx", 471]],
|
||||
"origin": [["src/components/Editor.tsx", 482]],
|
||||
"translation": "Dodaj opis... (wpisz '/' aby otworzyć polecenia lub '@', aby wspomnieć)"
|
||||
},
|
||||
"abUZlY": {
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 93]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 122]],
|
||||
"translation": "Dodaj do ulubionych"
|
||||
},
|
||||
"bmXKoX": {
|
||||
@@ -368,7 +375,7 @@
|
||||
"message": "Added to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 51]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 53]],
|
||||
"translation": "Dodano do ulubionych"
|
||||
},
|
||||
"14Xi3Z": {
|
||||
@@ -482,7 +489,7 @@
|
||||
"message": "Already have an account? <0><1>Sign in</1></0>",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 88]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 90]],
|
||||
"translation": "Masz już konto? <0><1>Zaloguj się</1></0>"
|
||||
},
|
||||
"WmPhYW": {
|
||||
@@ -574,6 +581,20 @@
|
||||
"origin": [["src/views/boards/components/TemplateBoards.tsx", 46]],
|
||||
"translation": "Zatwierdzenie"
|
||||
},
|
||||
"aKJHG+": {
|
||||
"message": "Archive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Zarchiwizuj tablicę"
|
||||
},
|
||||
"TdfEV7": {
|
||||
"message": "Archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 27]],
|
||||
"translation": "Zarchiwizowana"
|
||||
},
|
||||
"lo8xBK": {
|
||||
"message": "Are you sure want to remove {entityLabel}?",
|
||||
"placeholders": {
|
||||
@@ -785,6 +806,13 @@
|
||||
],
|
||||
"translation": "Analiza tablicy"
|
||||
},
|
||||
"i0ZMQl": {
|
||||
"message": "Board archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Tablica zarchiwizowana"
|
||||
},
|
||||
"wewm3j": {
|
||||
"message": "Board name cannot exceed 100 characters",
|
||||
"placeholders": {},
|
||||
@@ -815,6 +843,13 @@
|
||||
],
|
||||
"translation": "Szablony tablic"
|
||||
},
|
||||
"tbNcu4": {
|
||||
"message": "Board unarchived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Tablica przywrócona z archiwum"
|
||||
},
|
||||
"Xid3K6": {
|
||||
"message": "Board URL can only contain letters, numbers, and hyphens",
|
||||
"placeholders": {},
|
||||
@@ -873,6 +908,13 @@
|
||||
],
|
||||
"translation": "Tablice"
|
||||
},
|
||||
"0RE1AJ": {
|
||||
"message": "Boards you archive will appear here.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Zarchiwizowane tablice pojawią się tutaj."
|
||||
},
|
||||
"ZDo4HN": {
|
||||
"message": "Brainstorming",
|
||||
"placeholders": {},
|
||||
@@ -1237,7 +1279,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 43],
|
||||
["src/views/auth/signup/index.tsx", 69]
|
||||
["src/views/auth/signup/index.tsx", 71]
|
||||
],
|
||||
"translation": "Sprawdź swoją skrzynkę odbiorczą"
|
||||
},
|
||||
@@ -1305,7 +1347,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 48],
|
||||
["src/views/auth/signup/index.tsx", 74]
|
||||
["src/views/auth/signup/index.tsx", 76]
|
||||
],
|
||||
"translation": "Kliknij w link, który wysłaliśmy do {magicLinkRecipient}, aby się zalogować."
|
||||
},
|
||||
@@ -1612,8 +1654,8 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/components/BoardsList.tsx", 79],
|
||||
["src/views/boards/index.tsx", 28]
|
||||
["src/views/boards/components/BoardsList.tsx", 80],
|
||||
["src/views/boards/index.tsx", 41]
|
||||
],
|
||||
"translation": "Utwórz nową {0}"
|
||||
},
|
||||
@@ -1835,7 +1877,7 @@
|
||||
"message": "Delete board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Usuń tablicę"
|
||||
},
|
||||
"nabda1": {
|
||||
@@ -1863,7 +1905,7 @@
|
||||
"message": "Delete template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Usuń szablon"
|
||||
},
|
||||
"kYu0eF": {
|
||||
@@ -2079,7 +2121,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/BoardDropdown.tsx", 84],
|
||||
["src/views/board/components/BoardDropdown.tsx", 102],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 116]
|
||||
],
|
||||
"translation": "Edytuj URL tablicy"
|
||||
@@ -2622,7 +2664,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 69],
|
||||
["src/views/auth/signup/index.tsx", 71],
|
||||
["src/views/home/components/Cta.tsx", 61],
|
||||
["src/views/home/components/Header.tsx", 106],
|
||||
["src/views/pricing/components/PricingTiers.tsx", 29],
|
||||
@@ -2647,7 +2689,7 @@
|
||||
"0": ["isTemplate ? \"template\" : \"board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 65]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Rozpocznij, tworząc nowy {0}"
|
||||
},
|
||||
"zC8Whw": {
|
||||
@@ -2863,7 +2905,7 @@
|
||||
"message": "Import",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 60]],
|
||||
"origin": [["src/views/boards/index.tsx", 73]],
|
||||
"translation": "Importuj"
|
||||
},
|
||||
"2MPcep": {
|
||||
@@ -3299,7 +3341,7 @@
|
||||
"message": "Make template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 73]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Utwórz szablon"
|
||||
},
|
||||
"IDvohQ": {
|
||||
@@ -3432,7 +3474,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 82],
|
||||
["src/views/boards/index.tsx", 95],
|
||||
["src/views/home/components/Features.tsx", 73]
|
||||
],
|
||||
"translation": "Nowy"
|
||||
@@ -3550,9 +3592,16 @@
|
||||
"0": ["isTemplate ? \"templates\" : \"boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 62]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Brak {0}"
|
||||
},
|
||||
"tlhgDC": {
|
||||
"message": "No archived boards",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Brak zarchiwizowanych tablic"
|
||||
},
|
||||
"Eg99Sc": {
|
||||
"message": "No authentication methods are currently available",
|
||||
"placeholders": {},
|
||||
@@ -3963,7 +4012,7 @@
|
||||
["src/components/DeleteLabelConfirmation.tsx", 26],
|
||||
["src/components/FeedbackModal.tsx", 41],
|
||||
["src/components/NewWorkspaceForm.tsx", 158],
|
||||
["src/views/board/components/BoardDropdown.tsx", 63],
|
||||
["src/views/board/components/BoardDropdown.tsx", 65],
|
||||
["src/views/board/components/NewCardForm.tsx", 184],
|
||||
["src/views/board/components/NewListForm.tsx", 79],
|
||||
["src/views/board/components/NewTemplateForm.tsx", 61],
|
||||
@@ -4178,7 +4227,7 @@
|
||||
"message": "Remove from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 92]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 121]],
|
||||
"translation": "Usuń z ulubionych"
|
||||
},
|
||||
"99VIgC": {
|
||||
@@ -4232,7 +4281,7 @@
|
||||
"message": "Removed from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 52]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"translation": "Usunięto z ulubionych"
|
||||
},
|
||||
"YVT40D": {
|
||||
@@ -4570,8 +4619,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 34],
|
||||
["src/views/auth/signup/index.tsx", 59]
|
||||
["src/views/auth/signup/index.tsx", 36],
|
||||
["src/views/auth/signup/index.tsx", 61]
|
||||
],
|
||||
"translation": "Rejestracja | kan.bn"
|
||||
},
|
||||
@@ -4579,14 +4628,14 @@
|
||||
"message": "Sign up disabled",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 44]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 46]],
|
||||
"translation": "Rejestracja wyłączona"
|
||||
},
|
||||
"s6iE/c": {
|
||||
"message": "Sign up is currently disabled. Please try again later.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 47]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 49]],
|
||||
"translation": "Rejestracja jest obecnie wyłączona. Spróbuj ponownie później."
|
||||
},
|
||||
"6FDocz": {
|
||||
@@ -4809,6 +4858,20 @@
|
||||
"origin": [["src/components/FeedbackModal.tsx", 34]],
|
||||
"translation": "Dziękujemy za Twoją opinię!"
|
||||
},
|
||||
"NcFrgC": {
|
||||
"message": "The board has been archived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 46]],
|
||||
"translation": "Tablica została zarchiwizowana."
|
||||
},
|
||||
"C6gv54": {
|
||||
"message": "The board has been unarchived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Tablica została przywrócona z archiwum."
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5196,7 +5259,7 @@
|
||||
"message": "Unable to update board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 62]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 64]],
|
||||
"translation": "Nie można zaktualizować tablicy"
|
||||
},
|
||||
"XpcjLO": {
|
||||
@@ -5292,6 +5355,13 @@
|
||||
"origin": [["src/views/members/index.tsx", 65]],
|
||||
"translation": "Nie można zaktualizować roli"
|
||||
},
|
||||
"It4/FS": {
|
||||
"message": "Unarchive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Przywróć tablicę z archiwum"
|
||||
},
|
||||
"E8zYtd": {
|
||||
"message": "unassigned <0>{0}</0> from the card",
|
||||
"placeholders": {
|
||||
@@ -5671,7 +5741,7 @@
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 46],
|
||||
["src/views/board/index.tsx", 400],
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
["src/views/public/boards/index.tsx", 65]
|
||||
@@ -5883,9 +5953,9 @@
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 474],
|
||||
["src/views/board/index.tsx", 532],
|
||||
["src/views/boards/components/BoardsList.tsx", 70],
|
||||
["src/views/boards/index.tsx", 46],
|
||||
["src/views/boards/index.tsx", 67]
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
],
|
||||
"translation": "Nie masz uprawnień"
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -19,7 +19,7 @@
|
||||
"0": ["isTemplate ? \"Templates\" : \"Boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 40]],
|
||||
"origin": [["src/views/boards/index.tsx", 53]],
|
||||
"translation": "{0}"
|
||||
},
|
||||
"JArWcF": {
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/card/index.tsx", 309]
|
||||
],
|
||||
"translation": "{0} | {1}"
|
||||
@@ -44,7 +44,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 56]],
|
||||
"translation": "{0} foi adicionado aos seus favoritos."
|
||||
},
|
||||
"bDI6VI": {
|
||||
@@ -53,7 +53,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 55]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 57]],
|
||||
"translation": "{0} foi removido dos seus favoritos."
|
||||
},
|
||||
"CJukzS": {
|
||||
@@ -185,6 +185,13 @@
|
||||
"origin": [["src/providers/keyboard-shortcuts.tsx", 56]],
|
||||
"translation": "Ações"
|
||||
},
|
||||
"F6pfE9": {
|
||||
"message": "Active",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 26]],
|
||||
"translation": "Ativo"
|
||||
},
|
||||
"XJOV1Y": {
|
||||
"message": "Activity",
|
||||
"placeholders": {},
|
||||
@@ -246,7 +253,7 @@
|
||||
"message": "Add description... (type '/' to open commands or '@' to mention)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/Editor.tsx", 471]],
|
||||
"origin": [["src/components/Editor.tsx", 482]],
|
||||
"translation": "Adicionar descrição... (digite '/' para abrir comandos ou '@' para mencionar)"
|
||||
},
|
||||
"abUZlY": {
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 93]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 122]],
|
||||
"translation": "Adicionar aos favoritos"
|
||||
},
|
||||
"bmXKoX": {
|
||||
@@ -368,7 +375,7 @@
|
||||
"message": "Added to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 51]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 53]],
|
||||
"translation": "Adicionado aos favoritos"
|
||||
},
|
||||
"14Xi3Z": {
|
||||
@@ -482,7 +489,7 @@
|
||||
"message": "Already have an account? <0><1>Sign in</1></0>",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 88]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 90]],
|
||||
"translation": "Já tem uma conta? <0><1>Entrar</1></0>"
|
||||
},
|
||||
"WmPhYW": {
|
||||
@@ -574,6 +581,20 @@
|
||||
"origin": [["src/views/boards/components/TemplateBoards.tsx", 46]],
|
||||
"translation": "Aprovação"
|
||||
},
|
||||
"aKJHG+": {
|
||||
"message": "Archive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Arquivar quadro"
|
||||
},
|
||||
"TdfEV7": {
|
||||
"message": "Archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 27]],
|
||||
"translation": "Arquivado"
|
||||
},
|
||||
"lo8xBK": {
|
||||
"message": "Are you sure want to remove {entityLabel}?",
|
||||
"placeholders": {
|
||||
@@ -785,6 +806,13 @@
|
||||
],
|
||||
"translation": "Análise do quadro"
|
||||
},
|
||||
"i0ZMQl": {
|
||||
"message": "Board archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Quadro arquivado"
|
||||
},
|
||||
"wewm3j": {
|
||||
"message": "Board name cannot exceed 100 characters",
|
||||
"placeholders": {},
|
||||
@@ -815,6 +843,13 @@
|
||||
],
|
||||
"translation": "Modelos de quadro"
|
||||
},
|
||||
"tbNcu4": {
|
||||
"message": "Board unarchived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Quadro desarquivado"
|
||||
},
|
||||
"Xid3K6": {
|
||||
"message": "Board URL can only contain letters, numbers, and hyphens",
|
||||
"placeholders": {},
|
||||
@@ -873,6 +908,13 @@
|
||||
],
|
||||
"translation": "Quadros"
|
||||
},
|
||||
"0RE1AJ": {
|
||||
"message": "Boards you archive will appear here.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Os quadros que você arquivar aparecerão aqui."
|
||||
},
|
||||
"ZDo4HN": {
|
||||
"message": "Brainstorming",
|
||||
"placeholders": {},
|
||||
@@ -1237,7 +1279,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 43],
|
||||
["src/views/auth/signup/index.tsx", 69]
|
||||
["src/views/auth/signup/index.tsx", 71]
|
||||
],
|
||||
"translation": "Verifique sua caixa de entrada"
|
||||
},
|
||||
@@ -1305,7 +1347,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 48],
|
||||
["src/views/auth/signup/index.tsx", 74]
|
||||
["src/views/auth/signup/index.tsx", 76]
|
||||
],
|
||||
"translation": "Clique no link que enviamos para {magicLinkRecipient} para fazer login."
|
||||
},
|
||||
@@ -1612,8 +1654,8 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/components/BoardsList.tsx", 79],
|
||||
["src/views/boards/index.tsx", 28]
|
||||
["src/views/boards/components/BoardsList.tsx", 80],
|
||||
["src/views/boards/index.tsx", 41]
|
||||
],
|
||||
"translation": "Criar novo {0}"
|
||||
},
|
||||
@@ -1835,7 +1877,7 @@
|
||||
"message": "Delete board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Excluir quadro"
|
||||
},
|
||||
"nabda1": {
|
||||
@@ -1863,7 +1905,7 @@
|
||||
"message": "Delete template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Excluir modelo"
|
||||
},
|
||||
"kYu0eF": {
|
||||
@@ -2079,7 +2121,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/BoardDropdown.tsx", 84],
|
||||
["src/views/board/components/BoardDropdown.tsx", 102],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 116]
|
||||
],
|
||||
"translation": "Editar URL do quadro"
|
||||
@@ -2622,7 +2664,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 69],
|
||||
["src/views/auth/signup/index.tsx", 71],
|
||||
["src/views/home/components/Cta.tsx", 61],
|
||||
["src/views/home/components/Header.tsx", 106],
|
||||
["src/views/pricing/components/PricingTiers.tsx", 29],
|
||||
@@ -2647,7 +2689,7 @@
|
||||
"0": ["isTemplate ? \"template\" : \"board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 65]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Comece criando um novo {0}"
|
||||
},
|
||||
"zC8Whw": {
|
||||
@@ -2863,7 +2905,7 @@
|
||||
"message": "Import",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 60]],
|
||||
"origin": [["src/views/boards/index.tsx", 73]],
|
||||
"translation": "Importar"
|
||||
},
|
||||
"2MPcep": {
|
||||
@@ -3299,7 +3341,7 @@
|
||||
"message": "Make template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 73]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Criar modelo"
|
||||
},
|
||||
"IDvohQ": {
|
||||
@@ -3432,7 +3474,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 82],
|
||||
["src/views/boards/index.tsx", 95],
|
||||
["src/views/home/components/Features.tsx", 73]
|
||||
],
|
||||
"translation": "Novo"
|
||||
@@ -3550,9 +3592,16 @@
|
||||
"0": ["isTemplate ? \"templates\" : \"boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 62]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Nenhum {0}"
|
||||
},
|
||||
"tlhgDC": {
|
||||
"message": "No archived boards",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Nenhum quadro arquivado"
|
||||
},
|
||||
"Eg99Sc": {
|
||||
"message": "No authentication methods are currently available",
|
||||
"placeholders": {},
|
||||
@@ -3963,7 +4012,7 @@
|
||||
["src/components/DeleteLabelConfirmation.tsx", 26],
|
||||
["src/components/FeedbackModal.tsx", 41],
|
||||
["src/components/NewWorkspaceForm.tsx", 158],
|
||||
["src/views/board/components/BoardDropdown.tsx", 63],
|
||||
["src/views/board/components/BoardDropdown.tsx", 65],
|
||||
["src/views/board/components/NewCardForm.tsx", 184],
|
||||
["src/views/board/components/NewListForm.tsx", 79],
|
||||
["src/views/board/components/NewTemplateForm.tsx", 61],
|
||||
@@ -4178,7 +4227,7 @@
|
||||
"message": "Remove from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 92]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 121]],
|
||||
"translation": "Remover dos favoritos"
|
||||
},
|
||||
"99VIgC": {
|
||||
@@ -4232,7 +4281,7 @@
|
||||
"message": "Removed from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 52]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"translation": "Removido dos favoritos"
|
||||
},
|
||||
"YVT40D": {
|
||||
@@ -4570,8 +4619,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 34],
|
||||
["src/views/auth/signup/index.tsx", 59]
|
||||
["src/views/auth/signup/index.tsx", 36],
|
||||
["src/views/auth/signup/index.tsx", 61]
|
||||
],
|
||||
"translation": "Cadastrar | kan.bn"
|
||||
},
|
||||
@@ -4579,14 +4628,14 @@
|
||||
"message": "Sign up disabled",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 44]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 46]],
|
||||
"translation": "Cadastro desabilitado"
|
||||
},
|
||||
"s6iE/c": {
|
||||
"message": "Sign up is currently disabled. Please try again later.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 47]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 49]],
|
||||
"translation": "O cadastro está temporariamente desabilitado. Por favor, tente novamente mais tarde."
|
||||
},
|
||||
"6FDocz": {
|
||||
@@ -4809,6 +4858,20 @@
|
||||
"origin": [["src/components/FeedbackModal.tsx", 34]],
|
||||
"translation": "Obrigado pelo seu feedback!"
|
||||
},
|
||||
"NcFrgC": {
|
||||
"message": "The board has been archived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 46]],
|
||||
"translation": "O quadro foi arquivado."
|
||||
},
|
||||
"C6gv54": {
|
||||
"message": "The board has been unarchived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "O quadro foi desarquivado."
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5196,7 +5259,7 @@
|
||||
"message": "Unable to update board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 62]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 64]],
|
||||
"translation": "Não foi possível atualizar o quadro"
|
||||
},
|
||||
"XpcjLO": {
|
||||
@@ -5292,6 +5355,13 @@
|
||||
"origin": [["src/views/members/index.tsx", 65]],
|
||||
"translation": "Não foi possível atualizar a função"
|
||||
},
|
||||
"It4/FS": {
|
||||
"message": "Unarchive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Desarquivar quadro"
|
||||
},
|
||||
"E8zYtd": {
|
||||
"message": "unassigned <0>{0}</0> from the card",
|
||||
"placeholders": {
|
||||
@@ -5671,7 +5741,7 @@
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 46],
|
||||
["src/views/board/index.tsx", 400],
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
["src/views/public/boards/index.tsx", 65]
|
||||
@@ -5883,9 +5953,9 @@
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 474],
|
||||
["src/views/board/index.tsx", 532],
|
||||
["src/views/boards/components/BoardsList.tsx", 70],
|
||||
["src/views/boards/index.tsx", 46],
|
||||
["src/views/boards/index.tsx", 67]
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
],
|
||||
"translation": "Você não tem permissão"
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
2899
apps/web/src/locales/ptbr/messages.po
Normal file
2899
apps/web/src/locales/ptbr/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
1
apps/web/src/locales/ptbr/messages.ts
Normal file
1
apps/web/src/locales/ptbr/messages.ts
Normal file
File diff suppressed because one or more lines are too long
@@ -19,7 +19,7 @@
|
||||
"0": ["isTemplate ? \"Templates\" : \"Boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 40]],
|
||||
"origin": [["src/views/boards/index.tsx", 53]],
|
||||
"translation": "{0}"
|
||||
},
|
||||
"JArWcF": {
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/card/index.tsx", 309]
|
||||
],
|
||||
"translation": "{0} | {1}"
|
||||
@@ -44,7 +44,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 56]],
|
||||
"translation": "{0} добавлен в избранное."
|
||||
},
|
||||
"bDI6VI": {
|
||||
@@ -53,7 +53,7 @@
|
||||
"0": ["boardName ?? \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 55]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 57]],
|
||||
"translation": "{0} удалён из избранного."
|
||||
},
|
||||
"CJukzS": {
|
||||
@@ -185,6 +185,13 @@
|
||||
"origin": [["src/providers/keyboard-shortcuts.tsx", 56]],
|
||||
"translation": "Действия"
|
||||
},
|
||||
"F6pfE9": {
|
||||
"message": "Active",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 26]],
|
||||
"translation": "Активно"
|
||||
},
|
||||
"XJOV1Y": {
|
||||
"message": "Activity",
|
||||
"placeholders": {},
|
||||
@@ -246,7 +253,7 @@
|
||||
"message": "Add description... (type '/' to open commands or '@' to mention)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/Editor.tsx", 471]],
|
||||
"origin": [["src/components/Editor.tsx", 482]],
|
||||
"translation": "Добавить описание... (введите '/' для открытия команд или '@' для упоминания)"
|
||||
},
|
||||
"abUZlY": {
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 93]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 122]],
|
||||
"translation": "Добавить в избранное"
|
||||
},
|
||||
"bmXKoX": {
|
||||
@@ -368,7 +375,7 @@
|
||||
"message": "Added to favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 51]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 53]],
|
||||
"translation": "Добавлено в избранное"
|
||||
},
|
||||
"14Xi3Z": {
|
||||
@@ -482,7 +489,7 @@
|
||||
"message": "Already have an account? <0><1>Sign in</1></0>",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 88]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 90]],
|
||||
"translation": "Уже есть аккаунт? <0><1>Войти</1></0>"
|
||||
},
|
||||
"WmPhYW": {
|
||||
@@ -574,6 +581,20 @@
|
||||
"origin": [["src/views/boards/components/TemplateBoards.tsx", 46]],
|
||||
"translation": "Одобрение"
|
||||
},
|
||||
"aKJHG+": {
|
||||
"message": "Archive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Архивировать доску"
|
||||
},
|
||||
"TdfEV7": {
|
||||
"message": "Archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 27]],
|
||||
"translation": "В архиве"
|
||||
},
|
||||
"lo8xBK": {
|
||||
"message": "Are you sure want to remove {entityLabel}?",
|
||||
"placeholders": {
|
||||
@@ -785,6 +806,13 @@
|
||||
],
|
||||
"translation": "Аналитика доски"
|
||||
},
|
||||
"i0ZMQl": {
|
||||
"message": "Board archived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Доска архивирована"
|
||||
},
|
||||
"wewm3j": {
|
||||
"message": "Board name cannot exceed 100 characters",
|
||||
"placeholders": {},
|
||||
@@ -815,6 +843,13 @@
|
||||
],
|
||||
"translation": "Шаблоны досок"
|
||||
},
|
||||
"tbNcu4": {
|
||||
"message": "Board unarchived",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 44]],
|
||||
"translation": "Доска восстановлена из архива"
|
||||
},
|
||||
"Xid3K6": {
|
||||
"message": "Board URL can only contain letters, numbers, and hyphens",
|
||||
"placeholders": {},
|
||||
@@ -873,6 +908,13 @@
|
||||
],
|
||||
"translation": "Доски"
|
||||
},
|
||||
"0RE1AJ": {
|
||||
"message": "Boards you archive will appear here.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Здесь будут отображаться архивированные вами доски."
|
||||
},
|
||||
"ZDo4HN": {
|
||||
"message": "Brainstorming",
|
||||
"placeholders": {},
|
||||
@@ -1237,7 +1279,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 43],
|
||||
["src/views/auth/signup/index.tsx", 69]
|
||||
["src/views/auth/signup/index.tsx", 71]
|
||||
],
|
||||
"translation": "Проверьте свой почтовый ящик"
|
||||
},
|
||||
@@ -1305,7 +1347,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/login/index.tsx", 48],
|
||||
["src/views/auth/signup/index.tsx", 74]
|
||||
["src/views/auth/signup/index.tsx", 76]
|
||||
],
|
||||
"translation": "Перейдите по ссылке, которую мы отправили на {magicLinkRecipient}, чтобы войти."
|
||||
},
|
||||
@@ -1612,8 +1654,8 @@
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/components/BoardsList.tsx", 79],
|
||||
["src/views/boards/index.tsx", 28]
|
||||
["src/views/boards/components/BoardsList.tsx", 80],
|
||||
["src/views/boards/index.tsx", 41]
|
||||
],
|
||||
"translation": "Создать новый {0}"
|
||||
},
|
||||
@@ -1835,7 +1877,7 @@
|
||||
"message": "Delete board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Удалить доску"
|
||||
},
|
||||
"nabda1": {
|
||||
@@ -1863,7 +1905,7 @@
|
||||
"message": "Delete template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 104]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 133]],
|
||||
"translation": "Удалить шаблон"
|
||||
},
|
||||
"kYu0eF": {
|
||||
@@ -2079,7 +2121,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/BoardDropdown.tsx", 84],
|
||||
["src/views/board/components/BoardDropdown.tsx", 102],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 116]
|
||||
],
|
||||
"translation": "Изменить URL доски"
|
||||
@@ -2622,7 +2664,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 69],
|
||||
["src/views/auth/signup/index.tsx", 71],
|
||||
["src/views/home/components/Cta.tsx", 61],
|
||||
["src/views/home/components/Header.tsx", 106],
|
||||
["src/views/pricing/components/PricingTiers.tsx", 29],
|
||||
@@ -2647,7 +2689,7 @@
|
||||
"0": ["isTemplate ? \"template\" : \"board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 65]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 66]],
|
||||
"translation": "Начните с создания нового {0}"
|
||||
},
|
||||
"zC8Whw": {
|
||||
@@ -2863,7 +2905,7 @@
|
||||
"message": "Import",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/index.tsx", 60]],
|
||||
"origin": [["src/views/boards/index.tsx", 73]],
|
||||
"translation": "Импорт"
|
||||
},
|
||||
"2MPcep": {
|
||||
@@ -3299,7 +3341,7 @@
|
||||
"message": "Make template",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 73]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Создать шаблон"
|
||||
},
|
||||
"IDvohQ": {
|
||||
@@ -3432,7 +3474,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/boards/index.tsx", 82],
|
||||
["src/views/boards/index.tsx", 95],
|
||||
["src/views/home/components/Features.tsx", 73]
|
||||
],
|
||||
"translation": "Новый"
|
||||
@@ -3550,9 +3592,16 @@
|
||||
"0": ["isTemplate ? \"templates\" : \"boards\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 62]],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Нет {0}"
|
||||
},
|
||||
"tlhgDC": {
|
||||
"message": "No archived boards",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/boards/components/BoardsList.tsx", 63]],
|
||||
"translation": "Нет архивированных досок"
|
||||
},
|
||||
"Eg99Sc": {
|
||||
"message": "No authentication methods are currently available",
|
||||
"placeholders": {},
|
||||
@@ -3963,7 +4012,7 @@
|
||||
["src/components/DeleteLabelConfirmation.tsx", 26],
|
||||
["src/components/FeedbackModal.tsx", 41],
|
||||
["src/components/NewWorkspaceForm.tsx", 158],
|
||||
["src/views/board/components/BoardDropdown.tsx", 63],
|
||||
["src/views/board/components/BoardDropdown.tsx", 65],
|
||||
["src/views/board/components/NewCardForm.tsx", 184],
|
||||
["src/views/board/components/NewListForm.tsx", 79],
|
||||
["src/views/board/components/NewTemplateForm.tsx", 61],
|
||||
@@ -4178,7 +4227,7 @@
|
||||
"message": "Remove from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 92]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 121]],
|
||||
"translation": "Удалить из избранного"
|
||||
},
|
||||
"99VIgC": {
|
||||
@@ -4232,7 +4281,7 @@
|
||||
"message": "Removed from favorites",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 52]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 54]],
|
||||
"translation": "Удалено из избранного"
|
||||
},
|
||||
"YVT40D": {
|
||||
@@ -4570,8 +4619,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/auth/signup/index.tsx", 34],
|
||||
["src/views/auth/signup/index.tsx", 59]
|
||||
["src/views/auth/signup/index.tsx", 36],
|
||||
["src/views/auth/signup/index.tsx", 61]
|
||||
],
|
||||
"translation": "Регистрация | kan.bn"
|
||||
},
|
||||
@@ -4579,14 +4628,14 @@
|
||||
"message": "Sign up disabled",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 44]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 46]],
|
||||
"translation": "Регистрация отключена"
|
||||
},
|
||||
"s6iE/c": {
|
||||
"message": "Sign up is currently disabled. Please try again later.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 47]],
|
||||
"origin": [["src/views/auth/signup/index.tsx", 49]],
|
||||
"translation": "Регистрация сейчас недоступна. Пожалуйста, попробуйте позже."
|
||||
},
|
||||
"6FDocz": {
|
||||
@@ -4809,6 +4858,20 @@
|
||||
"origin": [["src/components/FeedbackModal.tsx", 34]],
|
||||
"translation": "Спасибо за ваш отзыв!"
|
||||
},
|
||||
"NcFrgC": {
|
||||
"message": "The board has been archived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 46]],
|
||||
"translation": "Доска была архивирована."
|
||||
},
|
||||
"C6gv54": {
|
||||
"message": "The board has been unarchived.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Доска была восстановлена из архива."
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5196,7 +5259,7 @@
|
||||
"message": "Unable to update board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 62]],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 64]],
|
||||
"translation": "Не удалось обновить доску"
|
||||
},
|
||||
"XpcjLO": {
|
||||
@@ -5292,6 +5355,13 @@
|
||||
"origin": [["src/views/members/index.tsx", 65]],
|
||||
"translation": "Не удалось обновить роль"
|
||||
},
|
||||
"It4/FS": {
|
||||
"message": "Unarchive board",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 111]],
|
||||
"translation": "Восстановить доску из архива"
|
||||
},
|
||||
"E8zYtd": {
|
||||
"message": "unassigned <0>{0}</0> from the card",
|
||||
"placeholders": {
|
||||
@@ -5671,7 +5741,7 @@
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 46],
|
||||
["src/views/board/index.tsx", 400],
|
||||
["src/views/boards/index.tsx", 35],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
["src/views/public/boards/index.tsx", 65]
|
||||
@@ -5883,9 +5953,9 @@
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 474],
|
||||
["src/views/board/index.tsx", 532],
|
||||
["src/views/boards/components/BoardsList.tsx", 70],
|
||||
["src/views/boards/index.tsx", 46],
|
||||
["src/views/boards/index.tsx", 67]
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
],
|
||||
"translation": "У вас нет прав доступа"
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -58,7 +58,7 @@ export default function LoginPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isSignUpDisabled && (
|
||||
{(!isSignUpDisabled || redirect?.startsWith("/invite/")) && (
|
||||
<p className="mt-4 text-sm text-light-1000 dark:text-dark-1000">
|
||||
<Trans>
|
||||
Don't have an account?{" "}
|
||||
|
||||
@@ -28,7 +28,9 @@ export default function SignUpPage() {
|
||||
setMagicLinkRecipient(recipient);
|
||||
};
|
||||
|
||||
if (isSignUpDisabled) {
|
||||
const isInviteFlow = redirect?.startsWith("/invite/");
|
||||
|
||||
if (isSignUpDisabled && !isInviteFlow) {
|
||||
return (
|
||||
<>
|
||||
<PageHead title={t`Sign up | kan.bn`} />
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
HiOutlineStar,
|
||||
HiStar,
|
||||
} from "react-icons/hi2";
|
||||
|
||||
import { IoArchiveOutline } from "react-icons/io5";
|
||||
import Dropdown from "~/components/Dropdown";
|
||||
import { usePermissions } from "~/hooks/usePermissions";
|
||||
import { useModal } from "~/providers/modal";
|
||||
@@ -17,6 +17,7 @@ import { api } from "~/utils/api";
|
||||
export default function BoardDropdown({
|
||||
isTemplate,
|
||||
isLoading,
|
||||
isArchived,
|
||||
boardPublicId,
|
||||
isFavorite,
|
||||
boardName,
|
||||
@@ -24,28 +25,29 @@ export default function BoardDropdown({
|
||||
isTemplate: boolean;
|
||||
isLoading: boolean;
|
||||
boardPublicId: string;
|
||||
isArchived?: boolean;
|
||||
isFavorite?: boolean;
|
||||
boardName?: string;
|
||||
}) {
|
||||
const { openModal } = useModal();
|
||||
const { canEditBoard, canDeleteBoard, canCreateBoard } = usePermissions();
|
||||
const { showPopup } = usePopup();
|
||||
const { canEditBoard, canDeleteBoard, canCreateBoard, canArchiveBoard } =
|
||||
usePermissions();
|
||||
const utils = api.useUtils();
|
||||
|
||||
const handleToggleFavorite = () => {
|
||||
updateBoard.mutate({
|
||||
boardPublicId,
|
||||
favorite: !isFavorite,
|
||||
});
|
||||
};
|
||||
|
||||
const updateBoard = api.board.update.useMutation({
|
||||
onSuccess: (data, variables) => {
|
||||
onSuccess: (_data, variables) => {
|
||||
void utils.board.all.invalidate();
|
||||
void utils.board.byId.invalidate();
|
||||
|
||||
// Show popup notification
|
||||
if (variables.favorite !== undefined) {
|
||||
if (variables.isArchived !== undefined) {
|
||||
showPopup({
|
||||
header: variables.isArchived ? t`Board archived` : t`Board unarchived`,
|
||||
message: variables.isArchived
|
||||
? t`The board has been archived.`
|
||||
: t`The board has been unarchived.`,
|
||||
icon: "success",
|
||||
});
|
||||
} else if (variables.favorite !== undefined) {
|
||||
showPopup({
|
||||
header: variables.favorite
|
||||
? t`Added to favorites`
|
||||
@@ -65,27 +67,54 @@ export default function BoardDropdown({
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const handleToggleFavorite = () => {
|
||||
updateBoard.mutate({
|
||||
boardPublicId,
|
||||
favorite: !isFavorite,
|
||||
});
|
||||
};
|
||||
|
||||
const handleArchiveOrUnarchive = () => {
|
||||
updateBoard.mutate({
|
||||
boardPublicId,
|
||||
isArchived: !isArchived,
|
||||
});
|
||||
};
|
||||
|
||||
const isArchiveActionPending = updateBoard.isPending;
|
||||
|
||||
const items = [
|
||||
...(isTemplate && canCreateBoard
|
||||
? [
|
||||
{
|
||||
label: t`Make template`,
|
||||
action: () => openModal("CREATE_TEMPLATE"),
|
||||
icon: (
|
||||
<HiOutlineDocumentDuplicate className="h-[16px] w-[16px] text-dark-900" />
|
||||
),
|
||||
},
|
||||
]
|
||||
{
|
||||
label: t`Make template`,
|
||||
action: () => openModal("CREATE_TEMPLATE"),
|
||||
icon: (
|
||||
<HiOutlineDocumentDuplicate className="h-[16px] w-[16px] text-dark-900" />
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!isTemplate && canEditBoard
|
||||
? [
|
||||
{
|
||||
label: t`Edit board URL`,
|
||||
action: () => openModal("UPDATE_BOARD_SLUG"),
|
||||
icon: <HiLink className="h-[16px] w-[16px] text-dark-900" />,
|
||||
},
|
||||
]
|
||||
{
|
||||
label: t`Edit board URL`,
|
||||
action: () => openModal("UPDATE_BOARD_SLUG"),
|
||||
icon: <HiLink className="h-[16px] w-[16px] text-dark-900" />,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!isTemplate && canArchiveBoard
|
||||
? [
|
||||
{
|
||||
label: isArchived ? t`Unarchive board` : t`Archive board`,
|
||||
action: handleArchiveOrUnarchive,
|
||||
icon: (
|
||||
<IoArchiveOutline className="h-[16px] w-[16px] text-dark-900" />
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: isFavorite
|
||||
@@ -100,22 +129,26 @@ export default function BoardDropdown({
|
||||
},
|
||||
...(canDeleteBoard
|
||||
? [
|
||||
{
|
||||
label: isTemplate ? t`Delete template` : t`Delete board`,
|
||||
action: () => openModal("DELETE_BOARD"),
|
||||
icon: <HiOutlineTrash className="h-[16px] w-[16px] text-dark-900" />,
|
||||
},
|
||||
]
|
||||
{
|
||||
label: isTemplate ? t`Delete template` : t`Delete board`,
|
||||
action: () => openModal("DELETE_BOARD"),
|
||||
icon: (
|
||||
<HiOutlineTrash className="h-[16px] w-[16px] text-dark-900" />
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
|
||||
if (items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown disabled={isLoading} items={items}>
|
||||
<Dropdown
|
||||
disabled={isLoading || isArchiveActionPending}
|
||||
items={items}
|
||||
>
|
||||
<HiEllipsisHorizontal className="h-5 w-5 text-dark-900" />
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
@@ -494,7 +494,7 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
isTemplate={!!isTemplate}
|
||||
isLoading={!boardData}
|
||||
boardPublicId={boardId ?? ""}
|
||||
workspacePublicId={workspace.publicId}
|
||||
isArchived={boardData?.isArchived ?? false}
|
||||
isFavorite={boardData?.favorite}
|
||||
boardName={boardData?.name}
|
||||
/>
|
||||
@@ -598,13 +598,12 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
? `/templates/${boardId}/cards/${card.publicId}`
|
||||
: `/cards/${card.publicId}`
|
||||
}
|
||||
className={`mb-2 flex !cursor-pointer flex-col ${
|
||||
card.publicId.startsWith(
|
||||
"PLACEHOLDER",
|
||||
)
|
||||
? "pointer-events-none"
|
||||
: ""
|
||||
}`}
|
||||
className={`mb-2 flex !cursor-pointer flex-col ${card.publicId.startsWith(
|
||||
"PLACEHOLDER",
|
||||
)
|
||||
? "pointer-events-none"
|
||||
: ""
|
||||
}`}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useModal } from "~/providers/modal";
|
||||
import { useWorkspace } from "~/providers/workspace";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
export function BoardsList({ isTemplate }: { isTemplate?: boolean }) {
|
||||
export function BoardsList({ isTemplate, archived = false }: { isTemplate?: boolean; archived?: boolean }) {
|
||||
const { workspace } = useWorkspace();
|
||||
const { openModal } = useModal();
|
||||
const { canCreateBoard } = usePermissions();
|
||||
@@ -26,6 +26,7 @@ export function BoardsList({ isTemplate }: { isTemplate?: boolean }) {
|
||||
{
|
||||
workspacePublicId: workspace.publicId,
|
||||
type: isTemplate ? "template" : "regular",
|
||||
archived: archived,
|
||||
},
|
||||
{ enabled: workspace.publicId ? true : false },
|
||||
);
|
||||
@@ -59,10 +60,10 @@ export function BoardsList({ isTemplate }: { isTemplate?: boolean }) {
|
||||
<div className="flex flex-col items-center">
|
||||
<HiOutlineRectangleStack className="h-10 w-10 text-light-800 dark:text-dark-800" />
|
||||
<p className="mb-2 mt-4 text-[14px] font-bold text-light-1000 dark:text-dark-950">
|
||||
{t`No ${isTemplate ? "templates" : "boards"}`}
|
||||
{archived ? t`No archived boards` : t`No ${isTemplate ? "templates" : "boards"}`}
|
||||
</p>
|
||||
<p className="text-[14px] text-light-900 dark:text-dark-900">
|
||||
{t`Get started by creating a new ${isTemplate ? "template" : "board"}`}
|
||||
{archived ? t`Boards you archive will appear here.` : t`Get started by creating a new ${isTemplate ? "template" : "board"}`}
|
||||
</p>
|
||||
</div>
|
||||
<Tooltip
|
||||
@@ -109,18 +110,18 @@ export function BoardsList({ isTemplate }: { isTemplate?: boolean }) {
|
||||
>
|
||||
<div className="group relative mr-5 flex h-[150px] w-full items-center justify-center rounded-md border border-dashed border-light-400 bg-light-50 shadow-sm hover:bg-light-200 dark:border-dark-600 dark:bg-dark-50 dark:hover:bg-dark-100">
|
||||
<PatternedBackground />
|
||||
<button
|
||||
onClick={(e) => handleToggleFavorite(e, board.publicId, board.favorite)}
|
||||
className={`absolute right-3 top-3 z-10 rounded p-1 transition-all hover:bg-light-300 dark:hover:bg-dark-200 ${board.favorite ? "" : "md:opacity-0 md:group-hover:opacity-100"
|
||||
}`}
|
||||
aria-label={board.favorite ? "Remove from favorites" : "Add to favorites"}
|
||||
>
|
||||
{board.favorite ? (
|
||||
<HiStar className="h-5 w-5 text-neutral-700 dark:text-dark-1000" />
|
||||
) : (
|
||||
<HiOutlineStar className="h-5 w-5 text-neutral-700 dark:text-dark-800" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => handleToggleFavorite(e, board.publicId, board.favorite)}
|
||||
className={`absolute right-3 top-3 z-10 rounded p-1 transition-all hover:bg-light-300 dark:hover:bg-dark-200 ${board.favorite ? "" : "md:opacity-0 md:group-hover:opacity-100"
|
||||
}`}
|
||||
aria-label={board.favorite ? "Remove from favorites" : "Add to favorites"}
|
||||
>
|
||||
{board.favorite ? (
|
||||
<HiStar className="h-5 w-5 text-neutral-700 dark:text-dark-1000" />
|
||||
) : (
|
||||
<HiOutlineStar className="h-5 w-5 text-neutral-700 dark:text-dark-800" />
|
||||
)}
|
||||
</button>
|
||||
<p className="px-4 text-[14px] font-bold text-neutral-700 dark:text-dark-1000">
|
||||
{board.name}
|
||||
</p>
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxOption,
|
||||
ListboxOptions,
|
||||
} from "@headlessui/react";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { HiArrowDownTray, HiOutlinePlusSmall } from "react-icons/hi2";
|
||||
import { HiArrowDownTray, HiChevronDown, HiOutlinePlusSmall } from "react-icons/hi2";
|
||||
import { useState } from "react";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import FeedbackModal from "~/components/FeedbackModal";
|
||||
@@ -15,9 +22,15 @@ import { BoardsList } from "./components/BoardsList";
|
||||
import { ImportBoardsForm } from "./components/ImportBoardsForm";
|
||||
import { NewBoardForm } from "./components/NewBoardForm";
|
||||
|
||||
const boardsTabs = [
|
||||
{ key: "boards" as const, label: t`Active` },
|
||||
{ key: "archived" as const, label: t`Archived` },
|
||||
];
|
||||
|
||||
export default function BoardsPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
const { openModal, modalContentType, isOpen } = useModal();
|
||||
const { workspace } = useWorkspace();
|
||||
const [activeTab, setActiveTab] = useState<"boards" | "archived">("boards");
|
||||
const { canCreateBoard } = usePermissions();
|
||||
|
||||
const { tooltipContent: createModalShortcutTooltipContent } =
|
||||
@@ -34,7 +47,7 @@ export default function BoardsPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
<PageHead
|
||||
title={t`${isTemplate ? "Templates" : "Boards"} | ${workspace.name ?? t`Workspace`}`}
|
||||
/>
|
||||
<div className="m-auto h-full max-w-[1100px] p-6 px-5 md:px-28 md:py-12">
|
||||
<div className="m-auto h-full max-w-[1100px] p-8 px-5 md:px-28 md:py-12">
|
||||
<div className="relative z-10 mb-8 flex w-full items-center justify-between">
|
||||
<h1 className="font-bold tracking-tight text-neutral-900 dark:text-dark-1000 sm:text-[1.2rem]">
|
||||
{t`${isTemplate ? "Templates" : "Boards"}`}
|
||||
@@ -115,9 +128,79 @@ export default function BoardsPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
<div className="flex h-full flex-row">
|
||||
<BoardsList isTemplate={!!isTemplate} />
|
||||
</div>
|
||||
{!isTemplate ? (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<div className="focus:outline-none">
|
||||
<div className="sm:hidden">
|
||||
<Listbox
|
||||
value={activeTab}
|
||||
onChange={(tab) => setActiveTab(tab)}
|
||||
>
|
||||
<div className="relative mb-4">
|
||||
<ListboxButton className="w-full appearance-none rounded-md border-0 bg-light-50 py-3 pl-3 pr-10 text-left text-sm font-semibold text-light-1000 shadow-sm ring-1 ring-inset ring-light-300 dark:bg-dark-50 dark:text-dark-1000 dark:ring-dark-300 dark:focus:ring-dark-500">
|
||||
{boardsTabs.find((tab) => tab.key === activeTab)?.label ??
|
||||
"Select a tab"}
|
||||
<HiChevronDown
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-light-900 dark:text-dark-900"
|
||||
/>
|
||||
</ListboxButton>
|
||||
<ListboxOptions className="absolute z-10 mt-1 w-full rounded-md bg-light-50 py-1 text-sm shadow-lg ring-1 ring-inset ring-light-300 dark:bg-dark-50 dark:ring-dark-300">
|
||||
{boardsTabs.map((tab) => (
|
||||
<ListboxOption
|
||||
key={tab.key}
|
||||
value={tab.key}
|
||||
className={({ selected }) =>
|
||||
`relative cursor-pointer select-none py-2 pl-3 pr-9 ${selected
|
||||
? "font-bold text-light-1000 dark:text-dark-1000"
|
||||
: "font-normal text-light-1000 dark:text-dark-1000"
|
||||
}`
|
||||
}
|
||||
>
|
||||
{tab.label}
|
||||
</ListboxOption>
|
||||
))}
|
||||
</ListboxOptions>
|
||||
</div>
|
||||
</Listbox>
|
||||
</div>
|
||||
<div className="hidden sm:block">
|
||||
<div>
|
||||
<nav
|
||||
aria-label="Tabs"
|
||||
className="-mb-px flex space-x-8 focus:outline-none"
|
||||
>
|
||||
{boardsTabs.map((tab) => (
|
||||
<button
|
||||
key={tab.key}
|
||||
type="button"
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
className={`whitespace-nowrap px-1 py-0 mt-2 mb-8 text-sm font-semibold transition-colors focus:outline-none ${activeTab === tab.key
|
||||
? "border-light-1000 text-light-1000 dark:border-dark-1000 dark:text-dark-1000"
|
||||
: "border-transparent text-light-900 hover:border-light-950 hover:text-light-950 dark:text-dark-900 dark:hover:border-white/20 dark:hover:text-dark-950"
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full flex-row focus:outline-none">
|
||||
{activeTab === "boards" && (
|
||||
<BoardsList isTemplate={false} archived={false} />
|
||||
)}
|
||||
{activeTab === "archived" && (
|
||||
<BoardsList isTemplate={false} archived={true} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-full flex-row">
|
||||
<BoardsList isTemplate={!!isTemplate} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -34,6 +34,7 @@ export const boardRouter = createTRPCRouter({
|
||||
z.object({
|
||||
workspacePublicId: z.string().min(12),
|
||||
type: z.enum(["regular", "template"]).optional(),
|
||||
archived: z.boolean().optional(),
|
||||
}),
|
||||
)
|
||||
.output(
|
||||
@@ -65,7 +66,10 @@ export const boardRouter = createTRPCRouter({
|
||||
ctx.db,
|
||||
workspace.id,
|
||||
userId,
|
||||
{ type: input.type }
|
||||
{
|
||||
type: input.type,
|
||||
archived: input.archived ?? false,
|
||||
}
|
||||
);
|
||||
|
||||
return result;
|
||||
@@ -153,24 +157,24 @@ export const boardRouter = createTRPCRouter({
|
||||
// Generate presigned URLs for workspace member avatars
|
||||
const workspaceWithAvatarUrls = result.workspace
|
||||
? {
|
||||
...result.workspace,
|
||||
members: await Promise.all(
|
||||
result.workspace.members.map(async (member) => {
|
||||
if (!member.user?.image) {
|
||||
return member;
|
||||
}
|
||||
...result.workspace,
|
||||
members: await Promise.all(
|
||||
result.workspace.members.map(async (member) => {
|
||||
if (!member.user?.image) {
|
||||
return member;
|
||||
}
|
||||
|
||||
const avatarUrl = await generateAvatarUrl(member.user.image);
|
||||
return {
|
||||
...member,
|
||||
user: {
|
||||
...member.user,
|
||||
image: avatarUrl,
|
||||
},
|
||||
};
|
||||
}),
|
||||
),
|
||||
}
|
||||
const avatarUrl = await generateAvatarUrl(member.user.image);
|
||||
return {
|
||||
...member,
|
||||
user: {
|
||||
...member.user,
|
||||
image: avatarUrl,
|
||||
},
|
||||
};
|
||||
}),
|
||||
),
|
||||
}
|
||||
: result.workspace;
|
||||
|
||||
// Generate presigned URLs for card member avatars
|
||||
@@ -461,7 +465,8 @@ export const boardRouter = createTRPCRouter({
|
||||
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/)
|
||||
.optional(),
|
||||
visibility: z.enum(["public", "private"]).optional(),
|
||||
favorite: z.boolean().optional()
|
||||
favorite: z.boolean().optional(),
|
||||
isArchived: z.boolean().optional(),
|
||||
}),
|
||||
)
|
||||
.output(z.object({ success: z.boolean() }).or(z.custom<Awaited<ReturnType<typeof boardRepo.update>>>()))
|
||||
@@ -503,7 +508,7 @@ export const boardRouter = createTRPCRouter({
|
||||
}
|
||||
|
||||
// Handle other updates (name, slug, visibility)
|
||||
const hasOtherUpdates = input.name || input.slug || input.visibility !== undefined;
|
||||
const hasOtherUpdates = input.name || input.slug || input.visibility !== undefined || input.isArchived !== undefined;
|
||||
|
||||
if (!hasOtherUpdates) {
|
||||
// Only favorite was updated, return success
|
||||
@@ -530,6 +535,7 @@ export const boardRouter = createTRPCRouter({
|
||||
slug: input.slug,
|
||||
boardPublicId: input.boardPublicId,
|
||||
visibility: input.visibility,
|
||||
isArchived: input.isArchived,
|
||||
});
|
||||
|
||||
if (!result)
|
||||
|
||||
@@ -13,6 +13,10 @@ import { mergeActivities } from "../utils/activities";
|
||||
import { sendMentionEmails } from "../utils/notifications";
|
||||
import { assertCanDelete, assertCanEdit, assertPermission } from "../utils/permissions";
|
||||
import { generateAttachmentUrl, generateAvatarUrl } from "@kan/shared/utils";
|
||||
import {
|
||||
createCardWebhookPayload,
|
||||
sendWebhooksForWorkspace,
|
||||
} from "../utils/webhook";
|
||||
|
||||
export const cardRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
@@ -165,6 +169,32 @@ export const cardRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
// Fire webhooks (non-blocking)
|
||||
sendWebhooksForWorkspace(
|
||||
ctx.db,
|
||||
list.workspaceId,
|
||||
createCardWebhookPayload(
|
||||
"card.created",
|
||||
{
|
||||
id: String(newCard.id),
|
||||
title: input.title,
|
||||
description: input.description,
|
||||
dueDate: input.dueDate ?? null,
|
||||
listId: String(newCard.listId),
|
||||
},
|
||||
{
|
||||
boardId: list.boardPublicId,
|
||||
boardName: list.boardName,
|
||||
listName: list.name,
|
||||
user: ctx.user
|
||||
? { id: ctx.user.id, name: ctx.user.name }
|
||||
: undefined,
|
||||
},
|
||||
),
|
||||
).catch((error) => {
|
||||
console.error("Webhook delivery failed:", error);
|
||||
});
|
||||
|
||||
return newCard;
|
||||
}),
|
||||
addComment: protectedProcedure
|
||||
@@ -1007,6 +1037,59 @@ export const cardRouter = createTRPCRouter({
|
||||
await cardActivityRepo.bulkCreate(ctx.db, activities);
|
||||
}
|
||||
|
||||
// Build changes object for webhook
|
||||
const webhookChanges: Record<string, { from: unknown; to: unknown }> = {};
|
||||
if (input.title && existingCard.title !== input.title) {
|
||||
webhookChanges.title = { from: existingCard.title, to: input.title };
|
||||
}
|
||||
if (input.description && existingCard.description !== input.description) {
|
||||
webhookChanges.description = {
|
||||
from: existingCard.description,
|
||||
to: input.description,
|
||||
};
|
||||
}
|
||||
if (
|
||||
input.dueDate !== undefined &&
|
||||
previousDueDate?.getTime() !== input.dueDate?.getTime()
|
||||
) {
|
||||
webhookChanges.dueDate = { from: previousDueDate, to: input.dueDate };
|
||||
}
|
||||
if (newListId && existingCard.listId !== newListId) {
|
||||
webhookChanges.listId = { from: existingCard.listId, to: newListId };
|
||||
}
|
||||
|
||||
// Fire webhooks (non-blocking)
|
||||
sendWebhooksForWorkspace(
|
||||
ctx.db,
|
||||
card.workspaceId,
|
||||
createCardWebhookPayload(
|
||||
newListId && existingCard.listId !== newListId
|
||||
? "card.moved"
|
||||
: "card.updated",
|
||||
{
|
||||
id: String(result.id),
|
||||
title: result.title,
|
||||
description: result.description,
|
||||
dueDate: result.dueDate,
|
||||
listId: String(newListId ?? existingCard.listId),
|
||||
},
|
||||
{
|
||||
boardId: card.boardPublicId,
|
||||
boardName: card.boardName,
|
||||
listName: card.listName,
|
||||
user: ctx.user
|
||||
? { id: ctx.user.id, name: ctx.user.name }
|
||||
: undefined,
|
||||
changes:
|
||||
Object.keys(webhookChanges).length > 0
|
||||
? webhookChanges
|
||||
: undefined,
|
||||
},
|
||||
),
|
||||
).catch((error) => {
|
||||
console.error("Webhook delivery failed:", error);
|
||||
});
|
||||
|
||||
return result;
|
||||
}),
|
||||
delete: protectedProcedure
|
||||
@@ -1054,6 +1137,9 @@ export const cardRouter = createTRPCRouter({
|
||||
card.createdBy,
|
||||
);
|
||||
|
||||
// Fetch full card data before delete for webhook
|
||||
const fullCard = await cardRepo.getByPublicId(ctx.db, input.cardPublicId);
|
||||
|
||||
const deletedAt = new Date();
|
||||
|
||||
await cardRepo.softDelete(ctx.db, {
|
||||
@@ -1068,6 +1154,34 @@ export const cardRouter = createTRPCRouter({
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
// Fire webhooks (non-blocking)
|
||||
if (fullCard) {
|
||||
sendWebhooksForWorkspace(
|
||||
ctx.db,
|
||||
card.workspaceId,
|
||||
createCardWebhookPayload(
|
||||
"card.deleted",
|
||||
{
|
||||
id: String(fullCard.id),
|
||||
title: fullCard.title,
|
||||
description: fullCard.description,
|
||||
dueDate: fullCard.dueDate,
|
||||
listId: String(fullCard.listId),
|
||||
},
|
||||
{
|
||||
boardId: card.boardPublicId,
|
||||
boardName: card.boardName,
|
||||
listName: card.listName,
|
||||
user: ctx.user
|
||||
? { id: ctx.user.id, name: ctx.user.name }
|
||||
: undefined,
|
||||
},
|
||||
),
|
||||
).catch((error) => {
|
||||
console.error("Webhook delivery failed:", error);
|
||||
});
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -81,6 +81,16 @@ export const checklistRouter = createTRPCRouter({
|
||||
return newChecklist;
|
||||
}),
|
||||
update: protectedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
summary: "Update a checklist",
|
||||
method: "PUT",
|
||||
path: "/checklists/{checklistPublicId}",
|
||||
description: "Updates a checklist by its public ID",
|
||||
tags: ["Cards"],
|
||||
protect: true,
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
checklistPublicId: z.string().length(12),
|
||||
|
||||
@@ -210,11 +210,12 @@ export const memberRouter = createTRPCRouter({
|
||||
input.memberPublicId,
|
||||
);
|
||||
|
||||
if (!member)
|
||||
if (!member || member.workspaceId !== workspace.id) {
|
||||
throw new TRPCError({
|
||||
message: `Member with public ID ${input.memberPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
}
|
||||
|
||||
const deletedMember = await memberRepo.softDelete(ctx.db, {
|
||||
memberId: member.id,
|
||||
@@ -720,7 +721,7 @@ export const memberRouter = createTRPCRouter({
|
||||
input.memberPublicId,
|
||||
);
|
||||
|
||||
if (!member) {
|
||||
if (!member || member.workspaceId !== workspace.id) {
|
||||
throw new TRPCError({
|
||||
message: "Member not found",
|
||||
code: "NOT_FOUND",
|
||||
|
||||
@@ -133,7 +133,7 @@ export const permissionRouter = createTRPCRouter({
|
||||
input.memberPublicId,
|
||||
);
|
||||
|
||||
if (!member) {
|
||||
if (!member || member.workspaceId !== workspace.id) {
|
||||
throw new TRPCError({
|
||||
message: "Member not found",
|
||||
code: "NOT_FOUND",
|
||||
@@ -209,7 +209,7 @@ export const permissionRouter = createTRPCRouter({
|
||||
input.memberPublicId,
|
||||
);
|
||||
|
||||
if (!member) {
|
||||
if (!member || member.workspaceId !== workspace.id) {
|
||||
throw new TRPCError({
|
||||
message: "Member not found",
|
||||
code: "NOT_FOUND",
|
||||
@@ -274,7 +274,7 @@ export const permissionRouter = createTRPCRouter({
|
||||
input.memberPublicId,
|
||||
);
|
||||
|
||||
if (!member) {
|
||||
if (!member || member.workspaceId !== workspace.id) {
|
||||
throw new TRPCError({
|
||||
message: "Member not found",
|
||||
code: "NOT_FOUND",
|
||||
@@ -339,7 +339,7 @@ export const permissionRouter = createTRPCRouter({
|
||||
input.memberPublicId,
|
||||
);
|
||||
|
||||
if (!member) {
|
||||
if (!member || member.workspaceId !== workspace.id) {
|
||||
throw new TRPCError({
|
||||
message: "Member not found",
|
||||
code: "NOT_FOUND",
|
||||
|
||||
528
packages/api/src/utils/webhook.test.ts
Normal file
528
packages/api/src/utils/webhook.test.ts
Normal file
@@ -0,0 +1,528 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
|
||||
vi.mock("@kan/db/repository/webhook.repo", () => ({
|
||||
getActiveByWorkspaceId: vi.fn(),
|
||||
}));
|
||||
|
||||
import * as webhookRepo from "@kan/db/repository/webhook.repo";
|
||||
import {
|
||||
sendWebhookToUrl,
|
||||
sendWebhooksForWorkspace,
|
||||
createCardWebhookPayload,
|
||||
webhookUrlSchema,
|
||||
type WebhookPayload,
|
||||
} from "./webhook";
|
||||
|
||||
const mockGetActiveByWorkspaceId = webhookRepo.getActiveByWorkspaceId as ReturnType<typeof vi.fn>;
|
||||
|
||||
describe("webhook utilities", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2024-01-15T12:00:00.000Z"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe("createCardWebhookPayload", () => {
|
||||
it("creates a payload with required card fields", () => {
|
||||
const payload = createCardWebhookPayload(
|
||||
"card.created",
|
||||
{
|
||||
id: "card-123",
|
||||
title: "Test Card",
|
||||
listId: "list-456",
|
||||
},
|
||||
{
|
||||
boardId: "board-789",
|
||||
},
|
||||
);
|
||||
|
||||
expect(payload.event).toBe("card.created");
|
||||
expect(payload.timestamp).toBe("2024-01-15T12:00:00.000Z");
|
||||
expect(payload.data.card).toEqual({
|
||||
id: "card-123",
|
||||
title: "Test Card",
|
||||
description: undefined,
|
||||
dueDate: null,
|
||||
listId: "list-456",
|
||||
boardId: "board-789",
|
||||
});
|
||||
});
|
||||
|
||||
it("includes optional card fields when provided", () => {
|
||||
const dueDate = new Date("2024-02-01T10:00:00.000Z");
|
||||
const payload = createCardWebhookPayload(
|
||||
"card.updated",
|
||||
{
|
||||
id: "card-123",
|
||||
title: "Test Card",
|
||||
description: "A description",
|
||||
dueDate,
|
||||
listId: "list-456",
|
||||
},
|
||||
{
|
||||
boardId: "board-789",
|
||||
},
|
||||
);
|
||||
|
||||
expect(payload.data.card.description).toBe("A description");
|
||||
expect(payload.data.card.dueDate).toBe("2024-02-01T10:00:00.000Z");
|
||||
});
|
||||
|
||||
it("includes board context when provided", () => {
|
||||
const payload = createCardWebhookPayload(
|
||||
"card.created",
|
||||
{
|
||||
id: "card-123",
|
||||
title: "Test Card",
|
||||
listId: "list-456",
|
||||
},
|
||||
{
|
||||
boardId: "board-789",
|
||||
boardName: "My Board",
|
||||
},
|
||||
);
|
||||
|
||||
expect(payload.data.board).toEqual({
|
||||
id: "board-789",
|
||||
name: "My Board",
|
||||
});
|
||||
});
|
||||
|
||||
it("includes list context when provided", () => {
|
||||
const payload = createCardWebhookPayload(
|
||||
"card.created",
|
||||
{
|
||||
id: "card-123",
|
||||
title: "Test Card",
|
||||
listId: "list-456",
|
||||
},
|
||||
{
|
||||
boardId: "board-789",
|
||||
listName: "To Do",
|
||||
},
|
||||
);
|
||||
|
||||
expect(payload.data.list).toEqual({
|
||||
id: "list-456",
|
||||
name: "To Do",
|
||||
});
|
||||
});
|
||||
|
||||
it("includes user context when provided", () => {
|
||||
const payload = createCardWebhookPayload(
|
||||
"card.created",
|
||||
{
|
||||
id: "card-123",
|
||||
title: "Test Card",
|
||||
listId: "list-456",
|
||||
},
|
||||
{
|
||||
boardId: "board-789",
|
||||
user: {
|
||||
id: "user-111",
|
||||
name: "John Doe",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(payload.data.user).toEqual({
|
||||
id: "user-111",
|
||||
name: "John Doe",
|
||||
});
|
||||
});
|
||||
|
||||
it("includes changes for card.updated events", () => {
|
||||
const payload = createCardWebhookPayload(
|
||||
"card.updated",
|
||||
{
|
||||
id: "card-123",
|
||||
title: "Updated Title",
|
||||
listId: "list-456",
|
||||
},
|
||||
{
|
||||
boardId: "board-789",
|
||||
changes: {
|
||||
title: { from: "Old Title", to: "Updated Title" },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(payload.data.changes).toEqual({
|
||||
title: { from: "Old Title", to: "Updated Title" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendWebhookToUrl", () => {
|
||||
const originalFetch = global.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
global.fetch = vi.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
});
|
||||
|
||||
const mockPayload: WebhookPayload = {
|
||||
event: "card.created",
|
||||
timestamp: "2024-01-15T12:00:00.000Z",
|
||||
data: {
|
||||
card: {
|
||||
id: "card-123",
|
||||
title: "Test Card",
|
||||
listId: "list-456",
|
||||
boardId: "board-789",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe("SSRF protection", () => {
|
||||
it("blocks HTTP URLs", async () => {
|
||||
const result = await sendWebhookToUrl("http://example.com/webhook", undefined, mockPayload);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain("HTTPS");
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("blocks localhost", async () => {
|
||||
const result = await sendWebhookToUrl("https://localhost/webhook", undefined, mockPayload);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain("Localhost");
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("blocks 127.0.0.1", async () => {
|
||||
const result = await sendWebhookToUrl("https://127.0.0.1/webhook", undefined, mockPayload);
|
||||
expect(result.success).toBe(false);
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("blocks cloud metadata endpoint", async () => {
|
||||
const result = await sendWebhookToUrl("https://169.254.169.254/latest/meta-data/", undefined, mockPayload);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain("metadata");
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("blocks private 10.x.x.x IPs", async () => {
|
||||
const result = await sendWebhookToUrl("https://10.0.0.1/webhook", undefined, mockPayload);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain("Private");
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("blocks private 192.168.x.x IPs", async () => {
|
||||
const result = await sendWebhookToUrl("https://192.168.1.1/webhook", undefined, mockPayload);
|
||||
expect(result.success).toBe(false);
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows valid HTTPS URLs", async () => {
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ ok: true, status: 200 });
|
||||
const result = await sendWebhookToUrl("https://example.com/webhook", undefined, mockPayload);
|
||||
expect(result.success).toBe(true);
|
||||
expect(global.fetch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("sends POST request with correct headers", async () => {
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
status: 200,
|
||||
});
|
||||
|
||||
await sendWebhookToUrl("https://example.com/webhook", undefined, mockPayload);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
"https://example.com/webhook",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: expect.objectContaining({
|
||||
"Content-Type": "application/json",
|
||||
"X-Webhook-Event": "card.created",
|
||||
"X-Webhook-Timestamp": "2024-01-15T12:00:00.000Z",
|
||||
}),
|
||||
body: JSON.stringify(mockPayload),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("includes signature header when secret is provided", async () => {
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
status: 200,
|
||||
});
|
||||
|
||||
await sendWebhookToUrl("https://example.com/webhook", "my-secret", mockPayload);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
"https://example.com/webhook",
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
"X-Webhook-Signature": expect.stringMatching(/^[a-f0-9]{64}$/),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns success for 2xx responses", async () => {
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
status: 200,
|
||||
});
|
||||
|
||||
const result = await sendWebhookToUrl(
|
||||
"https://example.com/webhook",
|
||||
undefined,
|
||||
mockPayload,
|
||||
);
|
||||
|
||||
expect(result).toEqual({ success: true, statusCode: 200 });
|
||||
});
|
||||
|
||||
it("returns failure for non-2xx responses", async () => {
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 500,
|
||||
statusText: "Internal Server Error",
|
||||
});
|
||||
|
||||
const result = await sendWebhookToUrl(
|
||||
"https://example.com/webhook",
|
||||
undefined,
|
||||
mockPayload,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
statusCode: 500,
|
||||
error: "500 Internal Server Error",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns failure on network error", async () => {
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockRejectedValueOnce(
|
||||
new Error("Network error"),
|
||||
);
|
||||
|
||||
const result = await sendWebhookToUrl(
|
||||
"https://example.com/webhook",
|
||||
undefined,
|
||||
mockPayload,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: "Network error",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns timeout error when request takes too long", async () => {
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockImplementationOnce(
|
||||
() =>
|
||||
new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
const error = new Error("Aborted");
|
||||
error.name = "AbortError";
|
||||
reject(error);
|
||||
}, 15000);
|
||||
}),
|
||||
);
|
||||
|
||||
const resultPromise = sendWebhookToUrl(
|
||||
"https://example.com/webhook",
|
||||
undefined,
|
||||
mockPayload,
|
||||
);
|
||||
|
||||
// Advance timers to trigger the abort
|
||||
vi.advanceTimersByTime(15000);
|
||||
|
||||
const result = await resultPromise;
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: "Request timed out",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendWebhooksForWorkspace", () => {
|
||||
const originalFetch = global.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
global.fetch = vi.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
});
|
||||
|
||||
const mockDb = {} as Parameters<typeof sendWebhooksForWorkspace>[0];
|
||||
|
||||
const mockPayload: WebhookPayload = {
|
||||
event: "card.created",
|
||||
timestamp: "2024-01-15T12:00:00.000Z",
|
||||
data: {
|
||||
card: {
|
||||
id: "card-123",
|
||||
title: "Test Card",
|
||||
listId: "list-456",
|
||||
boardId: "board-789",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it("sends to all active webhooks subscribed to the event", async () => {
|
||||
mockGetActiveByWorkspaceId.mockResolvedValueOnce([
|
||||
{
|
||||
id: 1,
|
||||
publicId: "wh-1",
|
||||
url: "https://example.com/webhook1",
|
||||
secret: "secret1",
|
||||
events: ["card.created", "card.updated"],
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
publicId: "wh-2",
|
||||
url: "https://example.com/webhook2",
|
||||
secret: null,
|
||||
events: ["card.created"],
|
||||
active: true,
|
||||
},
|
||||
]);
|
||||
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
});
|
||||
|
||||
await sendWebhooksForWorkspace(mockDb, 1, mockPayload);
|
||||
|
||||
// Event filtering now happens at DB level
|
||||
expect(mockGetActiveByWorkspaceId).toHaveBeenCalledWith(
|
||||
mockDb,
|
||||
1,
|
||||
"card.created",
|
||||
);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(2);
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
"https://example.com/webhook1",
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
"https://example.com/webhook2",
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not send when no webhooks match the event (DB-level filtering)", async () => {
|
||||
// DB-level event filter returns empty array when no webhooks match
|
||||
mockGetActiveByWorkspaceId.mockResolvedValueOnce([]);
|
||||
|
||||
await sendWebhooksForWorkspace(mockDb, 1, mockPayload);
|
||||
|
||||
expect(mockGetActiveByWorkspaceId).toHaveBeenCalledWith(
|
||||
mockDb,
|
||||
1,
|
||||
"card.created",
|
||||
);
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("continues sending to other webhooks when one fails", async () => {
|
||||
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
mockGetActiveByWorkspaceId.mockResolvedValueOnce([
|
||||
{
|
||||
id: 1,
|
||||
publicId: "wh-1",
|
||||
url: "https://example.com/webhook1",
|
||||
secret: null,
|
||||
events: ["card.created"],
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
publicId: "wh-2",
|
||||
url: "https://example.com/webhook2",
|
||||
secret: null,
|
||||
events: ["card.created"],
|
||||
active: true,
|
||||
},
|
||||
]);
|
||||
|
||||
(global.fetch as ReturnType<typeof vi.fn>)
|
||||
.mockResolvedValueOnce({ ok: false, status: 500, statusText: "Error" })
|
||||
.mockResolvedValueOnce({ ok: true, status: 200 });
|
||||
|
||||
await sendWebhooksForWorkspace(mockDb, 1, mockPayload);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledTimes(2);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Webhook delivery failed"),
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("handles empty webhook list", async () => {
|
||||
mockGetActiveByWorkspaceId.mockResolvedValueOnce([]);
|
||||
|
||||
await sendWebhooksForWorkspace(mockDb, 1, mockPayload);
|
||||
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("catches and logs DB errors without throwing", async () => {
|
||||
const consoleSpy = vi
|
||||
.spyOn(console, "error")
|
||||
.mockImplementation(() => {});
|
||||
mockGetActiveByWorkspaceId.mockRejectedValueOnce(
|
||||
new Error("DB connection failed"),
|
||||
);
|
||||
|
||||
// Should not throw — the try/catch absorbs the error
|
||||
await expect(
|
||||
sendWebhooksForWorkspace(mockDb, 1, mockPayload),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
"Failed to send webhooks for workspace:",
|
||||
expect.any(Error),
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("webhookUrlSchema", () => {
|
||||
it("accepts valid HTTPS URLs", () => {
|
||||
expect(webhookUrlSchema.safeParse("https://example.com/webhook").success).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects HTTP URLs", () => {
|
||||
const result = webhookUrlSchema.safeParse("http://example.com/webhook");
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects localhost", () => {
|
||||
const result = webhookUrlSchema.safeParse("https://localhost/webhook");
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects private IPs", () => {
|
||||
expect(webhookUrlSchema.safeParse("https://10.0.0.1/webhook").success).toBe(false);
|
||||
expect(webhookUrlSchema.safeParse("https://192.168.1.1/webhook").success).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects cloud metadata endpoints", () => {
|
||||
expect(webhookUrlSchema.safeParse("https://169.254.169.254/latest").success).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
258
packages/api/src/utils/webhook.ts
Normal file
258
packages/api/src/utils/webhook.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
import crypto from "crypto";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { dbClient } from "@kan/db/client";
|
||||
import type { WebhookEvent } from "@kan/db/schema";
|
||||
import * as webhookRepo from "@kan/db/repository/webhook.repo";
|
||||
|
||||
export type WebhookEventType = WebhookEvent;
|
||||
|
||||
export interface WebhookPayload {
|
||||
event: WebhookEventType;
|
||||
timestamp: string;
|
||||
data: {
|
||||
card: {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string | null;
|
||||
dueDate?: string | null; // ISO string after JSON serialization
|
||||
listId: string;
|
||||
boardId: string;
|
||||
};
|
||||
board?: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
list?: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
user?: {
|
||||
id: string;
|
||||
name: string | null;
|
||||
};
|
||||
changes?: Record<string, { from: unknown; to: unknown }>;
|
||||
};
|
||||
}
|
||||
|
||||
function generateSignature(payload: string, secret: string): string {
|
||||
return crypto.createHmac("sha256", secret).update(payload).digest("hex");
|
||||
}
|
||||
|
||||
/**
|
||||
* Zod schema for webhook URLs with SSRF mitigation.
|
||||
* Requires HTTPS and blocks private/internal IP ranges, localhost,
|
||||
* and cloud metadata endpoints.
|
||||
*/
|
||||
export const webhookUrlSchema = z
|
||||
.string()
|
||||
.url()
|
||||
.max(2048)
|
||||
.refine(
|
||||
(url) => {
|
||||
try {
|
||||
return new URL(url).protocol === "https:";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{ message: "Only HTTPS URLs are allowed" },
|
||||
)
|
||||
.refine(
|
||||
(url) => {
|
||||
try {
|
||||
const hostname = new URL(url).hostname.toLowerCase();
|
||||
return !(
|
||||
hostname === "localhost" ||
|
||||
hostname === "127.0.0.1" ||
|
||||
hostname === "::1" ||
|
||||
hostname === "0.0.0.0"
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{ message: "Localhost URLs are not allowed" },
|
||||
)
|
||||
.refine(
|
||||
(url) => {
|
||||
try {
|
||||
const hostname = new URL(url).hostname.toLowerCase();
|
||||
return !(
|
||||
hostname === "169.254.169.254" ||
|
||||
hostname === "metadata.google.internal"
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{ message: "Cloud metadata endpoints are not allowed" },
|
||||
)
|
||||
.refine(
|
||||
(url) => {
|
||||
try {
|
||||
const hostname = new URL(url).hostname.toLowerCase();
|
||||
const ipv4Match = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(hostname);
|
||||
if (ipv4Match) {
|
||||
const [, a, b] = ipv4Match.map(Number);
|
||||
if (
|
||||
a === 10 ||
|
||||
(a === 172 && b! >= 16 && b! <= 31) ||
|
||||
(a === 192 && b === 168)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{ message: "Private IP addresses are not allowed" },
|
||||
);
|
||||
|
||||
/**
|
||||
* Send a webhook payload to a specific URL.
|
||||
*
|
||||
* SSRF note: URL validation (HTTPS-only, no private IPs, no cloud metadata)
|
||||
* is enforced both here at delivery time and at webhook creation via
|
||||
* webhookUrlSchema. This function is only reachable by workspace admins.
|
||||
*/
|
||||
export async function sendWebhookToUrl(
|
||||
url: string,
|
||||
secret: string | undefined,
|
||||
payload: WebhookPayload,
|
||||
): Promise<{ success: boolean; statusCode?: number; error?: string }> {
|
||||
const result = webhookUrlSchema.safeParse(url);
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error.issues[0]?.message };
|
||||
}
|
||||
|
||||
const body = JSON.stringify(payload);
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Webhook-Event": payload.event,
|
||||
"X-Webhook-Timestamp": payload.timestamp,
|
||||
};
|
||||
|
||||
if (secret) {
|
||||
headers["X-Webhook-Signature"] = generateSignature(body, secret);
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body,
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
statusCode: response.status,
|
||||
error: `${response.status} ${response.statusText}`,
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true, statusCode: response.status };
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (error instanceof Error && error.name === "AbortError") {
|
||||
return { success: false, error: "Request timed out" };
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send webhook to all active webhooks for a workspace that are subscribed to the event.
|
||||
* Wrapped in try/catch so callers using fire-and-forget don't risk unhandled rejections.
|
||||
*/
|
||||
export async function sendWebhooksForWorkspace(
|
||||
db: dbClient,
|
||||
workspaceId: number,
|
||||
payload: WebhookPayload,
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Get active webhooks for this workspace
|
||||
const webhooks = await webhookRepo.getActiveByWorkspaceId(db, workspaceId);
|
||||
|
||||
// Filter webhooks that are subscribed to this specific event
|
||||
const webhooksForEvent = webhooks.filter((webhook) =>
|
||||
webhook.events.includes(payload.event),
|
||||
);
|
||||
|
||||
// Send to all subscribed webhooks in parallel (fire and forget)
|
||||
const promises = webhooksForEvent.map((webhook) =>
|
||||
sendWebhookToUrl(webhook.url, webhook.secret ?? undefined, payload).then(
|
||||
(result) => {
|
||||
if (!result.success) {
|
||||
console.error(
|
||||
`Webhook delivery failed to ${webhook.url}: ${result.error}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Wait for all to complete but don't block on failures
|
||||
await Promise.allSettled(promises);
|
||||
} catch (error) {
|
||||
console.error("Failed to send webhooks for workspace:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export function createCardWebhookPayload(
|
||||
event: WebhookEventType,
|
||||
card: {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string | null;
|
||||
dueDate?: Date | null;
|
||||
listId: string;
|
||||
},
|
||||
context: {
|
||||
boardId: string;
|
||||
boardName?: string;
|
||||
listName?: string;
|
||||
user?: {
|
||||
id: string;
|
||||
name: string | null;
|
||||
};
|
||||
changes?: Record<string, { from: unknown; to: unknown }>;
|
||||
},
|
||||
): WebhookPayload {
|
||||
return {
|
||||
event,
|
||||
timestamp: new Date().toISOString(),
|
||||
data: {
|
||||
card: {
|
||||
id: card.id,
|
||||
title: card.title,
|
||||
description: card.description,
|
||||
dueDate: card.dueDate?.toISOString() ?? null,
|
||||
listId: card.listId,
|
||||
boardId: context.boardId,
|
||||
},
|
||||
board: context.boardName
|
||||
? { id: context.boardId, name: context.boardName }
|
||||
: undefined,
|
||||
list: context.listName
|
||||
? { id: card.listId, name: context.listName }
|
||||
: undefined,
|
||||
user: context.user,
|
||||
changes: context.changes,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
"dev": "tsc",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint",
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -33,8 +33,10 @@ export const initAuth = (db: dbClient) => {
|
||||
},
|
||||
emailAndPassword: {
|
||||
enabled: env("NEXT_PUBLIC_ALLOW_CREDENTIALS")?.toLowerCase() === "true",
|
||||
disableSignUp:
|
||||
env("NEXT_PUBLIC_DISABLE_SIGN_UP")?.toLowerCase() === "true",
|
||||
// Sign-up restriction is handled by the user.create.before database
|
||||
// hook which checks for pending invitations, allowing invited users
|
||||
// to register even when public sign-up is disabled.
|
||||
disableSignUp: false,
|
||||
sendResetPassword: async (data) => {
|
||||
await sendEmail(data.user.email, "Reset Password", "RESET_PASSWORD", {
|
||||
resetPasswordUrl: data.url,
|
||||
|
||||
195
packages/auth/src/hooks.test.ts
Normal file
195
packages/auth/src/hooks.test.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
vi.mock("next-runtime-env", () => ({
|
||||
env: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@kan/db/repository/member.repo", () => ({
|
||||
getByEmailAndStatus: vi.fn(),
|
||||
getByPublicId: vi.fn(),
|
||||
acceptInvite: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@kan/db/repository/user.repo", () => ({
|
||||
update: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@kan/email", () => ({
|
||||
notificationClient: null,
|
||||
}));
|
||||
|
||||
vi.mock("@kan/shared", () => ({
|
||||
createEmailUnsubscribeLink: vi.fn(),
|
||||
createS3Client: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@aws-sdk/client-s3", () => ({
|
||||
PutObjectCommand: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@novu/api/models/components", () => ({
|
||||
ChatOrPushProviderEnum: { Discord: "discord" },
|
||||
}));
|
||||
|
||||
import { env } from "next-runtime-env";
|
||||
import * as memberRepo from "@kan/db/repository/member.repo";
|
||||
import { createDatabaseHooks } from "./hooks";
|
||||
|
||||
const mockEnv = env as ReturnType<typeof vi.fn>;
|
||||
const mockGetByEmailAndStatus =
|
||||
memberRepo.getByEmailAndStatus as ReturnType<typeof vi.fn>;
|
||||
|
||||
const db = {} as Parameters<typeof createDatabaseHooks>[0];
|
||||
|
||||
const fakeUser = {
|
||||
id: "user-1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
email: "test@example.com",
|
||||
emailVerified: false,
|
||||
name: "Test User",
|
||||
};
|
||||
|
||||
describe("createDatabaseHooks", () => {
|
||||
const hooks = createDatabaseHooks(db);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("user.create.before", () => {
|
||||
it("allows sign-up when DISABLE_SIGN_UP is not set", async () => {
|
||||
mockEnv.mockReturnValue(undefined);
|
||||
|
||||
const result = await hooks.user.create.before(fakeUser, {});
|
||||
expect(result).toBe(true);
|
||||
expect(mockGetByEmailAndStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows sign-up when DISABLE_SIGN_UP is false", async () => {
|
||||
mockEnv.mockImplementation((key: string) =>
|
||||
key === "NEXT_PUBLIC_DISABLE_SIGN_UP" ? "false" : undefined,
|
||||
);
|
||||
|
||||
const result = await hooks.user.create.before(fakeUser, {});
|
||||
expect(result).toBe(true);
|
||||
expect(mockGetByEmailAndStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("blocks sign-up when disabled and user has no pending invitation", async () => {
|
||||
mockEnv.mockImplementation((key: string) =>
|
||||
key === "NEXT_PUBLIC_DISABLE_SIGN_UP" ? "true" : undefined,
|
||||
);
|
||||
mockGetByEmailAndStatus.mockResolvedValue(undefined);
|
||||
|
||||
const result = await hooks.user.create.before(fakeUser, {});
|
||||
expect(result).toBe(false);
|
||||
expect(mockGetByEmailAndStatus).toHaveBeenCalledWith(
|
||||
db,
|
||||
"test@example.com",
|
||||
"invited",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows sign-up when disabled but user has a pending invitation", async () => {
|
||||
mockEnv.mockImplementation((key: string) =>
|
||||
key === "NEXT_PUBLIC_DISABLE_SIGN_UP" ? "true" : undefined,
|
||||
);
|
||||
mockGetByEmailAndStatus.mockResolvedValue({
|
||||
id: "member-1",
|
||||
email: "test@example.com",
|
||||
status: "invited",
|
||||
});
|
||||
|
||||
const result = await hooks.user.create.before(fakeUser, {});
|
||||
expect(result).toBe(true);
|
||||
expect(mockGetByEmailAndStatus).toHaveBeenCalledWith(
|
||||
db,
|
||||
"test@example.com",
|
||||
"invited",
|
||||
);
|
||||
});
|
||||
|
||||
it("blocks sign-up when disabled and invitation exists but domain is not allowed", async () => {
|
||||
mockEnv.mockImplementation((key: string) =>
|
||||
key === "NEXT_PUBLIC_DISABLE_SIGN_UP" ? "true" : undefined,
|
||||
);
|
||||
process.env.BETTER_AUTH_ALLOWED_DOMAINS = "acme.com";
|
||||
mockGetByEmailAndStatus.mockResolvedValue({
|
||||
id: "member-1",
|
||||
email: "test@example.com",
|
||||
status: "invited",
|
||||
});
|
||||
|
||||
const result = await hooks.user.create.before(fakeUser, {});
|
||||
expect(result).toBe(false);
|
||||
|
||||
delete process.env.BETTER_AUTH_ALLOWED_DOMAINS;
|
||||
});
|
||||
|
||||
// The user.create.before hook fires for ALL sign-up paths including
|
||||
// OIDC/social — verify invite bypass works regardless of auth method.
|
||||
it("allows OIDC/social sign-up when disabled but user has a pending invitation", async () => {
|
||||
mockEnv.mockImplementation((key: string) =>
|
||||
key === "NEXT_PUBLIC_DISABLE_SIGN_UP" ? "true" : undefined,
|
||||
);
|
||||
const oidcUser = {
|
||||
...fakeUser,
|
||||
id: "user-oidc",
|
||||
email: "sso@corp.com",
|
||||
image: "https://provider.com/avatar.jpg",
|
||||
};
|
||||
mockGetByEmailAndStatus.mockResolvedValue({
|
||||
id: "member-2",
|
||||
email: "sso@corp.com",
|
||||
status: "invited",
|
||||
});
|
||||
|
||||
const result = await hooks.user.create.before(oidcUser, {});
|
||||
expect(result).toBe(true);
|
||||
expect(mockGetByEmailAndStatus).toHaveBeenCalledWith(
|
||||
db,
|
||||
"sso@corp.com",
|
||||
"invited",
|
||||
);
|
||||
});
|
||||
|
||||
it("blocks OIDC/social sign-up when disabled and user has no pending invitation", async () => {
|
||||
mockEnv.mockImplementation((key: string) =>
|
||||
key === "NEXT_PUBLIC_DISABLE_SIGN_UP" ? "true" : undefined,
|
||||
);
|
||||
const oidcUser = {
|
||||
...fakeUser,
|
||||
id: "user-oidc",
|
||||
email: "random@external.com",
|
||||
image: "https://provider.com/avatar.jpg",
|
||||
};
|
||||
mockGetByEmailAndStatus.mockResolvedValue(undefined);
|
||||
|
||||
const result = await hooks.user.create.before(oidcUser, {});
|
||||
expect(result).toBe(false);
|
||||
expect(mockGetByEmailAndStatus).toHaveBeenCalledWith(
|
||||
db,
|
||||
"random@external.com",
|
||||
"invited",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows sign-up when disabled, invitation exists, and domain is allowed", async () => {
|
||||
mockEnv.mockImplementation((key: string) =>
|
||||
key === "NEXT_PUBLIC_DISABLE_SIGN_UP" ? "true" : undefined,
|
||||
);
|
||||
process.env.BETTER_AUTH_ALLOWED_DOMAINS = "example.com";
|
||||
mockGetByEmailAndStatus.mockResolvedValue({
|
||||
id: "member-1",
|
||||
email: "test@example.com",
|
||||
status: "invited",
|
||||
});
|
||||
|
||||
const result = await hooks.user.create.before(fakeUser, {});
|
||||
expect(result).toBe(true);
|
||||
|
||||
delete process.env.BETTER_AUTH_ALLOWED_DOMAINS;
|
||||
});
|
||||
});
|
||||
});
|
||||
14
packages/auth/vitest.config.ts
Normal file
14
packages/auth/vitest.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import { resolve } from "path";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
root: __dirname,
|
||||
include: ["src/**/*.test.ts"],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@kan/db": resolve(__dirname, "../db/src"),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "board" ADD COLUMN "isArchived" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "board_is_archived_idx" ON "board" USING btree ("isArchived");
|
||||
3709
packages/db/migrations/meta/20260225195947_snapshot.json
Normal file
3709
packages/db/migrations/meta/20260225195947_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -197,6 +197,13 @@
|
||||
"when": 1771192000000,
|
||||
"tag": "20260129210000_AddWorkspaceWebhooks",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 28,
|
||||
"version": "7",
|
||||
"when": 1772049587901,
|
||||
"tag": "20260225195947_AddIsArchivedToBoard",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export const getAllByWorkspaceId = async (
|
||||
db: dbClient,
|
||||
workspaceId: number,
|
||||
userId: string,
|
||||
opts?: { type?: "regular" | "template" },
|
||||
opts?: { type?: "regular" | "template"; archived?: boolean },
|
||||
) => {
|
||||
const boardsData = await db.query.boards.findMany({
|
||||
columns: {
|
||||
@@ -78,6 +78,7 @@ export const getAllByWorkspaceId = async (
|
||||
eq(boards.workspaceId, workspaceId),
|
||||
isNull(boards.deletedAt),
|
||||
opts?.type ? eq(boards.type, opts.type) : undefined,
|
||||
opts?.archived !== undefined ? eq(boards.isArchived, opts.archived) : undefined,
|
||||
),
|
||||
});
|
||||
|
||||
@@ -102,6 +103,7 @@ export const getIdByPublicId = async (db: dbClient, boardPublicId: string) => {
|
||||
columns: {
|
||||
id: true,
|
||||
type: true,
|
||||
isArchived: true,
|
||||
},
|
||||
where: eq(boards.publicId, boardPublicId),
|
||||
});
|
||||
@@ -195,6 +197,7 @@ export const getByPublicId = async (
|
||||
name: true,
|
||||
slug: true,
|
||||
visibility: true,
|
||||
isArchived: true,
|
||||
},
|
||||
with: {
|
||||
userFavorites: {
|
||||
@@ -628,6 +631,7 @@ export const update = async (
|
||||
slug: string | undefined;
|
||||
visibility: BoardVisibilityStatus | undefined;
|
||||
boardPublicId: string;
|
||||
isArchived?: boolean;
|
||||
},
|
||||
) => {
|
||||
const [result] = await db
|
||||
@@ -637,6 +641,7 @@ export const update = async (
|
||||
slug: boardInput.slug,
|
||||
visibility: boardInput.visibility,
|
||||
updatedAt: new Date(),
|
||||
...(boardInput.isArchived !== undefined && { isArchived: boardInput.isArchived })
|
||||
})
|
||||
.where(eq(boards.publicId, boardInput.boardPublicId))
|
||||
.returning({
|
||||
|
||||
@@ -30,22 +30,22 @@ const SYSTEM_ROLES: {
|
||||
description: string;
|
||||
hierarchyLevel: number;
|
||||
}[] = [
|
||||
{
|
||||
name: "admin",
|
||||
description: "Full access to all workspace features",
|
||||
hierarchyLevel: 100,
|
||||
},
|
||||
{
|
||||
name: "member",
|
||||
description: "Standard member with create and edit permissions",
|
||||
hierarchyLevel: 50,
|
||||
},
|
||||
{
|
||||
name: "guest",
|
||||
description: "View-only access",
|
||||
hierarchyLevel: 10,
|
||||
},
|
||||
];
|
||||
{
|
||||
name: "admin",
|
||||
description: "Full access to all workspace features",
|
||||
hierarchyLevel: 100,
|
||||
},
|
||||
{
|
||||
name: "member",
|
||||
description: "Standard member with create and edit permissions",
|
||||
hierarchyLevel: 50,
|
||||
},
|
||||
{
|
||||
name: "guest",
|
||||
description: "View-only access",
|
||||
hierarchyLevel: 10,
|
||||
},
|
||||
];
|
||||
|
||||
export const getCount = async (db: dbClient) => {
|
||||
const result = await db
|
||||
@@ -250,7 +250,7 @@ export const getBySlugWithBoards = (db: dbClient, workspaceSlug: string) => {
|
||||
slug: true,
|
||||
name: true,
|
||||
},
|
||||
where: and(isNull(boards.deletedAt), eq(boards.visibility, "public")),
|
||||
where: and(isNull(boards.deletedAt), eq(boards.visibility, "public"), eq(boards.isArchived, false)),
|
||||
orderBy: [asc(boards.name)]
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,7 +2,6 @@ import { relations, sql } from "drizzle-orm";
|
||||
import {
|
||||
bigint,
|
||||
bigserial,
|
||||
boolean,
|
||||
index,
|
||||
pgEnum,
|
||||
pgTable,
|
||||
@@ -12,8 +11,8 @@ import {
|
||||
uniqueIndex,
|
||||
uuid,
|
||||
varchar,
|
||||
boolean,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
import { imports } from "./imports";
|
||||
import { labels } from "./labels";
|
||||
import { lists } from "./lists";
|
||||
@@ -56,9 +55,11 @@ export const boards = pgTable(
|
||||
.references(() => workspaces.id, { onDelete: "cascade" }),
|
||||
visibility: boardVisibilityEnum("visibility").notNull().default("private"),
|
||||
type: boardTypeEnum("type").notNull().default("regular"),
|
||||
isArchived: boolean("isArchived").notNull().default(false),
|
||||
sourceBoardId: bigint("sourceBoardId", { mode: "number" }),
|
||||
},
|
||||
(table) => [
|
||||
index("board_is_archived_idx").on(table.isArchived),
|
||||
index("board_visibility_idx").on(table.visibility),
|
||||
index("board_type_idx").on(table.type),
|
||||
index("board_source_idx").on(table.sourceBoardId),
|
||||
|
||||
Reference in New Issue
Block a user