Compare commits
38 Commits
feat/loggi
...
feat/onboa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a31885e265 | ||
|
|
5c5d208fc4 | ||
|
|
cb76926041 | ||
|
|
6dfee580f7 | ||
|
|
e20104a292 | ||
|
|
dbf53717db | ||
|
|
0dc5092ebc | ||
|
|
e81f5d7fe6 | ||
|
|
f4bb7f8443 | ||
|
|
ac2d675934 | ||
|
|
edc9e1254e | ||
|
|
a713d09c46 | ||
|
|
385bc0de61 | ||
|
|
1e93d85318 | ||
|
|
abaf2badb0 | ||
|
|
603cc46b7d | ||
|
|
e9d338384a | ||
|
|
8a2a9ecef5 | ||
|
|
3a44957662 | ||
|
|
20e6c99e32 | ||
|
|
c0aae5449f | ||
|
|
596f346181 | ||
|
|
7b59e2ac07 | ||
|
|
2a9b1fd2b5 | ||
|
|
a2d4314e14 | ||
|
|
f94e4d6da2 | ||
|
|
97f4defc0e | ||
|
|
dd29dc89fd | ||
|
|
486cdf8313 | ||
|
|
67cf523ee8 | ||
|
|
0b45ceff7f | ||
|
|
76b2c58461 | ||
|
|
b8d1fe230d | ||
|
|
bd652ba6c9 | ||
|
|
1fb56aafd0 | ||
|
|
91deaca6ec | ||
|
|
ab81db0f56 | ||
|
|
29080fa851 |
@@ -51,6 +51,9 @@ TRELLO_APP_SECRET=
|
||||
# If not provided, rate limiting will use in-memory storage
|
||||
REDIS_URL= # e.g. redis://default:your_password@your_host:6379
|
||||
|
||||
# Logging (optional - debug, info, warn, error; defaults to debug in dev, info in prod)
|
||||
LOG_LEVEL=
|
||||
|
||||
# OAuth providers (optional)
|
||||
BETTER_AUTH_TRUSTED_ORIGINS=
|
||||
# Optional: Restrict OIDC/Social sign-ins to specific email domains (comma-separated)
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -54,3 +54,5 @@ i18n.cache
|
||||
|
||||
# pgdata
|
||||
/apps/web/pgdata
|
||||
|
||||
.claude
|
||||
20
AGENTS.md
20
AGENTS.md
@@ -185,9 +185,17 @@ Use `assertUserInWorkspace` helper for workspace checks.
|
||||
|
||||
- Use TRPCError with appropriate codes (UNAUTHORIZED, NOT_FOUND, etc.)
|
||||
- Provide user-friendly error messages
|
||||
- Log errors appropriately
|
||||
- Log errors appropriately using the `@kan/logger` package
|
||||
- Show popup notifications for user-facing errors
|
||||
|
||||
### Logging
|
||||
|
||||
- Import from `@kan/logger`: `import { createLogger } from "@kan/logger"`
|
||||
- Create a module-scoped logger: `const logger = createLogger("module-name")`
|
||||
- Log level is controlled by `LOG_LEVEL` env var (debug, info, warn, error)
|
||||
- Defaults to `debug` in development, `info` in production
|
||||
- Never use `console.log` — always use the logger
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Creating a Card
|
||||
@@ -219,6 +227,16 @@ Use `assertUserInWorkspace` helper for workspace checks.
|
||||
5. **Frontend**: Add UI components in `apps/web/src/`
|
||||
6. **i18n**: Add translations for new strings
|
||||
|
||||
## Adding a New Environment Variable
|
||||
|
||||
Update all of the following:
|
||||
|
||||
1. `.env.example` — add the variable with an empty value and a comment explaining it
|
||||
2. `turbo.json` — add to `globalEnv` (or `globalPassThroughEnv` for CI/platform vars)
|
||||
3. `docker-compose.yml` — add to the `web` service `environment` section
|
||||
4. `cloud/docker-compose.yml` — add to the `web` service `environment` section
|
||||
5. `README.md` — add a row to the Environment Variables table
|
||||
|
||||
## Database Changes
|
||||
|
||||
- Always create migrations (never modify existing migrations)
|
||||
|
||||
@@ -213,6 +213,7 @@ pnpm dev
|
||||
| `NEXT_PUBLIC_DISABLE_SIGN_UP` | Disable sign up | For authentication | `false` |
|
||||
| `NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY` | Hide “Powered by kan.bn” on public boards (self-host) | For white labelling | `true` |
|
||||
| `KAN_ADMIN_API_KEY` | Admin API key for stats and admin endpoints | For admin/monitoring | `your-secret-admin-key` |
|
||||
| `LOG_LEVEL` | Log verbosity level (debug, info, warn, error) | No (defaults to debug in dev, info in prod) | `info` |
|
||||
|
||||
See `.env.example` for a complete list of supported environment variables.
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ https://kan.bn/api/v1
|
||||
|
||||
## Authentication
|
||||
|
||||
Most endpoints require authentication using your API key. You can create one in the [settings page](https://kan.bn/settings) of your account. Include this key in the `x-api-key` header of each request.
|
||||
Most endpoints require authentication using your API key. You can create one in the [settings page](https://kan.bn/settings) of your account. Include this key as a Bearer token in the `Authorization` header of each request.
|
||||
|
||||
```
|
||||
'x-api-key': kan_123456789
|
||||
'Authorization': 'Bearer kan_123456789'
|
||||
```
|
||||
|
||||
## Response codes
|
||||
|
||||
@@ -103,6 +103,9 @@ checksums:
|
||||
tMYIu7/message: 8b1f0bb96a905646ecfad1cfdfa42168
|
||||
tMYIu7/origin/0/0: d77eb1f90f2fa86a55181629d88d237a
|
||||
tMYIu7/translation: 8b1f0bb96a905646ecfad1cfdfa42168
|
||||
UGCIzB/translation: f7b6699c16726afc48f1e1d1339b2e7e
|
||||
UGCIzB/message: f7b6699c16726afc48f1e1d1339b2e7e
|
||||
UGCIzB/origin/0/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
B3xxao/message: 9d80becc4fe65a74e0004e4fd290735d
|
||||
B3xxao/origin/0/0: 6812b79a8fdc3527586b523d4c137340
|
||||
B3xxao/translation: 9d80becc4fe65a74e0004e4fd290735d
|
||||
@@ -516,23 +519,27 @@ checksums:
|
||||
dEgA5A/message: 2e2a849c2223911717de8caa2c71bade
|
||||
dEgA5A/origin/0/0: cf5db0532c4f1cf49cebe78926c03385
|
||||
dEgA5A/origin/1/0: a50c4fbde8da6e0a77c4e0aef0b44dea
|
||||
dEgA5A/origin/2/0: 522726bb62051656dc0d165185d6b451
|
||||
dEgA5A/origin/3/0: b2059fa4b8b99cc10440da26475b234c
|
||||
dEgA5A/origin/4/0: 2a382c1cdf010f01a8e95c3e05f6df53
|
||||
dEgA5A/origin/5/0: d55f532bb5ae247e3a018978be0da463
|
||||
dEgA5A/origin/6/0: 4b4f55c033add8db69178563832227c6
|
||||
dEgA5A/origin/7/0: 68450df287fb46ea629e818732c1291a
|
||||
dEgA5A/origin/8/0: 03c00909b41be01231cd3e843c8ee44e
|
||||
dEgA5A/origin/9/0: 0627e0040ca4939c9a57349e98c51797
|
||||
dEgA5A/origin/10/0: 15792234f408027822ee71ccddde6f9b
|
||||
dEgA5A/origin/11/0: 5c60161e173f5eae9276d8e4c70b4ebb
|
||||
dEgA5A/origin/12/0: a1fde3fcce608870830f168f027727ff
|
||||
dEgA5A/origin/13/0: 83911e3eacbad4583e2b1647a784d154
|
||||
dEgA5A/origin/14/0: dbad0c8d7863cb41f5495264f82e7081
|
||||
dEgA5A/origin/2/0: 97bd206a05414e2a571020d64e166093
|
||||
dEgA5A/origin/3/0: 522726bb62051656dc0d165185d6b451
|
||||
dEgA5A/origin/4/0: b2059fa4b8b99cc10440da26475b234c
|
||||
dEgA5A/origin/5/0: 2a382c1cdf010f01a8e95c3e05f6df53
|
||||
dEgA5A/origin/6/0: d55f532bb5ae247e3a018978be0da463
|
||||
dEgA5A/origin/7/0: 4b4f55c033add8db69178563832227c6
|
||||
dEgA5A/origin/8/0: 68450df287fb46ea629e818732c1291a
|
||||
dEgA5A/origin/9/0: 03c00909b41be01231cd3e843c8ee44e
|
||||
dEgA5A/origin/10/0: 0627e0040ca4939c9a57349e98c51797
|
||||
dEgA5A/origin/11/0: 15792234f408027822ee71ccddde6f9b
|
||||
dEgA5A/origin/12/0: 5c60161e173f5eae9276d8e4c70b4ebb
|
||||
dEgA5A/origin/13/0: a1fde3fcce608870830f168f027727ff
|
||||
dEgA5A/origin/14/0: 83911e3eacbad4583e2b1647a784d154
|
||||
dEgA5A/origin/15/0: dbad0c8d7863cb41f5495264f82e7081
|
||||
dEgA5A/translation: 2e2a849c2223911717de8caa2c71bade
|
||||
kryGs%2B/message: bba0beaced7ea954ceb980f2b022ffee
|
||||
kryGs%2B/origin/0/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
kryGs%2B/translation: bba0beaced7ea954ceb980f2b022ffee
|
||||
sU2uWc/translation: 3fe52244676faf64e5266b6713d01e07
|
||||
sU2uWc/message: 3fe52244676faf64e5266b6713d01e07
|
||||
sU2uWc/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
fEY2vP/message: 91509e2f92b0b3b11330b6983139fdbf
|
||||
fEY2vP/origin/0/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
fEY2vP/origin/1/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
@@ -541,8 +548,9 @@ checksums:
|
||||
ZH7TVS/origin/0/0: 984122104bcc6d8fa6d4c8a35e7751d2
|
||||
ZH7TVS/translation: 7c34f59f4005e6cb3a6ff546ea0b96e3
|
||||
rQXTmd/message: 9387a4702b302f23f2f7c20b73a2b3dd
|
||||
rQXTmd/origin/0/0: 1b05beba6bdd908015fa8e798717f024
|
||||
rQXTmd/origin/1/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
rQXTmd/origin/0/0: fc0c18a095a68277a0f90a115942683b
|
||||
rQXTmd/origin/1/0: 1b05beba6bdd908015fa8e798717f024
|
||||
rQXTmd/origin/2/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
rQXTmd/translation: 9387a4702b302f23f2f7c20b73a2b3dd
|
||||
VhMDMg/message: a552fc5c4189ebc3e2e6018edda7d18f
|
||||
VhMDMg/origin/0/0: 627b72cd19a7154ae1c28cbd7ca0c730
|
||||
@@ -550,6 +558,9 @@ checksums:
|
||||
VhMDMg/origin/2/0: 0627e0040ca4939c9a57349e98c51797
|
||||
VhMDMg/origin/3/0: 0627e0040ca4939c9a57349e98c51797
|
||||
VhMDMg/translation: a552fc5c4189ebc3e2e6018edda7d18f
|
||||
fzGyWs/message: 25dbbca7a83f6af119179e7a56c953d4
|
||||
fzGyWs/origin/0/0: 627b72cd19a7154ae1c28cbd7ca0c730
|
||||
fzGyWs/translation: 25dbbca7a83f6af119179e7a56c953d4
|
||||
yD3ZSw/message: 293d49fc3c75e9c425b64bd7126e6b46
|
||||
yD3ZSw/origin/0/0: 627b72cd19a7154ae1c28cbd7ca0c730
|
||||
yD3ZSw/translation: 293d49fc3c75e9c425b64bd7126e6b46
|
||||
@@ -696,6 +707,18 @@ checksums:
|
||||
F0YmUY/message: 9def232c9163d109f0e9093684cfe75f
|
||||
F0YmUY/origin/0/0: 1b05beba6bdd908015fa8e798717f024
|
||||
F0YmUY/translation: 9def232c9163d109f0e9093684cfe75f
|
||||
0utuU6/translation: 9d7e453b24dfaf217352aeadc262074d
|
||||
0utuU6/message: 9d7e453b24dfaf217352aeadc262074d
|
||||
0utuU6/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
fDQ7hA/translation: 1d24ecbad9f2f0081ab51c7366e40506
|
||||
fDQ7hA/message: 1d24ecbad9f2f0081ab51c7366e40506
|
||||
fDQ7hA/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
1bJKhj/translation: f61803ac8da8253e4ba35f92b3af1101
|
||||
1bJKhj/message: f61803ac8da8253e4ba35f92b3af1101
|
||||
1bJKhj/origin/0/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
uvH2xS/translation: ecc2617544a6d385d18678577fa112fd
|
||||
uvH2xS/message: ecc2617544a6d385d18678577fa112fd
|
||||
uvH2xS/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
9PaIxv/message: da95932e7a1465a5d21aa3b855a46dc2
|
||||
9PaIxv/origin/0/0: c0b59968b2b234167c488a05cc38710b
|
||||
9PaIxv/translation: da95932e7a1465a5d21aa3b855a46dc2
|
||||
@@ -835,7 +858,8 @@ checksums:
|
||||
tmpW%2Bw/origin/0/0: 0ad2af981579a62b084b3d41462c5546
|
||||
tmpW%2Bw/translation: e915cd160651ad4b61a67e8da0737c23
|
||||
nabda1/message: 18811ccf76fbb7a2772d648f7c5f0ea7
|
||||
nabda1/origin/0/0: 1b05beba6bdd908015fa8e798717f024
|
||||
nabda1/origin/0/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
nabda1/origin/1/0: 1b05beba6bdd908015fa8e798717f024
|
||||
nabda1/translation: 18811ccf76fbb7a2772d648f7c5f0ea7
|
||||
njJFtc/message: a3dae0c9ddbc73677aad9812e3dffd69
|
||||
njJFtc/origin/0/0: b2059fa4b8b99cc10440da26475b234c
|
||||
@@ -916,12 +940,15 @@ checksums:
|
||||
vUr5JT/origin/0/0: 4a1be3707d14c696a16f453d132fb248
|
||||
vUr5JT/translation: 6ae8282ab8b4a01b4ce87b119e13714b
|
||||
DPfwMq/message: ffd408fa29d5bc9039ef8ea1b9b699bb
|
||||
DPfwMq/origin/0/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/1/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/2/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/0/0: 81b173297e9c022d9ebde5f7018c95f9
|
||||
DPfwMq/origin/1/0: 120e67abaa784f3adb192e7519919a81
|
||||
DPfwMq/origin/2/0: fc68b5394b9b466d5d67df855060544d
|
||||
DPfwMq/origin/3/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/4/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/5/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/6/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/7/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/origin/8/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
DPfwMq/translation: ffd408fa29d5bc9039ef8ea1b9b699bb
|
||||
R3FsF4/message: 142b391cca3e05f2af9617092f7358c5
|
||||
R3FsF4/origin/0/0: 3a89a46ac678e34dc8eb687b07a4ad4b
|
||||
@@ -943,6 +970,16 @@ checksums:
|
||||
zxKs%2By/message: 0b6c8ad7aba0873b7e212d5f1949ca97
|
||||
zxKs%2By/origin/0/0: db1e755dc7d73e037f425111bf7c30a6
|
||||
zxKs%2By/translation: 0b6c8ad7aba0873b7e212d5f1949ca97
|
||||
euc6Ns/translation: 27756566785c2b8463e21582c4bb619b
|
||||
euc6Ns/message: 27756566785c2b8463e21582c4bb619b
|
||||
euc6Ns/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
BTDPLo/translation: f6a33afcf5ff1489551c94867e083dca
|
||||
BTDPLo/message: f6a33afcf5ff1489551c94867e083dca
|
||||
BTDPLo/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
BTDPLo/origin/1/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
06EQqT/translation: e9f56489fb395a890ae2557200c29a36
|
||||
06EQqT/message: e9f56489fb395a890ae2557200c29a36
|
||||
06EQqT/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
ePK91l/message: eee7f39ff90b18852afc1671f21fbaa9
|
||||
ePK91l/origin/0/0: b697a20a9536be4dd8ecd143461eafc7
|
||||
ePK91l/origin/1/0: dc1a240d2d0cccb102c67ca311d4e457
|
||||
@@ -989,6 +1026,9 @@ checksums:
|
||||
IqjpYe/message: 81d41cf573a7109c376d30d905beb596
|
||||
IqjpYe/origin/0/0: 5f6c4a0c9be5098099167871ae1564af
|
||||
IqjpYe/translation: 81d41cf573a7109c376d30d905beb596
|
||||
bFREhu/translation: b2cc9a311fc28d1594252f697c7f5342
|
||||
bFREhu/message: b2cc9a311fc28d1594252f697c7f5342
|
||||
bFREhu/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
0%2BewPp/message: 785fe23c0eef0a5b60b5b2a88151de31
|
||||
0%2BewPp/origin/0/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
0%2BewPp/origin/1/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
@@ -1167,6 +1207,9 @@ checksums:
|
||||
l31EoQ/message: b4c05c9174637f4da2a41ead0d65bc01
|
||||
l31EoQ/origin/0/0: 4980d8e27a628e7cd9a70b839d844083
|
||||
l31EoQ/translation: b4c05c9174637f4da2a41ead0d65bc01
|
||||
q3il6U/message: 79a4fffd70bfbedb6174ea4c767f4ab9
|
||||
q3il6U/origin/0/0: 627b72cd19a7154ae1c28cbd7ca0c730
|
||||
q3il6U/translation: 79a4fffd70bfbedb6174ea4c767f4ab9
|
||||
"%2B3TYXa/message": 9fa1c942feb40d6350322b71561df789
|
||||
"%2B3TYXa/origin/0/0": 4980d8e27a628e7cd9a70b839d844083
|
||||
"%2B3TYXa/translation": 9fa1c942feb40d6350322b71561df789
|
||||
@@ -1420,9 +1463,10 @@ checksums:
|
||||
h7MgpO/origin/0/0: feacdaf2791aaddec128f463ffa2e807
|
||||
h7MgpO/translation: ef00d7494b69def6841620bd6554d040
|
||||
h8DugX/message: 6f15627a90002323eac018274b6922d6
|
||||
h8DugX/origin/0/0: db1e755dc7d73e037f425111bf7c30a6
|
||||
h8DugX/origin/1/0: 984122104bcc6d8fa6d4c8a35e7751d2
|
||||
h8DugX/origin/2/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
h8DugX/origin/0/0: 120e67abaa784f3adb192e7519919a81
|
||||
h8DugX/origin/1/0: db1e755dc7d73e037f425111bf7c30a6
|
||||
h8DugX/origin/2/0: 984122104bcc6d8fa6d4c8a35e7751d2
|
||||
h8DugX/origin/3/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
h8DugX/translation: 6f15627a90002323eac018274b6922d6
|
||||
AZI%2Fwb/message: e1ceda0c6cc32fb04cb9423582ad8fe4
|
||||
AZI%2Fwb/origin/0/0: c0b59968b2b234167c488a05cc38710b
|
||||
@@ -1433,6 +1477,9 @@ checksums:
|
||||
vXIe7J/message: 277fd1a41cc237a437cd1d5e4a80463b
|
||||
vXIe7J/origin/0/0: 627b72cd19a7154ae1c28cbd7ca0c730
|
||||
vXIe7J/translation: 277fd1a41cc237a437cd1d5e4a80463b
|
||||
k7rCa%2F/message: 890b7851af05f97ac53644e4154c13b4
|
||||
k7rCa%2F/origin/0/0: 3de48fb04f7df9bdbe399911cdb3fe86
|
||||
k7rCa%2F/translation: 890b7851af05f97ac53644e4154c13b4
|
||||
8Is1ih/message: 624592ba2ecf647a3fe60d493c1ee71a
|
||||
8Is1ih/origin/0/0: ff4a160eca5f8da4f075fd1dd6061fa6
|
||||
8Is1ih/origin/1/0: fadd5a5b6963ec5f261072f7e8d2140b
|
||||
@@ -1464,12 +1511,14 @@ checksums:
|
||||
TCt%2F8K/translation: ffd8033392a01d7b33f13afa51bccb9a
|
||||
edWbV6/message: 4a4805700b31ec65bcb3ca962bccfd1b
|
||||
edWbV6/origin/0/0: 85d0a3237fa9329fba3f0278a2cd7284
|
||||
edWbV6/origin/1/0: 1b05beba6bdd908015fa8e798717f024
|
||||
edWbV6/origin/2/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
edWbV6/origin/3/0: faa8a2576972b4eb31565d8ea6098f43
|
||||
edWbV6/origin/1/0: fc0c18a095a68277a0f90a115942683b
|
||||
edWbV6/origin/2/0: 1b05beba6bdd908015fa8e798717f024
|
||||
edWbV6/origin/3/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
edWbV6/origin/4/0: faa8a2576972b4eb31565d8ea6098f43
|
||||
edWbV6/translation: 4a4805700b31ec65bcb3ca962bccfd1b
|
||||
2BBAbc/message: 94f13e7ef909a4de9db7abaa1f9f0b61
|
||||
2BBAbc/origin/0/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
2BBAbc/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
2BBAbc/origin/1/0: 15fd9c05fc09ccbbb54aadc4aa5e5501
|
||||
2BBAbc/translation: 94f13e7ef909a4de9db7abaa1f9f0b61
|
||||
zQX5Dj/message: e925e2e6ccaf0eb4064a888aaea8d3c2
|
||||
zQX5Dj/origin/0/0: 5e445d66b146e0ca392faa35098701c4
|
||||
@@ -1507,6 +1556,10 @@ checksums:
|
||||
DbZgsJ/message: 7a1c63b75cea690b3fda6c3f203e31ff
|
||||
DbZgsJ/origin/0/0: 0ad2af981579a62b084b3d41462c5546
|
||||
DbZgsJ/translation: 7a1c63b75cea690b3fda6c3f203e31ff
|
||||
hB02vO/translation: c13aa2a155ab4aff1c8fed6650a94e1a
|
||||
hB02vO/message: c13aa2a155ab4aff1c8fed6650a94e1a
|
||||
hB02vO/origin/0/0: fc68b5394b9b466d5d67df855060544d
|
||||
hB02vO/origin/1/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
IDvohQ/message: 35d0822f65971b97774b962561b02649
|
||||
IDvohQ/origin/0/0: 1d6b5f88cdad160a67842f6b65387448
|
||||
IDvohQ/translation: 35d0822f65971b97774b962561b02649
|
||||
@@ -1514,6 +1567,9 @@ checksums:
|
||||
jl3aEJ/placeholders/0/0: 06f23520ebabfffed4999b5bcf61c9cd
|
||||
jl3aEJ/origin/0/0: 1d6b5f88cdad160a67842f6b65387448
|
||||
jl3aEJ/translation: 4c38799ff25321ea25017bf4cd8e2e4f
|
||||
agPptk/message: 43983f2178fb28aacaef6c5f41282c4a
|
||||
agPptk/origin/0/0: 3de48fb04f7df9bdbe399911cdb3fe86
|
||||
agPptk/translation: 43983f2178fb28aacaef6c5f41282c4a
|
||||
W%2FElkg/message: 1f527cd6d1ed602930bcaa303f503b51
|
||||
W%2FElkg/origin/0/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
W%2FElkg/translation: 1f527cd6d1ed602930bcaa303f503b51
|
||||
@@ -1537,9 +1593,9 @@ checksums:
|
||||
"%2FbZzdR/message": 40ea106c56d79634e058e087ad322025
|
||||
"%2FbZzdR/origin/0/0": c0b59968b2b234167c488a05cc38710b
|
||||
"%2FbZzdR/translation": 40ea106c56d79634e058e087ad322025
|
||||
hty0d5/translation: b4b1c8396e445e8b7956c747da2cf601
|
||||
hty0d5/message: b4b1c8396e445e8b7956c747da2cf601
|
||||
hty0d5/origin/0/0: 04c86ea96f180745739d59e822da4026
|
||||
hty0d5/translation: b4b1c8396e445e8b7956c747da2cf601
|
||||
"%2B8Nek%2F/message": 818f1192e32bb855597f930d3e78806e
|
||||
"%2B8Nek%2F/origin/0/0": 9737a9cef0646ff4bbd942b1924b9a2c
|
||||
"%2B8Nek%2F/origin/1/0": 82a5cf8d69f098641eb357bf102cd504
|
||||
@@ -1547,6 +1603,12 @@ checksums:
|
||||
vneRvS/message: e228107df80015377112d41f4f155cb3
|
||||
vneRvS/origin/0/0: 8628ddb6f4ded25c0f17e2cbda8207d3
|
||||
vneRvS/translation: e228107df80015377112d41f4f155cb3
|
||||
51UCsN/translation: ce14fde5e972c2b5276c25ddded817c4
|
||||
51UCsN/message: ce14fde5e972c2b5276c25ddded817c4
|
||||
51UCsN/origin/0/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
J4%2BOTA/translation: 6ad27e803483208b5313660b7b5791d3
|
||||
J4%2BOTA/message: 6ad27e803483208b5313660b7b5791d3
|
||||
J4%2BOTA/origin/0/0: fd0e66cef8d46e96bcf3143a12f9b6db
|
||||
BOqTi5/message: 11edf0427766c26db541d46379dc3c16
|
||||
BOqTi5/placeholders/0/0: 9db68a4b386b9fa1fd1d5797c32be100
|
||||
BOqTi5/placeholders/1/0: b837a9e8bd06b58be95699b3ee663f43
|
||||
@@ -1861,10 +1923,16 @@ checksums:
|
||||
PZCqeW/origin/1/0: 8628ddb6f4ded25c0f17e2cbda8207d3
|
||||
PZCqeW/translation: 325dea6dd0348a27a6818db2c1340c98
|
||||
fuwKpE/message: 346a18a54564881c888b95873d03df44
|
||||
fuwKpE/origin/0/0: 1b05beba6bdd908015fa8e798717f024
|
||||
fuwKpE/origin/1/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
fuwKpE/origin/2/0: faa8a2576972b4eb31565d8ea6098f43
|
||||
fuwKpE/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
fuwKpE/origin/1/0: fd0e66cef8d46e96bcf3143a12f9b6db
|
||||
fuwKpE/origin/2/0: fc0c18a095a68277a0f90a115942683b
|
||||
fuwKpE/origin/3/0: 1b05beba6bdd908015fa8e798717f024
|
||||
fuwKpE/origin/4/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
fuwKpE/origin/5/0: faa8a2576972b4eb31565d8ea6098f43
|
||||
fuwKpE/translation: 346a18a54564881c888b95873d03df44
|
||||
nYNMZZ/translation: d5a8a1b38eaf02d02cd6ac2211b42a92
|
||||
nYNMZZ/message: d5a8a1b38eaf02d02cd6ac2211b42a92
|
||||
nYNMZZ/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
aHCEmh/message: ce27f1aeacccc542a174c4b2bce022b0
|
||||
aHCEmh/origin/0/0: 98e2302d0d8d12ce65c4140b87450673
|
||||
aHCEmh/origin/1/0: 634473ef965836871efe40875a36b6ab
|
||||
@@ -2013,9 +2081,9 @@ checksums:
|
||||
YEOVrT/message: 86b76024524fc585b2c3950126ef6f62
|
||||
YEOVrT/origin/0/0: fadd5a5b6963ec5f261072f7e8d2140b
|
||||
YEOVrT/translation: 86b76024524fc585b2c3950126ef6f62
|
||||
"%2B5kO8P/translation": 77b65adf8ae2b69eaed51b6104ded65e
|
||||
"%2B5kO8P/message": 77b65adf8ae2b69eaed51b6104ded65e
|
||||
"%2B5kO8P/origin/0/0": 04c86ea96f180745739d59e822da4026
|
||||
"%2B5kO8P/translation": 77b65adf8ae2b69eaed51b6104ded65e
|
||||
tfDRzk/message: f7a2929f33bc420195e59ac5a8bcd454
|
||||
tfDRzk/origin/0/0: a50c4fbde8da6e0a77c4e0aef0b44dea
|
||||
tfDRzk/origin/1/0: b2059fa4b8b99cc10440da26475b234c
|
||||
@@ -2045,6 +2113,9 @@ checksums:
|
||||
K3iLeO/message: e3bf6f95419169f460fb6918a0afc512
|
||||
K3iLeO/origin/0/0: 5e2ed4920ffd2f535ece848ff44900bd
|
||||
K3iLeO/translation: e3bf6f95419169f460fb6918a0afc512
|
||||
zYRVNp/translation: 8b36455dd5b43e56d2ab06e522c3bacc
|
||||
zYRVNp/message: 8b36455dd5b43e56d2ab06e522c3bacc
|
||||
zYRVNp/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
wgNoIs/message: eedc7cdb02de467c15dc418a066a77f2
|
||||
wgNoIs/origin/0/0: 3c57a918258af4b44c187f58d203345f
|
||||
wgNoIs/origin/1/0: 3c57a918258af4b44c187f58d203345f
|
||||
@@ -2074,7 +2145,9 @@ checksums:
|
||||
v3YoMA/origin/0/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
v3YoMA/translation: 3fff865dc00435f82896fc302ea45630
|
||||
8AbRER/message: 3cb81c4829857556f5d117c9a23f7bcc
|
||||
8AbRER/origin/0/0: 2201bf679c663a732d7f1b3c3ce3897f
|
||||
8AbRER/origin/0/0: 81b173297e9c022d9ebde5f7018c95f9
|
||||
8AbRER/origin/1/0: 0eeeb7e68164ff118040918f1ef00a7a
|
||||
8AbRER/origin/2/0: 2201bf679c663a732d7f1b3c3ce3897f
|
||||
8AbRER/translation: 3cb81c4829857556f5d117c9a23f7bcc
|
||||
Eit43O/message: b5d9a1edd988f455041aee9fa7fb3d63
|
||||
Eit43O/origin/0/0: 1d6b5f88cdad160a67842f6b65387448
|
||||
@@ -2139,6 +2212,9 @@ checksums:
|
||||
zEcnBA/message: 513c3b4d9f0fda60fe079d42dcbd3882
|
||||
zEcnBA/origin/0/0: c0b59968b2b234167c488a05cc38710b
|
||||
zEcnBA/translation: 513c3b4d9f0fda60fe079d42dcbd3882
|
||||
4Revpc/message: 3adb3ae7891c5736776df840cff2360b
|
||||
4Revpc/origin/0/0: 3de48fb04f7df9bdbe399911cdb3fe86
|
||||
4Revpc/translation: 3adb3ae7891c5736776df840cff2360b
|
||||
"%2B%2BrVAm/message": 2227508afb0d38a027e323207e47d4a2
|
||||
"%2B%2BrVAm/origin/0/0": eb63312c63f6c8d2a5b6520c89012123
|
||||
"%2B%2BrVAm/translation": 2227508afb0d38a027e323207e47d4a2
|
||||
@@ -2168,9 +2244,9 @@ checksums:
|
||||
zzDlyQ/origin/0/0: d96af8a06c2e38a72238bcd16e426859
|
||||
zzDlyQ/origin/1/0: d96af8a06c2e38a72238bcd16e426859
|
||||
zzDlyQ/translation: c43827becada6750f7a25890905f38b9
|
||||
DBC3t5/translation: d21934fb36dd8a82e36352968207d44e
|
||||
DBC3t5/message: d21934fb36dd8a82e36352968207d44e
|
||||
DBC3t5/origin/0/0: 04c86ea96f180745739d59e822da4026
|
||||
DBC3t5/translation: d21934fb36dd8a82e36352968207d44e
|
||||
uZg2%2Bw/message: 21af8119141afcce5f12da489f34db6a
|
||||
uZg2%2Bw/origin/0/0: dbad0c8d7863cb41f5495264f82e7081
|
||||
uZg2%2Bw/translation: 21af8119141afcce5f12da489f34db6a
|
||||
@@ -2235,6 +2311,9 @@ checksums:
|
||||
C6gv54/message: 1573c77059bc92df17e661befda8d9b7
|
||||
C6gv54/origin/0/0: 0ad2af981579a62b084b3d41462c5546
|
||||
C6gv54/translation: 1573c77059bc92df17e661befda8d9b7
|
||||
nKBUeF/translation: 07e3ce10820b8ab28643e38b2ed51f64
|
||||
nKBUeF/message: 07e3ce10820b8ab28643e38b2ed51f64
|
||||
nKBUeF/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
jlB2RY/message: 67a76bf346ab4b48563269e9d941a64a
|
||||
jlB2RY/origin/0/0: 0627e0040ca4939c9a57349e98c51797
|
||||
jlB2RY/translation: 67a76bf346ab4b48563269e9d941a64a
|
||||
@@ -2307,6 +2386,9 @@ checksums:
|
||||
MHrjPM/message: 344e64395eaff6822a57d18623853e1a
|
||||
MHrjPM/origin/0/0: a50c4fbde8da6e0a77c4e0aef0b44dea
|
||||
MHrjPM/translation: 344e64395eaff6822a57d18623853e1a
|
||||
wK4OTM/translation: b2a89bfd8e13d77f626e84840602a603
|
||||
wK4OTM/message: b2a89bfd8e13d77f626e84840602a603
|
||||
wK4OTM/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
Se%2FUIp/message: d60813ea824f373462471e092d136eed
|
||||
Se%2FUIp/origin/0/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
Se%2FUIp/origin/1/0: eb63312c63f6c8d2a5b6520c89012123
|
||||
@@ -2349,9 +2431,10 @@ checksums:
|
||||
LeM5RY/origin/0/0: 0dccc39d0debbf3e089cb6d292de762b
|
||||
LeM5RY/translation: dfeac280858332082ad34d2623a59863
|
||||
W1ewR0/message: ddcac5d14d48358b043a9487b42eec16
|
||||
W1ewR0/origin/0/0: 1b05beba6bdd908015fa8e798717f024
|
||||
W1ewR0/origin/1/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
W1ewR0/origin/2/0: faa8a2576972b4eb31565d8ea6098f43
|
||||
W1ewR0/origin/0/0: fc0c18a095a68277a0f90a115942683b
|
||||
W1ewR0/origin/1/0: 1b05beba6bdd908015fa8e798717f024
|
||||
W1ewR0/origin/2/0: 9c8b544f5db130cd7c5eda9064ba3957
|
||||
W1ewR0/origin/3/0: faa8a2576972b4eb31565d8ea6098f43
|
||||
W1ewR0/translation: ddcac5d14d48358b043a9487b42eec16
|
||||
zl1b97/message: 90112ea12ec6fa42097a6e022ae048a4
|
||||
zl1b97/origin/0/0: 984122104bcc6d8fa6d4c8a35e7751d2
|
||||
@@ -2384,6 +2467,12 @@ checksums:
|
||||
2QGEbi/message: 550198b2c87f06726a843c79c1026ed9
|
||||
2QGEbi/origin/0/0: 4b4f55c033add8db69178563832227c6
|
||||
2QGEbi/translation: 550198b2c87f06726a843c79c1026ed9
|
||||
Oo4E6p/translation: af985cf72c47df76e2f2139a427d9948
|
||||
Oo4E6p/message: af985cf72c47df76e2f2139a427d9948
|
||||
Oo4E6p/origin/0/0: 97bd206a05414e2a571020d64e166093
|
||||
K7k9u3/translation: 1cf9d227042f6a65adba38cb7296830d
|
||||
K7k9u3/message: 1cf9d227042f6a65adba38cb7296830d
|
||||
K7k9u3/origin/0/0: fd0e66cef8d46e96bcf3143a12f9b6db
|
||||
8yFGGF/message: 39025a0c53818438829603d213baef06
|
||||
8yFGGF/origin/0/0: 68450df287fb46ea629e818732c1291a
|
||||
8yFGGF/translation: 39025a0c53818438829603d213baef06
|
||||
@@ -2626,9 +2715,9 @@ checksums:
|
||||
v1kQyJ/origin/0/0: 77c9f6139024b2069e4fdc5120f69cb5
|
||||
v1kQyJ/origin/1/0: 566b5982085ae649b2be0c614f666591
|
||||
v1kQyJ/translation: 0329bc102d3ab3c73410d126e119de12
|
||||
xLTZVf/translation: ce928c74255344905f5c3f02f44c991e
|
||||
xLTZVf/message: ce928c74255344905f5c3f02f44c991e
|
||||
xLTZVf/origin/0/0: 5f6c4a0c9be5098099167871ae1564af
|
||||
xLTZVf/translation: ce928c74255344905f5c3f02f44c991e
|
||||
9eF5oV/message: 4928884739ba559e6e4b960e80fe1452
|
||||
9eF5oV/origin/0/0: 4a1be3707d14c696a16f453d132fb248
|
||||
9eF5oV/translation: 4928884739ba559e6e4b960e80fe1452
|
||||
|
||||
@@ -28,6 +28,7 @@ const config = {
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
/** Enables hot reloading for local packages without a build step */
|
||||
transpilePackages: [
|
||||
"@kan/api",
|
||||
@@ -65,6 +66,8 @@ const config = {
|
||||
},
|
||||
},
|
||||
},
|
||||
serverExternalPackages: ["pino"],
|
||||
|
||||
experimental: {
|
||||
// instrumentationHook: true,
|
||||
swcPlugins: [["@lingui/swc-plugin", {}]],
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"@kan/api": "workspace:*",
|
||||
"@kan/auth": "workspace:*",
|
||||
"@kan/db": "workspace:^",
|
||||
"@kan/logger": "workspace:^",
|
||||
"@kan/shared": "workspace:^",
|
||||
"@lingui/babel-preset-react": "^2.9.2",
|
||||
"@lingui/conf": "^5.3.2",
|
||||
|
||||
@@ -145,7 +145,6 @@ export default function CommandPallette({
|
||||
value={result}
|
||||
className="cursor-pointer select-none px-4 py-3 data-[focus]:bg-light-200 hover:bg-light-200 focus:outline-none dark:data-[focus]:bg-dark-200 dark:hover:bg-dark-200"
|
||||
onClick={() => {
|
||||
console.log("clicked", url);
|
||||
void router.push(url);
|
||||
onClose();
|
||||
setQuery("");
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useTheme } from "next-themes";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { env } from "next-runtime-env";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
TbLayoutSidebarLeftCollapse,
|
||||
@@ -43,6 +45,7 @@ export default function Dashboard({
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { openModal } = useModal();
|
||||
const { availableWorkspaces, hasLoaded } = useWorkspace();
|
||||
const router = useRouter();
|
||||
|
||||
const { data: session, isPending: sessionLoading } = authClient.useSession();
|
||||
const { data: user, isLoading: userLoading } = api.user.getUser.useQuery(
|
||||
@@ -98,9 +101,13 @@ export default function Dashboard({
|
||||
|
||||
useEffect(() => {
|
||||
if (hasLoaded && availableWorkspaces.length === 0) {
|
||||
openModal("NEW_WORKSPACE", undefined, undefined, false);
|
||||
if (env("NEXT_PUBLIC_KAN_ENV") === "cloud") {
|
||||
router.push(`/onboarding/select-plan?returnUrl=${encodeURIComponent(window.location.pathname)}`);
|
||||
} else {
|
||||
openModal("NEW_WORKSPACE", undefined, undefined, false);
|
||||
}
|
||||
}
|
||||
}, [hasLoaded, availableWorkspaces.length, openModal]);
|
||||
}, [hasLoaded, availableWorkspaces.length, openModal, router]);
|
||||
|
||||
const isDarkMode = resolvedTheme === "dark";
|
||||
|
||||
|
||||
32
apps/web/src/components/FontSizeSelector.tsx
Normal file
32
apps/web/src/components/FontSizeSelector.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { HiOutlineAdjustmentsHorizontal } from "react-icons/hi2";
|
||||
|
||||
import { useFontSize, type FontSize } from "~/providers/font-size";
|
||||
|
||||
const fontSizeOptions: { value: FontSize; label: () => string }[] = [
|
||||
{ value: "small", label: () => t`Small` },
|
||||
{ value: "medium", label: () => t`Medium` },
|
||||
{ value: "large", label: () => t`Large` },
|
||||
];
|
||||
|
||||
export function FontSizeSelector() {
|
||||
const { fontSize, setFontSize } = useFontSize();
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<HiOutlineAdjustmentsHorizontal className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
|
||||
<select
|
||||
id="font-size-select"
|
||||
value={fontSize}
|
||||
onChange={(e) => setFontSize(e.target.value as FontSize)}
|
||||
className="block w-full max-w-[180px] rounded-lg border-0 bg-light-50 pl-10 text-sm shadow-sm ring-1 ring-inset ring-light-300 focus:ring-2 focus:ring-inset focus:ring-light-400 dark:bg-dark-50 dark:text-dark-1000 dark:ring-dark-300 dark:focus:ring-dark-500"
|
||||
>
|
||||
{fontSizeOptions.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label()}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
</button>
|
||||
)}
|
||||
{iconRight && type !== "password" && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 leading-[0]">
|
||||
{iconRight}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function LabelForm({
|
||||
labelPublicId: entityId,
|
||||
},
|
||||
{
|
||||
enabled: isEdit && !!entityId,
|
||||
enabled: !!isEdit && entityId.length >= 12,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -75,7 +75,7 @@ export function LabelForm({
|
||||
reset(newFormState);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -231,9 +231,7 @@ export function LabelForm({
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={updateLabel.isPending || createLabel.isPending}
|
||||
disabled={
|
||||
!watch("name")
|
||||
}
|
||||
disabled={!watch("name")}
|
||||
>
|
||||
{isEdit ? t`Update label` : t`Create label`}
|
||||
</Button>
|
||||
|
||||
@@ -127,7 +127,7 @@ export function NewWorkspaceForm() {
|
||||
body: JSON.stringify({
|
||||
slug: slug || undefined,
|
||||
workspacePublicId: values.publicId,
|
||||
cancelUrl: "/settings/workspace?upgrade=pro",
|
||||
cancelUrl: window.location.pathname + window.location.search,
|
||||
successUrl: "/boards",
|
||||
}),
|
||||
},
|
||||
|
||||
130
apps/web/src/components/PlainTextEditor.tsx
Normal file
130
apps/web/src/components/PlainTextEditor.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import { EditorContent, useEditor } from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface PlainTextEditorProps {
|
||||
content: string;
|
||||
onChange?: (value: string) => void;
|
||||
onBlur?: (value: string) => void;
|
||||
onEnter?: (value: string) => void;
|
||||
onEscape?: () => void;
|
||||
readOnly?: boolean;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function PlainTextEditor({
|
||||
content,
|
||||
onChange,
|
||||
onBlur,
|
||||
onEnter,
|
||||
onEscape,
|
||||
readOnly = false,
|
||||
placeholder,
|
||||
className,
|
||||
}: PlainTextEditorProps) {
|
||||
const onEnterRef = useRef(onEnter);
|
||||
const onEscapeRef = useRef(onEscape);
|
||||
const onBlurRef = useRef(onBlur);
|
||||
const onChangeRef = useRef(onChange);
|
||||
const contentRef = useRef(content);
|
||||
|
||||
useEffect(() => {
|
||||
onEnterRef.current = onEnter;
|
||||
}, [onEnter]);
|
||||
useEffect(() => {
|
||||
onEscapeRef.current = onEscape;
|
||||
}, [onEscape]);
|
||||
useEffect(() => {
|
||||
onBlurRef.current = onBlur;
|
||||
}, [onBlur]);
|
||||
useEffect(() => {
|
||||
onChangeRef.current = onChange;
|
||||
}, [onChange]);
|
||||
useEffect(() => {
|
||||
contentRef.current = content;
|
||||
}, [content]);
|
||||
|
||||
const editor = useEditor(
|
||||
{
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
bold: false,
|
||||
italic: false,
|
||||
strike: false,
|
||||
code: false,
|
||||
codeBlock: false,
|
||||
blockquote: false,
|
||||
heading: false,
|
||||
bulletList: false,
|
||||
orderedList: false,
|
||||
listItem: false,
|
||||
horizontalRule: false,
|
||||
hardBreak: false,
|
||||
}),
|
||||
Placeholder.configure({ placeholder }),
|
||||
],
|
||||
content,
|
||||
editable: !readOnly,
|
||||
onUpdate: ({ editor }) => onChangeRef.current?.(editor.getText()),
|
||||
onBlur: ({ editor }) => onBlurRef.current?.(editor.getText()),
|
||||
editorProps: {
|
||||
handleKeyDown: (view, event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
onEnterRef.current?.(view.state.doc.textContent.trim());
|
||||
return true;
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
// Reset to original content before calling the callback
|
||||
editor?.commands.setContent(contentRef.current, false);
|
||||
editor?.commands.blur();
|
||||
onEscapeRef.current?.();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
attributes: {
|
||||
class: "outline-none focus:outline-none focus-visible:ring-0",
|
||||
},
|
||||
},
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
if (content !== editor.getText()) {
|
||||
editor.commands.setContent(content, false);
|
||||
}
|
||||
}, [content, editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
editor.setEditable(!readOnly);
|
||||
}, [readOnly, editor]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<style jsx global>{`
|
||||
.plain-text-editor p.is-empty::before {
|
||||
content: attr(data-placeholder);
|
||||
float: left;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
color: var(--placeholder-color, #9ca3af);
|
||||
}
|
||||
.plain-text-editor .tiptap p {
|
||||
margin: 0 !important;
|
||||
}
|
||||
`}</style>
|
||||
<EditorContent
|
||||
editor={editor}
|
||||
className={twMerge("plain-text-editor", className)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -7,15 +7,17 @@ const Toggle = ({
|
||||
label,
|
||||
disabled,
|
||||
showLabel = true,
|
||||
labelPosition = "before",
|
||||
}: {
|
||||
isChecked: boolean;
|
||||
onChange: () => void;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
showLabel?: boolean;
|
||||
labelPosition?: "before" | "after";
|
||||
}) => (
|
||||
<div className="mr-4 flex items-center justify-end">
|
||||
{showLabel && (
|
||||
<div className="flex items-center">
|
||||
{showLabel && labelPosition === "before" && (
|
||||
<span className="mr-2 text-xs text-light-900 dark:text-dark-900">
|
||||
{label}
|
||||
</span>
|
||||
@@ -38,6 +40,11 @@ const Toggle = ({
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
{showLabel && labelPosition === "after" && (
|
||||
<span className="ml-2 text-xs text-light-900 dark:text-dark-900">
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Button, Menu, Transition } from "@headlessui/react";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { env } from "next-runtime-env";
|
||||
import { Fragment, useState } from "react";
|
||||
import { HiCheck, HiMagnifyingGlass } from "react-icons/hi2";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
@@ -18,6 +20,7 @@ export default function WorkspaceMenu({
|
||||
const { workspace, isLoading, availableWorkspaces, switchWorkspace } =
|
||||
useWorkspace();
|
||||
const { openModal } = useModal();
|
||||
const router = useRouter();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { tooltipContent: commandPaletteShortcutTooltipContent } =
|
||||
@@ -147,7 +150,11 @@ export default function WorkspaceMenu({
|
||||
<div className="border-t-[1px] border-light-600 p-1 dark:border-dark-500">
|
||||
<Menu.Item>
|
||||
<button
|
||||
onClick={() => openModal("NEW_WORKSPACE")}
|
||||
onClick={() =>
|
||||
env("NEXT_PUBLIC_KAN_ENV") === "cloud"
|
||||
? router.push(`/onboarding/select-plan?returnUrl=${encodeURIComponent(window.location.pathname)}`)
|
||||
: openModal("NEW_WORKSPACE")
|
||||
}
|
||||
className="flex w-full items-center justify-between rounded-[5px] px-3 py-2 text-left text-xs text-neutral-900 hover:bg-light-200 dark:text-dark-1000 dark:hover:bg-dark-400"
|
||||
>
|
||||
{t`Create workspace`}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} nicht gefunden"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Aktivitätsprotokolle"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Label hinzufügen / bearbeiten",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Details hinzufügen..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Label hinzufügen"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Mitglied hinzufügen"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Karte"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Karte dupliziert",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1293,18 +1309,25 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 77],
|
||||
["src/views/settings/AccountSettings.tsx", 87],
|
||||
["src/views/settings/AccountSettings.tsx", 88],
|
||||
["src/views/settings/AccountSettings.tsx", 98],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 109],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 178]
|
||||
],
|
||||
"translation": "Passwort ändern"
|
||||
},
|
||||
"fzGyWs": {
|
||||
"message": "Change the application font size.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 63]],
|
||||
"translation": "Schriftgröße der Anwendung ändern."
|
||||
},
|
||||
"yD3ZSw": {
|
||||
"message": "Change your language preferences.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 52]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 53]],
|
||||
"translation": "Ändern Sie Ihre Spracheinstellungen."
|
||||
},
|
||||
"gZxH/p": {
|
||||
@@ -1654,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Karten-Link kopieren"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Checklisten kopieren",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Labels kopieren",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Link zur Karte kopieren",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Mitglieder kopieren",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1715,7 +1772,7 @@
|
||||
"message": "Create label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Label erstellen"
|
||||
},
|
||||
"XTKtey": {
|
||||
@@ -1750,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Neues Label erstellen"
|
||||
},
|
||||
@@ -1759,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Neue Liste erstellen"
|
||||
},
|
||||
@@ -1961,8 +2018,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 59],
|
||||
["src/views/settings/AccountSettings.tsx", 69],
|
||||
["src/views/settings/AccountSettings.tsx", 70],
|
||||
["src/views/settings/AccountSettings.tsx", 80],
|
||||
["src/views/settings/components/DeleteAccountConfirmation.tsx", 96]
|
||||
],
|
||||
"translation": "Account löschen"
|
||||
@@ -1978,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Karte löschen"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2086,7 +2146,7 @@
|
||||
"message": "Display name",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 35]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 36]],
|
||||
"translation": "Anzeigename"
|
||||
},
|
||||
"MlyjJu": {
|
||||
@@ -2164,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2219,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Morgen fällig"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Duplizieren",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Karte duplizieren",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Wird dupliziert…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2304,7 +2395,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/members/components/InviteMemberForm.tsx", 272],
|
||||
["src/views/settings/AccountSettings.tsx", 42]
|
||||
["src/views/settings/AccountSettings.tsx", 43]
|
||||
],
|
||||
"translation": "E-Mail"
|
||||
},
|
||||
@@ -2331,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "E-Mail-Sichtbarkeit"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Ende der Liste",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2768,6 +2868,13 @@
|
||||
"origin": [["src/views/home/components/Faqs.tsx", 150]],
|
||||
"translation": "Finden Sie Antworten auf häufig gestellte Fragen zu Kan. Können Sie nicht finden, wonach Sie suchen? <0>Kontaktieren Sie uns</0> gerne."
|
||||
},
|
||||
"q3il6U": {
|
||||
"message": "Font size",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 60]],
|
||||
"translation": "Schriftgröße"
|
||||
},
|
||||
"+3TYXa": {
|
||||
"message": "For long-term sustainability, we recognise all good open source projects need a source of revenue. Ours comes from the paid cloud offering for workspaces with multiple users and for custom workspace slugs. There's no obligation to use it, and you can self-host the software on your own infrastructure free of charge.",
|
||||
"placeholders": {},
|
||||
@@ -2880,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Legen Sie los, indem Sie eine neue Liste erstellen"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3187,7 +3294,7 @@
|
||||
"message": "in",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 168]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 167]],
|
||||
"translation": "in"
|
||||
},
|
||||
"WbTDwg": {
|
||||
@@ -3300,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Mitglied einladen"
|
||||
@@ -3372,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3398,9 +3506,16 @@
|
||||
"message": "Language",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 49]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 50]],
|
||||
"translation": "Sprache"
|
||||
},
|
||||
"k7rCa/": {
|
||||
"message": "Large",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 9]],
|
||||
"translation": "Groß"
|
||||
},
|
||||
"8Is1ih": {
|
||||
"message": "Launch offer",
|
||||
"placeholders": {},
|
||||
@@ -3476,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3486,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Liste"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3575,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Vorlage erstellen"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Mitglieder verwalten",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3591,6 +3720,13 @@
|
||||
"origin": [["src/views/card/components/ActivityList.tsx", 258]],
|
||||
"translation": "hat Checklistenelement <0>{0}</0> als unvollständig markiert"
|
||||
},
|
||||
"agPptk": {
|
||||
"message": "Medium",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 8]],
|
||||
"translation": "Mittel"
|
||||
},
|
||||
"W/Elkg": {
|
||||
"message": "Medium Priority",
|
||||
"placeholders": {},
|
||||
@@ -3642,11 +3778,13 @@
|
||||
"translation": "Erwähnungen"
|
||||
},
|
||||
"hty0d5": {
|
||||
"translation": "Montag",
|
||||
"message": "Monday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]
|
||||
],
|
||||
"translation": "Montag"
|
||||
},
|
||||
"+8Nek/": {
|
||||
"message": "Monthly",
|
||||
@@ -3665,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "Monatliche Abrechnung"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "In andere Liste verschieben",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "In Liste verschieben",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3776,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Neue Liste"
|
||||
},
|
||||
@@ -3902,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Keine Listen"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Es wurden noch keine Listen erstellt"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -3925,7 +4077,7 @@
|
||||
"debouncedQuery": ["debouncedQuery"]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 184]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 183]],
|
||||
"translation": "Keine Ergebnisse für \"{debouncedQuery}\" gefunden."
|
||||
},
|
||||
"Q9pQaX": {
|
||||
@@ -3976,7 +4128,7 @@
|
||||
"message": "Once you delete your account, there is no going back. This action cannot be undone.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 62]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 73]],
|
||||
"translation": "Sobald Sie Ihr Konto löschen, gibt es kein Zurück mehr. Diese Aktion kann nicht rückgängig gemacht werden."
|
||||
},
|
||||
"dmKdk0": {
|
||||
@@ -4301,13 +4453,13 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -4357,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Bitte versuchen Sie es erneut."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Position in Liste (0 = erste, leer lassen für Ende)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4438,7 +4602,7 @@
|
||||
"message": "Profile picture",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 29]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 30]],
|
||||
"translation": "Profilbild"
|
||||
},
|
||||
"7d1a0d": {
|
||||
@@ -4707,11 +4871,13 @@
|
||||
"translation": "SAML SSO"
|
||||
},
|
||||
"+5kO8P": {
|
||||
"translation": "Samstag",
|
||||
"message": "Saturday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]
|
||||
],
|
||||
"translation": "Samstag"
|
||||
},
|
||||
"tfDRzk": {
|
||||
"message": "Save",
|
||||
@@ -4782,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Sehen Sie, was unsere Nutzer über Kan sagen"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Liste auswählen",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4852,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Fälligkeitsdatum festlegen"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -4876,7 +5055,7 @@
|
||||
"message": "Settings | Account",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 25]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 26]],
|
||||
"translation": "Einstellungen | Konto"
|
||||
},
|
||||
"iy1wb+": {
|
||||
@@ -5006,6 +5185,13 @@
|
||||
],
|
||||
"translation": "SLA"
|
||||
},
|
||||
"4Revpc": {
|
||||
"message": "Small",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 7]],
|
||||
"translation": "Klein"
|
||||
},
|
||||
"++rVAm": {
|
||||
"message": "Social Media",
|
||||
"placeholders": {},
|
||||
@@ -5078,11 +5264,13 @@
|
||||
"translation": "Erfolg"
|
||||
},
|
||||
"DBC3t5": {
|
||||
"translation": "Sonntag",
|
||||
"message": "Sunday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]
|
||||
],
|
||||
"translation": "Sonntag"
|
||||
},
|
||||
"uZg2+w": {
|
||||
"message": "Supercharge your workspace for just $29/month. Here's what you'll get:",
|
||||
@@ -5143,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Vorlage"
|
||||
},
|
||||
@@ -5238,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Das Board wurde wiederhergestellt."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "Die Karte wurde dupliziert.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5420,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Titel"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Titel (optional)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5522,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5593,7 +5798,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Checklistenelement konnte nicht gelöscht werden"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5603,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Kommentar konnte nicht gelöscht werden"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Karte konnte nicht dupliziert werden",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Karte konnte nicht verschoben werden",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5661,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Karte konnte nicht aktualisiert werden"
|
||||
@@ -5677,7 +5896,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Checklisten-Element konnte nicht aktualisiert werden"
|
||||
},
|
||||
"fYVH36": {
|
||||
@@ -5706,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Liste konnte nicht aktualisiert werden"
|
||||
@@ -5876,7 +6095,7 @@
|
||||
"message": "Update label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Label aktualisieren"
|
||||
},
|
||||
"L5ZPjz": {
|
||||
@@ -6168,11 +6387,11 @@
|
||||
"translation": "Webhooks"
|
||||
},
|
||||
"xLTZVf": {
|
||||
"translation": "Wochenbeginn",
|
||||
"message": "Week start day",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]]
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]],
|
||||
"translation": "Wochenbeginn"
|
||||
},
|
||||
"9eF5oV": {
|
||||
"message": "Welcome back",
|
||||
@@ -6215,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6394,7 +6613,7 @@
|
||||
"message": "You are about to change your password.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 80]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 91]],
|
||||
"translation": "Sie sind dabei, Ihr Passwort zu ändern."
|
||||
},
|
||||
"aelko+": {
|
||||
@@ -6426,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -123,7 +123,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
440
|
||||
556
|
||||
]
|
||||
],
|
||||
"translation": "{0} not found"
|
||||
@@ -400,6 +400,18 @@
|
||||
],
|
||||
"translation": "Activity logs"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Add / edit label",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
50
|
||||
]
|
||||
]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -471,7 +483,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/card/components/ChecklistItemRow.tsx",
|
||||
200
|
||||
169
|
||||
]
|
||||
],
|
||||
"translation": "Add details..."
|
||||
@@ -483,11 +495,11 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/card/components/LabelSelector.tsx",
|
||||
113
|
||||
114
|
||||
],
|
||||
[
|
||||
"src/views/card/components/LabelSelector.tsx",
|
||||
118
|
||||
119
|
||||
]
|
||||
],
|
||||
"translation": "Add label"
|
||||
@@ -499,7 +511,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/card/components/MemberSelector.tsx",
|
||||
134
|
||||
135
|
||||
],
|
||||
[
|
||||
"src/views/members/components/InviteMemberForm.tsx",
|
||||
@@ -1409,7 +1421,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
413
|
||||
529
|
||||
],
|
||||
[
|
||||
"src/views/card/index.tsx",
|
||||
@@ -2051,6 +2063,10 @@
|
||||
"src/components/YouTubeEmbed/EditYouTubeModal.tsx",
|
||||
176
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
267
|
||||
],
|
||||
[
|
||||
"src/views/board/components/DeleteBoardConfirmation.tsx",
|
||||
44
|
||||
@@ -2118,6 +2134,18 @@
|
||||
],
|
||||
"translation": "Card"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Card duplicated",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
67
|
||||
]
|
||||
]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -2151,6 +2179,10 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
307
|
||||
],
|
||||
[
|
||||
"src/views/card/components/Dropdown.tsx",
|
||||
44
|
||||
@@ -2169,11 +2201,11 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
77
|
||||
88
|
||||
],
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
87
|
||||
98
|
||||
],
|
||||
[
|
||||
"src/views/settings/components/ChangePasswordConfirmation.tsx",
|
||||
@@ -2186,6 +2218,18 @@
|
||||
],
|
||||
"translation": "Change Password"
|
||||
},
|
||||
"fzGyWs": {
|
||||
"message": "Change the application font size.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
63
|
||||
]
|
||||
],
|
||||
"translation": "Change the application font size."
|
||||
},
|
||||
"yD3ZSw": {
|
||||
"message": "Change your language preferences.",
|
||||
"placeholders": {},
|
||||
@@ -2193,7 +2237,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
52
|
||||
53
|
||||
]
|
||||
],
|
||||
"translation": "Change your language preferences."
|
||||
@@ -2770,6 +2814,54 @@
|
||||
],
|
||||
"translation": "Copy card link"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Copy checklists",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
257
|
||||
]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Copy labels",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
221
|
||||
]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Copy link to card",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
62
|
||||
]
|
||||
]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Copy members",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
239
|
||||
]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -2873,7 +2965,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/components/LabelForm.tsx",
|
||||
238
|
||||
236
|
||||
]
|
||||
],
|
||||
"translation": "Create label"
|
||||
@@ -2933,7 +3025,7 @@
|
||||
],
|
||||
[
|
||||
"src/views/card/components/LabelSelector.tsx",
|
||||
100
|
||||
101
|
||||
]
|
||||
],
|
||||
"translation": "Create new label"
|
||||
@@ -2945,11 +3037,11 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
76
|
||||
91
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
554
|
||||
670
|
||||
]
|
||||
],
|
||||
"translation": "Create new list"
|
||||
@@ -3297,11 +3389,11 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
59
|
||||
70
|
||||
],
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
69
|
||||
80
|
||||
],
|
||||
[
|
||||
"src/views/settings/components/DeleteAccountConfirmation.tsx",
|
||||
@@ -3327,6 +3419,10 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
74
|
||||
],
|
||||
[
|
||||
"src/views/card/components/Dropdown.tsx",
|
||||
76
|
||||
@@ -3513,7 +3609,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
35
|
||||
36
|
||||
]
|
||||
],
|
||||
"translation": "Display name"
|
||||
@@ -3651,6 +3747,18 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDueDateModal.tsx",
|
||||
36
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextLabelsModal.tsx",
|
||||
48
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextMembersModal.tsx",
|
||||
68
|
||||
],
|
||||
[
|
||||
"src/views/boards/components/TemplateBoards.tsx",
|
||||
17
|
||||
@@ -3758,6 +3866,46 @@
|
||||
],
|
||||
"translation": "Due tomorrow"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Duplicate",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
279
|
||||
]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Duplicate card",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
111
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
68
|
||||
]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Duplicating…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
279
|
||||
]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -3901,7 +4049,7 @@
|
||||
],
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
42
|
||||
43
|
||||
]
|
||||
],
|
||||
"translation": "Email"
|
||||
@@ -3942,6 +4090,18 @@
|
||||
],
|
||||
"translation": "Email visibility"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "End of list",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
200
|
||||
]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -4654,6 +4814,18 @@
|
||||
],
|
||||
"translation": "Find answers to common questions about Kan. Can't find what you're looking for? Feel free to <0>contact us</0>."
|
||||
},
|
||||
"q3il6U": {
|
||||
"message": "Font size",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
60
|
||||
]
|
||||
],
|
||||
"translation": "Font size"
|
||||
},
|
||||
"+3TYXa": {
|
||||
"message": "For long-term sustainability, we recognise all good open source projects need a source of revenue. Ours comes from the paid cloud offering for workspaces with multiple users and for custom workspace slugs. There's no obligation to use it, and you can self-host the software on your own infrastructure free of charge.",
|
||||
"placeholders": {},
|
||||
@@ -4857,7 +5029,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
539
|
||||
655
|
||||
]
|
||||
],
|
||||
"translation": "Get started by creating a new list"
|
||||
@@ -5365,7 +5537,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/components/CommandPallette.tsx",
|
||||
168
|
||||
167
|
||||
]
|
||||
],
|
||||
"translation": "in"
|
||||
@@ -5557,7 +5729,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/card/components/MemberSelector.tsx",
|
||||
114
|
||||
115
|
||||
],
|
||||
[
|
||||
"src/views/members/components/InviteMemberForm.tsx",
|
||||
@@ -5675,6 +5847,10 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextLabelsModal.tsx",
|
||||
31
|
||||
],
|
||||
[
|
||||
"src/views/board/components/Filters.tsx",
|
||||
155
|
||||
@@ -5721,11 +5897,23 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
49
|
||||
50
|
||||
]
|
||||
],
|
||||
"translation": "Language"
|
||||
},
|
||||
"k7rCa/": {
|
||||
"message": "Large",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/components/FontSizeSelector.tsx",
|
||||
9
|
||||
]
|
||||
],
|
||||
"translation": "Large"
|
||||
},
|
||||
"8Is1ih": {
|
||||
"message": "Launch offer",
|
||||
"placeholders": {},
|
||||
@@ -5851,6 +6039,10 @@
|
||||
"src/views/board/components/UpdateBoardSlugButton.tsx",
|
||||
80
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
305
|
||||
],
|
||||
[
|
||||
"src/views/card/components/Dropdown.tsx",
|
||||
42
|
||||
@@ -5871,6 +6063,10 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
117
|
||||
],
|
||||
[
|
||||
"src/views/card/index.tsx",
|
||||
124
|
||||
@@ -6022,6 +6218,22 @@
|
||||
],
|
||||
"translation": "Make template"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Manage members",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMembersModal.tsx",
|
||||
51
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
38
|
||||
]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -6050,6 +6262,18 @@
|
||||
],
|
||||
"translation": "marked checklist item <0>{0}</0> as incomplete"
|
||||
},
|
||||
"agPptk": {
|
||||
"message": "Medium",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/components/FontSizeSelector.tsx",
|
||||
8
|
||||
]
|
||||
],
|
||||
"translation": "Medium"
|
||||
},
|
||||
"W/Elkg": {
|
||||
"message": "Medium Priority",
|
||||
"placeholders": {},
|
||||
@@ -6143,7 +6367,6 @@
|
||||
"translation": "Mentions"
|
||||
},
|
||||
"hty0d5": {
|
||||
"translation": "Monday",
|
||||
"message": "Monday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
@@ -6152,7 +6375,8 @@
|
||||
"src/views/settings/components/UpdateWeekStartDayForm.tsx",
|
||||
53
|
||||
]
|
||||
]
|
||||
],
|
||||
"translation": "Monday"
|
||||
},
|
||||
"+8Nek/": {
|
||||
"message": "Monthly",
|
||||
@@ -6182,6 +6406,30 @@
|
||||
],
|
||||
"translation": "monthly billing"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Move to another list",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
44
|
||||
]
|
||||
]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Move to list",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMoveListModal.tsx",
|
||||
70
|
||||
]
|
||||
]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -6380,7 +6628,7 @@
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
503
|
||||
619
|
||||
]
|
||||
],
|
||||
"translation": "New list"
|
||||
@@ -6588,7 +6836,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
535
|
||||
651
|
||||
]
|
||||
],
|
||||
"translation": "No lists"
|
||||
@@ -6600,7 +6848,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
540
|
||||
656
|
||||
]
|
||||
],
|
||||
"translation": "No lists have been created yet"
|
||||
@@ -6628,7 +6876,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/components/CommandPallette.tsx",
|
||||
184
|
||||
183
|
||||
]
|
||||
],
|
||||
"translation": "No results found for \"{debouncedQuery}\"."
|
||||
@@ -6712,7 +6960,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
62
|
||||
73
|
||||
]
|
||||
],
|
||||
"translation": "Once you delete your account, there is no going back. This action cannot be undone."
|
||||
@@ -7264,11 +7512,11 @@
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
194
|
||||
217
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
250
|
||||
273
|
||||
],
|
||||
[
|
||||
"src/views/boards/components/ImportBoardsForm.tsx",
|
||||
@@ -7284,11 +7532,11 @@
|
||||
],
|
||||
[
|
||||
"src/views/card/components/ChecklistItemRow.tsx",
|
||||
69
|
||||
67
|
||||
],
|
||||
[
|
||||
"src/views/card/components/ChecklistItemRow.tsx",
|
||||
97
|
||||
95
|
||||
],
|
||||
[
|
||||
"src/views/card/components/ChecklistNameInput.tsx",
|
||||
@@ -7438,6 +7686,18 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
76
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextMoveListModal.tsx",
|
||||
42
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
314
|
||||
],
|
||||
[
|
||||
"src/views/card/components/Dropdown.tsx",
|
||||
51
|
||||
@@ -7453,6 +7713,18 @@
|
||||
],
|
||||
"translation": "Please try again."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Position in list (0 = first, leave empty for end)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
193
|
||||
]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -7592,7 +7864,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
29
|
||||
30
|
||||
]
|
||||
],
|
||||
"translation": "Profile picture"
|
||||
@@ -8045,7 +8317,6 @@
|
||||
"translation": "SAML SSO"
|
||||
},
|
||||
"+5kO8P": {
|
||||
"translation": "Saturday",
|
||||
"message": "Saturday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
@@ -8054,7 +8325,8 @@
|
||||
"src/views/settings/components/UpdateWeekStartDayForm.tsx",
|
||||
54
|
||||
]
|
||||
]
|
||||
],
|
||||
"translation": "Saturday"
|
||||
},
|
||||
"tfDRzk": {
|
||||
"message": "Save",
|
||||
@@ -8172,6 +8444,18 @@
|
||||
],
|
||||
"translation": "See what our users are saying about Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Select a list",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
131
|
||||
]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -8289,6 +8573,14 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDueDateModal.tsx",
|
||||
20
|
||||
],
|
||||
[
|
||||
"src/views/board/components/CardContextMenu.tsx",
|
||||
56
|
||||
],
|
||||
[
|
||||
"src/views/card/components/DueDateSelector.tsx",
|
||||
121
|
||||
@@ -8331,7 +8623,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
25
|
||||
26
|
||||
]
|
||||
],
|
||||
"translation": "Settings | Account"
|
||||
@@ -8548,6 +8840,18 @@
|
||||
],
|
||||
"translation": "SLA"
|
||||
},
|
||||
"4Revpc": {
|
||||
"message": "Small",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/components/FontSizeSelector.tsx",
|
||||
7
|
||||
]
|
||||
],
|
||||
"translation": "Small"
|
||||
},
|
||||
"++rVAm": {
|
||||
"message": "Social Media",
|
||||
"placeholders": {},
|
||||
@@ -8665,7 +8969,6 @@
|
||||
"translation": "Success"
|
||||
},
|
||||
"DBC3t5": {
|
||||
"translation": "Sunday",
|
||||
"message": "Sunday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
@@ -8674,7 +8977,8 @@
|
||||
"src/views/settings/components/UpdateWeekStartDayForm.tsx",
|
||||
52
|
||||
]
|
||||
]
|
||||
],
|
||||
"translation": "Sunday"
|
||||
},
|
||||
"uZg2+w": {
|
||||
"message": "Supercharge your workspace for just $29/month. Here's what you'll get:",
|
||||
@@ -8771,11 +9075,11 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
413
|
||||
529
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
449
|
||||
565
|
||||
]
|
||||
],
|
||||
"translation": "Template"
|
||||
@@ -8932,6 +9236,18 @@
|
||||
],
|
||||
"translation": "The board has been unarchived."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "The card has been duplicated.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
69
|
||||
]
|
||||
]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -9220,6 +9536,18 @@
|
||||
],
|
||||
"translation": "Title"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Title (optional)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
180
|
||||
]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -9389,6 +9717,10 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
312
|
||||
],
|
||||
[
|
||||
"src/views/card/components/Dropdown.tsx",
|
||||
49
|
||||
@@ -9511,7 +9843,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/card/components/ChecklistItemRow.tsx",
|
||||
96
|
||||
94
|
||||
]
|
||||
],
|
||||
"translation": "Unable to delete checklist item"
|
||||
@@ -9528,6 +9860,30 @@
|
||||
],
|
||||
"translation": "Unable to delete comment"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Unable to duplicate card",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextDuplicateModal.tsx",
|
||||
75
|
||||
]
|
||||
]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Unable to move card",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/components/CardContextMoveListModal.tsx",
|
||||
41
|
||||
]
|
||||
]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -9619,7 +9975,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
249
|
||||
272
|
||||
],
|
||||
[
|
||||
"src/views/card/index.tsx",
|
||||
@@ -9647,7 +10003,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/card/components/ChecklistItemRow.tsx",
|
||||
68
|
||||
66
|
||||
]
|
||||
],
|
||||
"translation": "Unable to update checklist item"
|
||||
@@ -9695,7 +10051,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
193
|
||||
216
|
||||
],
|
||||
[
|
||||
"src/views/card/components/ListSelector.tsx",
|
||||
@@ -10007,7 +10363,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/components/LabelForm.tsx",
|
||||
238
|
||||
236
|
||||
]
|
||||
],
|
||||
"translation": "Update label"
|
||||
@@ -10497,7 +10853,6 @@
|
||||
"translation": "Webhooks"
|
||||
},
|
||||
"xLTZVf": {
|
||||
"translation": "Week start day",
|
||||
"message": "Week start day",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
@@ -10506,7 +10861,8 @@
|
||||
"src/views/settings/WorkspaceSettings.tsx",
|
||||
91
|
||||
]
|
||||
]
|
||||
],
|
||||
"translation": "Week start day"
|
||||
},
|
||||
"9eF5oV": {
|
||||
"message": "Welcome back",
|
||||
@@ -10579,7 +10935,7 @@
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
413
|
||||
529
|
||||
],
|
||||
[
|
||||
"src/views/boards/index.tsx",
|
||||
@@ -10867,7 +11223,7 @@
|
||||
"origin": [
|
||||
[
|
||||
"src/views/settings/AccountSettings.tsx",
|
||||
80
|
||||
91
|
||||
]
|
||||
],
|
||||
"translation": "You are about to change your password."
|
||||
@@ -10927,11 +11283,11 @@
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
487
|
||||
603
|
||||
],
|
||||
[
|
||||
"src/views/board/index.tsx",
|
||||
545
|
||||
661
|
||||
],
|
||||
[
|
||||
"src/views/boards/components/BoardsList.tsx",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} no encontrado"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Registros de actividad"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Añadir / editar etiqueta",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Añadir detalles..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Añadir etiqueta"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Añadir miembro"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Tarjeta"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Tarjeta duplicada",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1293,18 +1309,25 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 77],
|
||||
["src/views/settings/AccountSettings.tsx", 87],
|
||||
["src/views/settings/AccountSettings.tsx", 88],
|
||||
["src/views/settings/AccountSettings.tsx", 98],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 109],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 178]
|
||||
],
|
||||
"translation": "Cambiar contraseña"
|
||||
},
|
||||
"fzGyWs": {
|
||||
"message": "Change the application font size.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 63]],
|
||||
"translation": "Cambia el tamaño de fuente de la aplicación."
|
||||
},
|
||||
"yD3ZSw": {
|
||||
"message": "Change your language preferences.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 52]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 53]],
|
||||
"translation": "Cambia tus preferencias de idioma."
|
||||
},
|
||||
"gZxH/p": {
|
||||
@@ -1654,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Copiar enlace de la tarjeta"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Copiar listas de verificación",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Copiar etiquetas",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Copiar enlace a la tarjeta",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Copiar miembros",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1715,7 +1772,7 @@
|
||||
"message": "Create label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Crear etiqueta"
|
||||
},
|
||||
"XTKtey": {
|
||||
@@ -1750,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Crear nueva etiqueta"
|
||||
},
|
||||
@@ -1759,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Crear nueva lista"
|
||||
},
|
||||
@@ -1961,8 +2018,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 59],
|
||||
["src/views/settings/AccountSettings.tsx", 69],
|
||||
["src/views/settings/AccountSettings.tsx", 70],
|
||||
["src/views/settings/AccountSettings.tsx", 80],
|
||||
["src/views/settings/components/DeleteAccountConfirmation.tsx", 96]
|
||||
],
|
||||
"translation": "Eliminar cuenta"
|
||||
@@ -1978,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Eliminar tarjeta"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2086,7 +2146,7 @@
|
||||
"message": "Display name",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 35]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 36]],
|
||||
"translation": "Nombre para mostrar"
|
||||
},
|
||||
"MlyjJu": {
|
||||
@@ -2164,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2219,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Vence mañana"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Duplicar",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Duplicar tarjeta",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Duplicando…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2304,7 +2395,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/members/components/InviteMemberForm.tsx", 272],
|
||||
["src/views/settings/AccountSettings.tsx", 42]
|
||||
["src/views/settings/AccountSettings.tsx", 43]
|
||||
],
|
||||
"translation": "Email"
|
||||
},
|
||||
@@ -2331,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "Visibilidad del email"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Fin de la lista",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2768,6 +2868,13 @@
|
||||
"origin": [["src/views/home/components/Faqs.tsx", 150]],
|
||||
"translation": "Encuentra respuestas a preguntas frecuentes sobre Kan. ¿No encuentras lo que buscas? No dudes en <0>contactarnos</0>."
|
||||
},
|
||||
"q3il6U": {
|
||||
"message": "Font size",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 60]],
|
||||
"translation": "Tamaño de fuente"
|
||||
},
|
||||
"+3TYXa": {
|
||||
"message": "For long-term sustainability, we recognise all good open source projects need a source of revenue. Ours comes from the paid cloud offering for workspaces with multiple users and for custom workspace slugs. There's no obligation to use it, and you can self-host the software on your own infrastructure free of charge.",
|
||||
"placeholders": {},
|
||||
@@ -2880,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Comienza creando una nueva lista"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3187,7 +3294,7 @@
|
||||
"message": "in",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 168]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 167]],
|
||||
"translation": "en"
|
||||
},
|
||||
"WbTDwg": {
|
||||
@@ -3300,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Invitar miembro"
|
||||
@@ -3372,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3398,9 +3506,16 @@
|
||||
"message": "Language",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 49]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 50]],
|
||||
"translation": "Idioma"
|
||||
},
|
||||
"k7rCa/": {
|
||||
"message": "Large",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 9]],
|
||||
"translation": "Grande"
|
||||
},
|
||||
"8Is1ih": {
|
||||
"message": "Launch offer",
|
||||
"placeholders": {},
|
||||
@@ -3476,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3486,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Lista"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3575,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Crear plantilla"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Gestionar miembros",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3591,6 +3720,13 @@
|
||||
"origin": [["src/views/card/components/ActivityList.tsx", 258]],
|
||||
"translation": "marcó el elemento de la lista de verificación <0>{0}</0> como incompleto"
|
||||
},
|
||||
"agPptk": {
|
||||
"message": "Medium",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 8]],
|
||||
"translation": "Mediano"
|
||||
},
|
||||
"W/Elkg": {
|
||||
"message": "Medium Priority",
|
||||
"placeholders": {},
|
||||
@@ -3642,11 +3778,13 @@
|
||||
"translation": "Menciones"
|
||||
},
|
||||
"hty0d5": {
|
||||
"translation": "Lunes",
|
||||
"message": "Monday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]
|
||||
],
|
||||
"translation": "Lunes"
|
||||
},
|
||||
"+8Nek/": {
|
||||
"message": "Monthly",
|
||||
@@ -3665,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "facturación mensual"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Mover a otra lista",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Mover a lista",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3776,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Nueva lista"
|
||||
},
|
||||
@@ -3902,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Sin listas"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Aún no se han creado listas"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -3925,7 +4077,7 @@
|
||||
"debouncedQuery": ["debouncedQuery"]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 184]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 183]],
|
||||
"translation": "No se encontraron resultados para \"{debouncedQuery}\"."
|
||||
},
|
||||
"Q9pQaX": {
|
||||
@@ -3976,7 +4128,7 @@
|
||||
"message": "Once you delete your account, there is no going back. This action cannot be undone.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 62]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 73]],
|
||||
"translation": "Una vez que elimines tu cuenta, no hay vuelta atrás. Esta acción no se puede deshacer."
|
||||
},
|
||||
"dmKdk0": {
|
||||
@@ -4301,13 +4453,13 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -4357,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Por favor, inténtalo de nuevo."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Posición en la lista (0 = primero, dejar vacío para el final)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4438,7 +4602,7 @@
|
||||
"message": "Profile picture",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 29]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 30]],
|
||||
"translation": "Foto de perfil"
|
||||
},
|
||||
"7d1a0d": {
|
||||
@@ -4707,11 +4871,13 @@
|
||||
"translation": "SAML SSO"
|
||||
},
|
||||
"+5kO8P": {
|
||||
"translation": "Sábado",
|
||||
"message": "Saturday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]
|
||||
],
|
||||
"translation": "Sábado"
|
||||
},
|
||||
"tfDRzk": {
|
||||
"message": "Save",
|
||||
@@ -4782,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Mira lo que nuestros usuarios dicen sobre Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Seleccionar una lista",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4852,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Establecer fecha de vencimiento"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -4876,7 +5055,7 @@
|
||||
"message": "Settings | Account",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 25]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 26]],
|
||||
"translation": "Configuración | Cuenta"
|
||||
},
|
||||
"iy1wb+": {
|
||||
@@ -5006,6 +5185,13 @@
|
||||
],
|
||||
"translation": "SLA"
|
||||
},
|
||||
"4Revpc": {
|
||||
"message": "Small",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 7]],
|
||||
"translation": "Pequeño"
|
||||
},
|
||||
"++rVAm": {
|
||||
"message": "Social Media",
|
||||
"placeholders": {},
|
||||
@@ -5078,11 +5264,13 @@
|
||||
"translation": "Éxito"
|
||||
},
|
||||
"DBC3t5": {
|
||||
"translation": "Domingo",
|
||||
"message": "Sunday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]
|
||||
],
|
||||
"translation": "Domingo"
|
||||
},
|
||||
"uZg2+w": {
|
||||
"message": "Supercharge your workspace for just $29/month. Here's what you'll get:",
|
||||
@@ -5143,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Plantilla"
|
||||
},
|
||||
@@ -5238,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "El tablero ha sido desarchivado."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "La tarjeta ha sido duplicada.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5420,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Título"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Título (opcional)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5522,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5593,7 +5798,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "No se pudo eliminar el elemento de la lista de verificación"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5603,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "No se pudo eliminar el comentario"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "No se puede duplicar la tarjeta",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "No se puede mover la tarjeta",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5661,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "No se pudo actualizar la tarjeta"
|
||||
@@ -5677,7 +5896,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "No se pudo actualizar el elemento de la lista de verificación"
|
||||
},
|
||||
"fYVH36": {
|
||||
@@ -5706,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "No se pudo actualizar la lista"
|
||||
@@ -5876,7 +6095,7 @@
|
||||
"message": "Update label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Actualizar etiqueta"
|
||||
},
|
||||
"L5ZPjz": {
|
||||
@@ -6168,11 +6387,11 @@
|
||||
"translation": "Webhooks"
|
||||
},
|
||||
"xLTZVf": {
|
||||
"translation": "Día de inicio de semana",
|
||||
"message": "Week start day",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]]
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]],
|
||||
"translation": "Día de inicio de semana"
|
||||
},
|
||||
"9eF5oV": {
|
||||
"message": "Welcome back",
|
||||
@@ -6215,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6394,7 +6613,7 @@
|
||||
"message": "You are about to change your password.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 80]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 91]],
|
||||
"translation": "Estás a punto de cambiar tu contraseña."
|
||||
},
|
||||
"aelko+": {
|
||||
@@ -6426,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} introuvable"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Journaux d'activité"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Ajouter / modifier l'étiquette",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Ajouter des détails..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Ajouter une étiquette"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Ajouter un membre"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Carte"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Carte dupliquée",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1293,18 +1309,25 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 77],
|
||||
["src/views/settings/AccountSettings.tsx", 87],
|
||||
["src/views/settings/AccountSettings.tsx", 88],
|
||||
["src/views/settings/AccountSettings.tsx", 98],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 109],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 178]
|
||||
],
|
||||
"translation": "Changer le mot de passe"
|
||||
},
|
||||
"fzGyWs": {
|
||||
"message": "Change the application font size.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 63]],
|
||||
"translation": "Modifier la taille de police de l'application."
|
||||
},
|
||||
"yD3ZSw": {
|
||||
"message": "Change your language preferences.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 52]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 53]],
|
||||
"translation": "Modifiez vos préférences de langue."
|
||||
},
|
||||
"gZxH/p": {
|
||||
@@ -1654,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Copier le lien de la carte"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Copier les listes de contrôle",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Copier les étiquettes",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Copier le lien vers la carte",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Copier les membres",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1715,7 +1772,7 @@
|
||||
"message": "Create label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Créer une étiquette"
|
||||
},
|
||||
"XTKtey": {
|
||||
@@ -1750,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Créer une nouvelle étiquette"
|
||||
},
|
||||
@@ -1759,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Créer une nouvelle liste"
|
||||
},
|
||||
@@ -1961,8 +2018,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 59],
|
||||
["src/views/settings/AccountSettings.tsx", 69],
|
||||
["src/views/settings/AccountSettings.tsx", 70],
|
||||
["src/views/settings/AccountSettings.tsx", 80],
|
||||
["src/views/settings/components/DeleteAccountConfirmation.tsx", 96]
|
||||
],
|
||||
"translation": "Supprimer le compte"
|
||||
@@ -1978,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Supprimer la carte"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2086,7 +2146,7 @@
|
||||
"message": "Display name",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 35]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 36]],
|
||||
"translation": "Nom d'affichage"
|
||||
},
|
||||
"MlyjJu": {
|
||||
@@ -2164,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2219,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Échéance demain"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Dupliquer",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Dupliquer la carte",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Duplication en cours…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2304,7 +2395,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/members/components/InviteMemberForm.tsx", 272],
|
||||
["src/views/settings/AccountSettings.tsx", 42]
|
||||
["src/views/settings/AccountSettings.tsx", 43]
|
||||
],
|
||||
"translation": "E-mail"
|
||||
},
|
||||
@@ -2331,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "Visibilité de l'e-mail"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Fin de la liste",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2768,6 +2868,13 @@
|
||||
"origin": [["src/views/home/components/Faqs.tsx", 150]],
|
||||
"translation": "Trouvez des réponses aux questions courantes sur Kan. Vous ne trouvez pas ce que vous cherchez ? N'hésitez pas à <0>nous contacter</0>."
|
||||
},
|
||||
"q3il6U": {
|
||||
"message": "Font size",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 60]],
|
||||
"translation": "Taille de police"
|
||||
},
|
||||
"+3TYXa": {
|
||||
"message": "For long-term sustainability, we recognise all good open source projects need a source of revenue. Ours comes from the paid cloud offering for workspaces with multiple users and for custom workspace slugs. There's no obligation to use it, and you can self-host the software on your own infrastructure free of charge.",
|
||||
"placeholders": {},
|
||||
@@ -2880,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Commencez en créant une nouvelle liste"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3187,7 +3294,7 @@
|
||||
"message": "in",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 168]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 167]],
|
||||
"translation": "dans"
|
||||
},
|
||||
"WbTDwg": {
|
||||
@@ -3300,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Inviter un membre"
|
||||
@@ -3372,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3398,9 +3506,16 @@
|
||||
"message": "Language",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 49]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 50]],
|
||||
"translation": "Langue"
|
||||
},
|
||||
"k7rCa/": {
|
||||
"message": "Large",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 9]],
|
||||
"translation": "Grande"
|
||||
},
|
||||
"8Is1ih": {
|
||||
"message": "Launch offer",
|
||||
"placeholders": {},
|
||||
@@ -3476,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3486,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Liste"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3575,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Créer un modèle"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Gérer les membres",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3591,6 +3720,13 @@
|
||||
"origin": [["src/views/card/components/ActivityList.tsx", 258]],
|
||||
"translation": "a marqué l'élément de checklist <0>{0}</0> comme incomplet"
|
||||
},
|
||||
"agPptk": {
|
||||
"message": "Medium",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 8]],
|
||||
"translation": "Moyenne"
|
||||
},
|
||||
"W/Elkg": {
|
||||
"message": "Medium Priority",
|
||||
"placeholders": {},
|
||||
@@ -3642,11 +3778,13 @@
|
||||
"translation": "Mentions"
|
||||
},
|
||||
"hty0d5": {
|
||||
"translation": "Lundi",
|
||||
"message": "Monday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]
|
||||
],
|
||||
"translation": "Lundi"
|
||||
},
|
||||
"+8Nek/": {
|
||||
"message": "Monthly",
|
||||
@@ -3665,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "facturation mensuelle"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Déplacer vers une autre liste",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Déplacer vers la liste",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3776,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Nouvelle liste"
|
||||
},
|
||||
@@ -3902,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Aucune liste"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Aucune liste n'a encore été créée"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -3925,7 +4077,7 @@
|
||||
"debouncedQuery": ["debouncedQuery"]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 184]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 183]],
|
||||
"translation": "Aucun résultat trouvé pour « {debouncedQuery} »."
|
||||
},
|
||||
"Q9pQaX": {
|
||||
@@ -3976,7 +4128,7 @@
|
||||
"message": "Once you delete your account, there is no going back. This action cannot be undone.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 62]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 73]],
|
||||
"translation": "Une fois votre compte supprimé, il n'y a pas de retour en arrière possible. Cette action ne peut pas être annulée."
|
||||
},
|
||||
"dmKdk0": {
|
||||
@@ -4301,13 +4453,13 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -4357,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Veuillez réessayer."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Position dans la liste (0 = premier, laisser vide pour la fin)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4438,7 +4602,7 @@
|
||||
"message": "Profile picture",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 29]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 30]],
|
||||
"translation": "Photo de profil"
|
||||
},
|
||||
"7d1a0d": {
|
||||
@@ -4707,11 +4871,13 @@
|
||||
"translation": "SAML SSO"
|
||||
},
|
||||
"+5kO8P": {
|
||||
"translation": "Samedi",
|
||||
"message": "Saturday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]
|
||||
],
|
||||
"translation": "Samedi"
|
||||
},
|
||||
"tfDRzk": {
|
||||
"message": "Save",
|
||||
@@ -4782,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Découvrez ce que nos utilisateurs disent de Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Sélectionner une liste",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4852,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Définir la date d'échéance"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -4876,7 +5055,7 @@
|
||||
"message": "Settings | Account",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 25]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 26]],
|
||||
"translation": "Paramètres | Compte"
|
||||
},
|
||||
"iy1wb+": {
|
||||
@@ -5006,6 +5185,13 @@
|
||||
],
|
||||
"translation": "SLA"
|
||||
},
|
||||
"4Revpc": {
|
||||
"message": "Small",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 7]],
|
||||
"translation": "Petite"
|
||||
},
|
||||
"++rVAm": {
|
||||
"message": "Social Media",
|
||||
"placeholders": {},
|
||||
@@ -5078,11 +5264,13 @@
|
||||
"translation": "Succès"
|
||||
},
|
||||
"DBC3t5": {
|
||||
"translation": "Dimanche",
|
||||
"message": "Sunday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]
|
||||
],
|
||||
"translation": "Dimanche"
|
||||
},
|
||||
"uZg2+w": {
|
||||
"message": "Supercharge your workspace for just $29/month. Here's what you'll get:",
|
||||
@@ -5143,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Modèle"
|
||||
},
|
||||
@@ -5238,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Le tableau a été désarchivé."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "La carte a été dupliquée.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5420,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Titre"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Titre (facultatif)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5522,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5593,7 +5798,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Impossible de supprimer l'élément de liste de contrôle"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5603,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Impossible de supprimer le commentaire"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Impossible de dupliquer la carte",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Impossible de déplacer la carte",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5661,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Impossible de mettre à jour la carte"
|
||||
@@ -5677,7 +5896,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Impossible de mettre à jour l'élément de la checklist"
|
||||
},
|
||||
"fYVH36": {
|
||||
@@ -5706,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Impossible de mettre à jour la liste"
|
||||
@@ -5876,7 +6095,7 @@
|
||||
"message": "Update label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Mettre à jour l'étiquette"
|
||||
},
|
||||
"L5ZPjz": {
|
||||
@@ -6168,11 +6387,11 @@
|
||||
"translation": "Webhooks"
|
||||
},
|
||||
"xLTZVf": {
|
||||
"translation": "Jour de début de semaine",
|
||||
"message": "Week start day",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]]
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]],
|
||||
"translation": "Jour de début de semaine"
|
||||
},
|
||||
"9eF5oV": {
|
||||
"message": "Welcome back",
|
||||
@@ -6215,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6394,7 +6613,7 @@
|
||||
"message": "You are about to change your password.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 80]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 91]],
|
||||
"translation": "Vous êtes sur le point de modifier votre mot de passe."
|
||||
},
|
||||
"aelko+": {
|
||||
@@ -6426,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} non trovato"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Registri attività"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Aggiungi / modifica etichetta",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Aggiungi dettagli..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Aggiungi etichetta"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Aggiungi membro"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Card"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Scheda duplicata",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1293,18 +1309,25 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 77],
|
||||
["src/views/settings/AccountSettings.tsx", 87],
|
||||
["src/views/settings/AccountSettings.tsx", 88],
|
||||
["src/views/settings/AccountSettings.tsx", 98],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 109],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 178]
|
||||
],
|
||||
"translation": "Cambia password"
|
||||
},
|
||||
"fzGyWs": {
|
||||
"message": "Change the application font size.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 63]],
|
||||
"translation": "Modifica la dimensione del carattere dell'applicazione."
|
||||
},
|
||||
"yD3ZSw": {
|
||||
"message": "Change your language preferences.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 52]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 53]],
|
||||
"translation": "Modifica le tue preferenze di lingua."
|
||||
},
|
||||
"gZxH/p": {
|
||||
@@ -1654,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Copia link card"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Copia checklist",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Copia etichette",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Copia link alla scheda",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Copia membri",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1715,7 +1772,7 @@
|
||||
"message": "Create label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Crea etichetta"
|
||||
},
|
||||
"XTKtey": {
|
||||
@@ -1750,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Crea nuova etichetta"
|
||||
},
|
||||
@@ -1759,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Crea nuova lista"
|
||||
},
|
||||
@@ -1961,8 +2018,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 59],
|
||||
["src/views/settings/AccountSettings.tsx", 69],
|
||||
["src/views/settings/AccountSettings.tsx", 70],
|
||||
["src/views/settings/AccountSettings.tsx", 80],
|
||||
["src/views/settings/components/DeleteAccountConfirmation.tsx", 96]
|
||||
],
|
||||
"translation": "Elimina account"
|
||||
@@ -1978,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Elimina card"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2086,7 +2146,7 @@
|
||||
"message": "Display name",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 35]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 36]],
|
||||
"translation": "Nome visualizzato"
|
||||
},
|
||||
"MlyjJu": {
|
||||
@@ -2164,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2219,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Scadenza domani"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Duplica",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Duplica scheda",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Duplicazione in corso…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2304,7 +2395,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/members/components/InviteMemberForm.tsx", 272],
|
||||
["src/views/settings/AccountSettings.tsx", 42]
|
||||
["src/views/settings/AccountSettings.tsx", 43]
|
||||
],
|
||||
"translation": "Email"
|
||||
},
|
||||
@@ -2331,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "Visibilità email"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Fine dell'elenco",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2768,6 +2868,13 @@
|
||||
"origin": [["src/views/home/components/Faqs.tsx", 150]],
|
||||
"translation": "Trova risposte alle domande più comuni su Kan. Non trovi quello che cerchi? Sentiti libero di <0>contattarci</0>."
|
||||
},
|
||||
"q3il6U": {
|
||||
"message": "Font size",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 60]],
|
||||
"translation": "Dimensione del carattere"
|
||||
},
|
||||
"+3TYXa": {
|
||||
"message": "For long-term sustainability, we recognise all good open source projects need a source of revenue. Ours comes from the paid cloud offering for workspaces with multiple users and for custom workspace slugs. There's no obligation to use it, and you can self-host the software on your own infrastructure free of charge.",
|
||||
"placeholders": {},
|
||||
@@ -2880,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Inizia creando una nuova lista"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3187,7 +3294,7 @@
|
||||
"message": "in",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 168]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 167]],
|
||||
"translation": "in"
|
||||
},
|
||||
"WbTDwg": {
|
||||
@@ -3300,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Invita membro"
|
||||
@@ -3372,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3398,9 +3506,16 @@
|
||||
"message": "Language",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 49]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 50]],
|
||||
"translation": "Lingua"
|
||||
},
|
||||
"k7rCa/": {
|
||||
"message": "Large",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 9]],
|
||||
"translation": "Grande"
|
||||
},
|
||||
"8Is1ih": {
|
||||
"message": "Launch offer",
|
||||
"placeholders": {},
|
||||
@@ -3476,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3486,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Lista"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3575,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Crea template"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Gestisci membri",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3591,6 +3720,13 @@
|
||||
"origin": [["src/views/card/components/ActivityList.tsx", 258]],
|
||||
"translation": "ha contrassegnato l'elemento della checklist <0>{0}</0> come incompleto"
|
||||
},
|
||||
"agPptk": {
|
||||
"message": "Medium",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 8]],
|
||||
"translation": "Medio"
|
||||
},
|
||||
"W/Elkg": {
|
||||
"message": "Medium Priority",
|
||||
"placeholders": {},
|
||||
@@ -3642,11 +3778,13 @@
|
||||
"translation": "Menzioni"
|
||||
},
|
||||
"hty0d5": {
|
||||
"translation": "Lunedì",
|
||||
"message": "Monday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]
|
||||
],
|
||||
"translation": "Lunedì"
|
||||
},
|
||||
"+8Nek/": {
|
||||
"message": "Monthly",
|
||||
@@ -3665,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "fatturazione mensile"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Sposta in un altro elenco",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Sposta nell'elenco",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3776,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Nuova lista"
|
||||
},
|
||||
@@ -3902,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Nessuna lista"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Nessuna lista è stata ancora creata"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -3925,7 +4077,7 @@
|
||||
"debouncedQuery": ["debouncedQuery"]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 184]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 183]],
|
||||
"translation": "Nessun risultato trovato per \"{debouncedQuery}\"."
|
||||
},
|
||||
"Q9pQaX": {
|
||||
@@ -3976,7 +4128,7 @@
|
||||
"message": "Once you delete your account, there is no going back. This action cannot be undone.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 62]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 73]],
|
||||
"translation": "Una volta eliminato il tuo account, non si può tornare indietro. Questa azione non può essere annullata."
|
||||
},
|
||||
"dmKdk0": {
|
||||
@@ -4301,13 +4453,13 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -4357,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Riprova."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Posizione nell'elenco (0 = primo, lascia vuoto per la fine)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4438,7 +4602,7 @@
|
||||
"message": "Profile picture",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 29]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 30]],
|
||||
"translation": "Immagine del profilo"
|
||||
},
|
||||
"7d1a0d": {
|
||||
@@ -4707,11 +4871,13 @@
|
||||
"translation": "SAML SSO"
|
||||
},
|
||||
"+5kO8P": {
|
||||
"translation": "Sabato",
|
||||
"message": "Saturday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]
|
||||
],
|
||||
"translation": "Sabato"
|
||||
},
|
||||
"tfDRzk": {
|
||||
"message": "Save",
|
||||
@@ -4782,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Scopri cosa dicono i nostri utenti su Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Seleziona un elenco",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4852,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Imposta scadenza"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -4876,7 +5055,7 @@
|
||||
"message": "Settings | Account",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 25]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 26]],
|
||||
"translation": "Impostazioni | Account"
|
||||
},
|
||||
"iy1wb+": {
|
||||
@@ -5006,6 +5185,13 @@
|
||||
],
|
||||
"translation": "SLA"
|
||||
},
|
||||
"4Revpc": {
|
||||
"message": "Small",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 7]],
|
||||
"translation": "Piccolo"
|
||||
},
|
||||
"++rVAm": {
|
||||
"message": "Social Media",
|
||||
"placeholders": {},
|
||||
@@ -5078,11 +5264,13 @@
|
||||
"translation": "Successo"
|
||||
},
|
||||
"DBC3t5": {
|
||||
"translation": "Domenica",
|
||||
"message": "Sunday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]
|
||||
],
|
||||
"translation": "Domenica"
|
||||
},
|
||||
"uZg2+w": {
|
||||
"message": "Supercharge your workspace for just $29/month. Here's what you'll get:",
|
||||
@@ -5143,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Template"
|
||||
},
|
||||
@@ -5238,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "La bacheca è stata ripristinata."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "La scheda è stata duplicata.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5420,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Titolo"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Titolo (facoltativo)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5522,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5593,7 +5798,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Impossibile eliminare l'elemento della checklist"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5603,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Impossibile eliminare il commento"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Impossibile duplicare la scheda",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Impossibile spostare la scheda",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5661,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Impossibile aggiornare la card"
|
||||
@@ -5677,7 +5896,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Impossibile aggiornare l'elemento della checklist"
|
||||
},
|
||||
"fYVH36": {
|
||||
@@ -5706,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Impossibile aggiornare la lista"
|
||||
@@ -5876,7 +6095,7 @@
|
||||
"message": "Update label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Aggiorna etichetta"
|
||||
},
|
||||
"L5ZPjz": {
|
||||
@@ -6168,11 +6387,11 @@
|
||||
"translation": "Webhook"
|
||||
},
|
||||
"xLTZVf": {
|
||||
"translation": "Giorno di inizio settimana",
|
||||
"message": "Week start day",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]]
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]],
|
||||
"translation": "Giorno di inizio settimana"
|
||||
},
|
||||
"9eF5oV": {
|
||||
"message": "Welcome back",
|
||||
@@ -6215,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6394,7 +6613,7 @@
|
||||
"message": "You are about to change your password.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 80]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 91]],
|
||||
"translation": "Stai per modificare la tua password."
|
||||
},
|
||||
"aelko+": {
|
||||
@@ -6426,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} niet gevonden"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Activiteitenlogboeken"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Label toevoegen / bewerken",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Voeg details toe..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Label toevoegen"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Lid toevoegen"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Kaart"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Kaart gedupliceerd",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1293,18 +1309,25 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 77],
|
||||
["src/views/settings/AccountSettings.tsx", 87],
|
||||
["src/views/settings/AccountSettings.tsx", 88],
|
||||
["src/views/settings/AccountSettings.tsx", 98],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 109],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 178]
|
||||
],
|
||||
"translation": "Wachtwoord wijzigen"
|
||||
},
|
||||
"fzGyWs": {
|
||||
"message": "Change the application font size.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 63]],
|
||||
"translation": "Pas de lettergrootte van de applicatie aan."
|
||||
},
|
||||
"yD3ZSw": {
|
||||
"message": "Change your language preferences.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 52]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 53]],
|
||||
"translation": "Wijzig je taalvoorkeuren."
|
||||
},
|
||||
"gZxH/p": {
|
||||
@@ -1654,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Kopieer kaartlink"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Checklists kopiëren",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Labels kopiëren",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Link naar kaart kopiëren",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Leden kopiëren",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1715,7 +1772,7 @@
|
||||
"message": "Create label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Maak label"
|
||||
},
|
||||
"XTKtey": {
|
||||
@@ -1750,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Maak nieuw label"
|
||||
},
|
||||
@@ -1759,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Maak nieuwe lijst"
|
||||
},
|
||||
@@ -1961,8 +2018,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 59],
|
||||
["src/views/settings/AccountSettings.tsx", 69],
|
||||
["src/views/settings/AccountSettings.tsx", 70],
|
||||
["src/views/settings/AccountSettings.tsx", 80],
|
||||
["src/views/settings/components/DeleteAccountConfirmation.tsx", 96]
|
||||
],
|
||||
"translation": "Account verwijderen"
|
||||
@@ -1978,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Kaart verwijderen"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2086,7 +2146,7 @@
|
||||
"message": "Display name",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 35]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 36]],
|
||||
"translation": "Weergavenaam"
|
||||
},
|
||||
"MlyjJu": {
|
||||
@@ -2164,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2219,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Verloopt morgen"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Dupliceren",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Kaart dupliceren",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Dupliceren…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2304,7 +2395,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/members/components/InviteMemberForm.tsx", 272],
|
||||
["src/views/settings/AccountSettings.tsx", 42]
|
||||
["src/views/settings/AccountSettings.tsx", 43]
|
||||
],
|
||||
"translation": "E-mail"
|
||||
},
|
||||
@@ -2331,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "E-mailzichtbaarheid"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Einde van lijst",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2768,6 +2868,13 @@
|
||||
"origin": [["src/views/home/components/Faqs.tsx", 150]],
|
||||
"translation": "Vind antwoorden op veelgestelde vragen over Kan. Kun je niet vinden wat je zoekt? Neem gerust <0>contact met ons op</0>."
|
||||
},
|
||||
"q3il6U": {
|
||||
"message": "Font size",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 60]],
|
||||
"translation": "Lettergrootte"
|
||||
},
|
||||
"+3TYXa": {
|
||||
"message": "For long-term sustainability, we recognise all good open source projects need a source of revenue. Ours comes from the paid cloud offering for workspaces with multiple users and for custom workspace slugs. There's no obligation to use it, and you can self-host the software on your own infrastructure free of charge.",
|
||||
"placeholders": {},
|
||||
@@ -2880,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Ga aan de slag door een nieuwe lijst te maken"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3187,7 +3294,7 @@
|
||||
"message": "in",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 168]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 167]],
|
||||
"translation": "in"
|
||||
},
|
||||
"WbTDwg": {
|
||||
@@ -3300,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Lid uitnodigen"
|
||||
@@ -3372,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3398,9 +3506,16 @@
|
||||
"message": "Language",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 49]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 50]],
|
||||
"translation": "Taal"
|
||||
},
|
||||
"k7rCa/": {
|
||||
"message": "Large",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 9]],
|
||||
"translation": "Groot"
|
||||
},
|
||||
"8Is1ih": {
|
||||
"message": "Launch offer",
|
||||
"placeholders": {},
|
||||
@@ -3476,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3486,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Lijst"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3575,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Sjabloon maken"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Leden beheren",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3591,6 +3720,13 @@
|
||||
"origin": [["src/views/card/components/ActivityList.tsx", 258]],
|
||||
"translation": "heeft checklistitem <0>{0}</0> als onvolledig gemarkeerd"
|
||||
},
|
||||
"agPptk": {
|
||||
"message": "Medium",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 8]],
|
||||
"translation": "Gemiddeld"
|
||||
},
|
||||
"W/Elkg": {
|
||||
"message": "Medium Priority",
|
||||
"placeholders": {},
|
||||
@@ -3642,11 +3778,13 @@
|
||||
"translation": "Vermeldingen"
|
||||
},
|
||||
"hty0d5": {
|
||||
"translation": "Maandag",
|
||||
"message": "Monday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]
|
||||
],
|
||||
"translation": "Maandag"
|
||||
},
|
||||
"+8Nek/": {
|
||||
"message": "Monthly",
|
||||
@@ -3665,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "maandelijkse facturering"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Verplaats naar andere lijst",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Verplaats naar lijst",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3776,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Nieuwe lijst"
|
||||
},
|
||||
@@ -3902,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Geen lijsten"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Er zijn nog geen lijsten aangemaakt"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -3925,7 +4077,7 @@
|
||||
"debouncedQuery": ["debouncedQuery"]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 184]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 183]],
|
||||
"translation": "Geen resultaten gevonden voor \"{debouncedQuery}\"."
|
||||
},
|
||||
"Q9pQaX": {
|
||||
@@ -3976,7 +4128,7 @@
|
||||
"message": "Once you delete your account, there is no going back. This action cannot be undone.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 62]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 73]],
|
||||
"translation": "Zodra je je account verwijdert, is er geen weg terug. Deze actie kan niet ongedaan worden gemaakt."
|
||||
},
|
||||
"dmKdk0": {
|
||||
@@ -4301,13 +4453,13 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -4357,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Probeer het opnieuw."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Positie in lijst (0 = eerste, leeg laten voor het einde)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4438,7 +4602,7 @@
|
||||
"message": "Profile picture",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 29]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 30]],
|
||||
"translation": "Profielfoto"
|
||||
},
|
||||
"7d1a0d": {
|
||||
@@ -4707,11 +4871,13 @@
|
||||
"translation": "SAML SSO"
|
||||
},
|
||||
"+5kO8P": {
|
||||
"translation": "Zaterdag",
|
||||
"message": "Saturday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]
|
||||
],
|
||||
"translation": "Zaterdag"
|
||||
},
|
||||
"tfDRzk": {
|
||||
"message": "Save",
|
||||
@@ -4782,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Bekijk wat onze gebruikers over Kan zeggen"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Selecteer een lijst",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4852,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Vervaldatum instellen"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -4876,7 +5055,7 @@
|
||||
"message": "Settings | Account",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 25]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 26]],
|
||||
"translation": "Instellingen | Account"
|
||||
},
|
||||
"iy1wb+": {
|
||||
@@ -5006,6 +5185,13 @@
|
||||
],
|
||||
"translation": "SLA"
|
||||
},
|
||||
"4Revpc": {
|
||||
"message": "Small",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 7]],
|
||||
"translation": "Klein"
|
||||
},
|
||||
"++rVAm": {
|
||||
"message": "Social Media",
|
||||
"placeholders": {},
|
||||
@@ -5078,11 +5264,13 @@
|
||||
"translation": "Succes"
|
||||
},
|
||||
"DBC3t5": {
|
||||
"translation": "Zondag",
|
||||
"message": "Sunday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]
|
||||
],
|
||||
"translation": "Zondag"
|
||||
},
|
||||
"uZg2+w": {
|
||||
"message": "Supercharge your workspace for just $29/month. Here's what you'll get:",
|
||||
@@ -5143,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Sjabloon"
|
||||
},
|
||||
@@ -5238,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Het board is gedearchiveerd."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "De kaart is gedupliceerd.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5420,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Titel"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Titel (optioneel)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5522,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5593,7 +5798,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Kan checklistitem niet verwijderen"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5603,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Kan opmerking niet verwijderen"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Kan kaart niet dupliceren",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Kan kaart niet verplaatsen",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5661,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Kan kaart niet bijwerken"
|
||||
@@ -5677,7 +5896,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Kan checklistitem niet bijwerken"
|
||||
},
|
||||
"fYVH36": {
|
||||
@@ -5706,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Kan lijst niet bijwerken"
|
||||
@@ -5876,7 +6095,7 @@
|
||||
"message": "Update label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Label bijwerken"
|
||||
},
|
||||
"L5ZPjz": {
|
||||
@@ -6168,11 +6387,11 @@
|
||||
"translation": "Webhooks"
|
||||
},
|
||||
"xLTZVf": {
|
||||
"translation": "Eerste dag van de week",
|
||||
"message": "Week start day",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]]
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]],
|
||||
"translation": "Eerste dag van de week"
|
||||
},
|
||||
"9eF5oV": {
|
||||
"message": "Welcome back",
|
||||
@@ -6215,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6394,7 +6613,7 @@
|
||||
"message": "You are about to change your password.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 80]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 91]],
|
||||
"translation": "Je staat op het punt je wachtwoord te wijzigen."
|
||||
},
|
||||
"aelko+": {
|
||||
@@ -6426,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} nie znaleziono"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Dzienniki aktywności"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Dodaj / edytuj etykietę",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Dodaj szczegóły..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Dodaj etykietę"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Dodaj członka"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Karta"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Karta zduplikowana",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1293,18 +1309,25 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 77],
|
||||
["src/views/settings/AccountSettings.tsx", 87],
|
||||
["src/views/settings/AccountSettings.tsx", 88],
|
||||
["src/views/settings/AccountSettings.tsx", 98],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 109],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 178]
|
||||
],
|
||||
"translation": "Zmień hasło"
|
||||
},
|
||||
"fzGyWs": {
|
||||
"message": "Change the application font size.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 63]],
|
||||
"translation": "Zmień rozmiar czcionki aplikacji."
|
||||
},
|
||||
"yD3ZSw": {
|
||||
"message": "Change your language preferences.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 52]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 53]],
|
||||
"translation": "Zmień preferencje językowe."
|
||||
},
|
||||
"gZxH/p": {
|
||||
@@ -1654,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Kopiuj link do karty"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Kopiuj listy kontrolne",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Kopiuj etykiety",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Kopiuj link do karty",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Kopiuj członków",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1715,7 +1772,7 @@
|
||||
"message": "Create label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Utwórz etykietę"
|
||||
},
|
||||
"XTKtey": {
|
||||
@@ -1750,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Utwórz nową etykietę"
|
||||
},
|
||||
@@ -1759,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Utwórz nową listę"
|
||||
},
|
||||
@@ -1961,8 +2018,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 59],
|
||||
["src/views/settings/AccountSettings.tsx", 69],
|
||||
["src/views/settings/AccountSettings.tsx", 70],
|
||||
["src/views/settings/AccountSettings.tsx", 80],
|
||||
["src/views/settings/components/DeleteAccountConfirmation.tsx", 96]
|
||||
],
|
||||
"translation": "Usuń konto"
|
||||
@@ -1978,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Usuń kartę"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2086,7 +2146,7 @@
|
||||
"message": "Display name",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 35]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 36]],
|
||||
"translation": "Nazwa wyświetlana"
|
||||
},
|
||||
"MlyjJu": {
|
||||
@@ -2164,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2219,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Termin jutro"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Duplikuj",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Duplikuj kartę",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Duplikowanie…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2304,7 +2395,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/members/components/InviteMemberForm.tsx", 272],
|
||||
["src/views/settings/AccountSettings.tsx", 42]
|
||||
["src/views/settings/AccountSettings.tsx", 43]
|
||||
],
|
||||
"translation": "E-mail"
|
||||
},
|
||||
@@ -2331,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "Widoczność e-maila"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Koniec listy",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2768,6 +2868,13 @@
|
||||
"origin": [["src/views/home/components/Faqs.tsx", 150]],
|
||||
"translation": "Znajdź odpowiedzi na najczęstsze pytania dotyczące Kan. Nie możesz znaleźć tego, czego szukasz? Śmiało <0>skontaktuj się z nami</0>."
|
||||
},
|
||||
"q3il6U": {
|
||||
"message": "Font size",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 60]],
|
||||
"translation": "Rozmiar czcionki"
|
||||
},
|
||||
"+3TYXa": {
|
||||
"message": "For long-term sustainability, we recognise all good open source projects need a source of revenue. Ours comes from the paid cloud offering for workspaces with multiple users and for custom workspace slugs. There's no obligation to use it, and you can self-host the software on your own infrastructure free of charge.",
|
||||
"placeholders": {},
|
||||
@@ -2880,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Rozpocznij, tworząc nową listę"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3187,7 +3294,7 @@
|
||||
"message": "in",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 168]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 167]],
|
||||
"translation": "w"
|
||||
},
|
||||
"WbTDwg": {
|
||||
@@ -3300,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Zaproś członka"
|
||||
@@ -3372,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3398,9 +3506,16 @@
|
||||
"message": "Language",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 49]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 50]],
|
||||
"translation": "Język"
|
||||
},
|
||||
"k7rCa/": {
|
||||
"message": "Large",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 9]],
|
||||
"translation": "Duży"
|
||||
},
|
||||
"8Is1ih": {
|
||||
"message": "Launch offer",
|
||||
"placeholders": {},
|
||||
@@ -3476,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3486,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Lista"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3575,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Utwórz szablon"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Zarządzaj członkami",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3591,6 +3720,13 @@
|
||||
"origin": [["src/views/card/components/ActivityList.tsx", 258]],
|
||||
"translation": "oznaczył(a) element listy kontrolnej <0>{0}</0> jako nieukończony"
|
||||
},
|
||||
"agPptk": {
|
||||
"message": "Medium",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 8]],
|
||||
"translation": "Średni"
|
||||
},
|
||||
"W/Elkg": {
|
||||
"message": "Medium Priority",
|
||||
"placeholders": {},
|
||||
@@ -3642,11 +3778,13 @@
|
||||
"translation": "Wzmianki"
|
||||
},
|
||||
"hty0d5": {
|
||||
"translation": "Poniedziałek",
|
||||
"message": "Monday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]
|
||||
],
|
||||
"translation": "Poniedziałek"
|
||||
},
|
||||
"+8Nek/": {
|
||||
"message": "Monthly",
|
||||
@@ -3665,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "rozliczenie miesięczne"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Przenieś do innej listy",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Przenieś do listy",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3776,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Nowa lista"
|
||||
},
|
||||
@@ -3902,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Brak list"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Nie utworzono jeszcze żadnych list"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -3925,7 +4077,7 @@
|
||||
"debouncedQuery": ["debouncedQuery"]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 184]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 183]],
|
||||
"translation": "Nie znaleziono wyników dla „{debouncedQuery}”."
|
||||
},
|
||||
"Q9pQaX": {
|
||||
@@ -3976,7 +4128,7 @@
|
||||
"message": "Once you delete your account, there is no going back. This action cannot be undone.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 62]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 73]],
|
||||
"translation": "Po usunięciu konta nie będzie można tego cofnąć. Tej operacji nie da się odwrócić."
|
||||
},
|
||||
"dmKdk0": {
|
||||
@@ -4301,13 +4453,13 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -4357,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Spróbuj ponownie."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Pozycja na liście (0 = pierwsza, puste pole = koniec)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4438,7 +4602,7 @@
|
||||
"message": "Profile picture",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 29]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 30]],
|
||||
"translation": "Zdjęcie profilowe"
|
||||
},
|
||||
"7d1a0d": {
|
||||
@@ -4707,11 +4871,13 @@
|
||||
"translation": "SAML SSO"
|
||||
},
|
||||
"+5kO8P": {
|
||||
"translation": "Sobota",
|
||||
"message": "Saturday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]
|
||||
],
|
||||
"translation": "Sobota"
|
||||
},
|
||||
"tfDRzk": {
|
||||
"message": "Save",
|
||||
@@ -4782,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Zobacz, co nasi użytkownicy mówią o Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Wybierz listę",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4852,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Ustaw termin"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -4876,7 +5055,7 @@
|
||||
"message": "Settings | Account",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 25]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 26]],
|
||||
"translation": "Ustawienia | Konto"
|
||||
},
|
||||
"iy1wb+": {
|
||||
@@ -5006,6 +5185,13 @@
|
||||
],
|
||||
"translation": "SLA"
|
||||
},
|
||||
"4Revpc": {
|
||||
"message": "Small",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 7]],
|
||||
"translation": "Mały"
|
||||
},
|
||||
"++rVAm": {
|
||||
"message": "Social Media",
|
||||
"placeholders": {},
|
||||
@@ -5078,11 +5264,13 @@
|
||||
"translation": "Sukces"
|
||||
},
|
||||
"DBC3t5": {
|
||||
"translation": "Niedziela",
|
||||
"message": "Sunday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]
|
||||
],
|
||||
"translation": "Niedziela"
|
||||
},
|
||||
"uZg2+w": {
|
||||
"message": "Supercharge your workspace for just $29/month. Here's what you'll get:",
|
||||
@@ -5143,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Szablon"
|
||||
},
|
||||
@@ -5238,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Tablica została przywrócona z archiwum."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "Karta została zduplikowana.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5420,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Tytuł"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Tytuł (opcjonalnie)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5522,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5593,7 +5798,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Nie można usunąć elementu listy kontrolnej"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5603,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Nie można usunąć komentarza"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Nie można zduplikować karty",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Nie można przenieść karty",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5661,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Nie można zaktualizować karty"
|
||||
@@ -5677,7 +5896,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Nie można zaktualizować elementu listy kontrolnej"
|
||||
},
|
||||
"fYVH36": {
|
||||
@@ -5706,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Nie można zaktualizować listy"
|
||||
@@ -5876,7 +6095,7 @@
|
||||
"message": "Update label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Aktualizuj etykietę"
|
||||
},
|
||||
"L5ZPjz": {
|
||||
@@ -6168,11 +6387,11 @@
|
||||
"translation": "Webhooki"
|
||||
},
|
||||
"xLTZVf": {
|
||||
"translation": "Pierwszy dzień tygodnia",
|
||||
"message": "Week start day",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]]
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]],
|
||||
"translation": "Pierwszy dzień tygodnia"
|
||||
},
|
||||
"9eF5oV": {
|
||||
"message": "Welcome back",
|
||||
@@ -6215,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6394,7 +6613,7 @@
|
||||
"message": "You are about to change your password.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 80]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 91]],
|
||||
"translation": "Zaraz zmienisz swoje hasło."
|
||||
},
|
||||
"aelko+": {
|
||||
@@ -6426,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} não encontrado"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Registros de atividades"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Adicionar / editar etiqueta",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Adicionar detalhes..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Adicionar etiqueta"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Adicionar membro"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Cartão"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Cartão duplicado",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1293,18 +1309,25 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 77],
|
||||
["src/views/settings/AccountSettings.tsx", 87],
|
||||
["src/views/settings/AccountSettings.tsx", 88],
|
||||
["src/views/settings/AccountSettings.tsx", 98],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 109],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 178]
|
||||
],
|
||||
"translation": "Alterar senha"
|
||||
},
|
||||
"fzGyWs": {
|
||||
"message": "Change the application font size.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 63]],
|
||||
"translation": "Altere o tamanho da fonte do aplicativo."
|
||||
},
|
||||
"yD3ZSw": {
|
||||
"message": "Change your language preferences.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 52]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 53]],
|
||||
"translation": "Altere suas preferências de idioma."
|
||||
},
|
||||
"gZxH/p": {
|
||||
@@ -1654,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Copiar link do cartão"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Copiar checklists",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Copiar etiquetas",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Copiar link do cartão",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Copiar membros",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1715,7 +1772,7 @@
|
||||
"message": "Create label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Criar etiqueta"
|
||||
},
|
||||
"XTKtey": {
|
||||
@@ -1750,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Criar nova etiqueta"
|
||||
},
|
||||
@@ -1759,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Criar nova lista"
|
||||
},
|
||||
@@ -1961,8 +2018,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 59],
|
||||
["src/views/settings/AccountSettings.tsx", 69],
|
||||
["src/views/settings/AccountSettings.tsx", 70],
|
||||
["src/views/settings/AccountSettings.tsx", 80],
|
||||
["src/views/settings/components/DeleteAccountConfirmation.tsx", 96]
|
||||
],
|
||||
"translation": "Excluir conta"
|
||||
@@ -1978,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Excluir cartão"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2086,7 +2146,7 @@
|
||||
"message": "Display name",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 35]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 36]],
|
||||
"translation": "Nome de exibição"
|
||||
},
|
||||
"MlyjJu": {
|
||||
@@ -2164,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2219,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Vence amanhã"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Duplicar",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Duplicar cartão",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Duplicando…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2304,7 +2395,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/members/components/InviteMemberForm.tsx", 272],
|
||||
["src/views/settings/AccountSettings.tsx", 42]
|
||||
["src/views/settings/AccountSettings.tsx", 43]
|
||||
],
|
||||
"translation": "Email"
|
||||
},
|
||||
@@ -2331,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "Visibilidade do email"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Fim da lista",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2768,6 +2868,13 @@
|
||||
"origin": [["src/views/home/components/Faqs.tsx", 150]],
|
||||
"translation": "Encontre respostas para perguntas comuns sobre o Kan. Não encontrou o que procura? Sinta-se à vontade para <0>entrar em contato</0>."
|
||||
},
|
||||
"q3il6U": {
|
||||
"message": "Font size",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 60]],
|
||||
"translation": "Tamanho da fonte"
|
||||
},
|
||||
"+3TYXa": {
|
||||
"message": "For long-term sustainability, we recognise all good open source projects need a source of revenue. Ours comes from the paid cloud offering for workspaces with multiple users and for custom workspace slugs. There's no obligation to use it, and you can self-host the software on your own infrastructure free of charge.",
|
||||
"placeholders": {},
|
||||
@@ -2880,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Comece criando uma nova lista"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3187,7 +3294,7 @@
|
||||
"message": "in",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 168]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 167]],
|
||||
"translation": "em"
|
||||
},
|
||||
"WbTDwg": {
|
||||
@@ -3300,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Convidar membro"
|
||||
@@ -3372,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3398,9 +3506,16 @@
|
||||
"message": "Language",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 49]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 50]],
|
||||
"translation": "Idioma"
|
||||
},
|
||||
"k7rCa/": {
|
||||
"message": "Large",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 9]],
|
||||
"translation": "Grande"
|
||||
},
|
||||
"8Is1ih": {
|
||||
"message": "Launch offer",
|
||||
"placeholders": {},
|
||||
@@ -3476,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3486,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Lista"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3575,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Criar modelo"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Gerenciar membros",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3591,6 +3720,13 @@
|
||||
"origin": [["src/views/card/components/ActivityList.tsx", 258]],
|
||||
"translation": "marcou o item da checklist <0>{0}</0> como incompleto"
|
||||
},
|
||||
"agPptk": {
|
||||
"message": "Medium",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 8]],
|
||||
"translation": "Médio"
|
||||
},
|
||||
"W/Elkg": {
|
||||
"message": "Medium Priority",
|
||||
"placeholders": {},
|
||||
@@ -3642,11 +3778,13 @@
|
||||
"translation": "Menções"
|
||||
},
|
||||
"hty0d5": {
|
||||
"translation": "Segunda-feira",
|
||||
"message": "Monday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]
|
||||
],
|
||||
"translation": "Segunda-feira"
|
||||
},
|
||||
"+8Nek/": {
|
||||
"message": "Monthly",
|
||||
@@ -3665,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "cobrança mensal"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Mover para outra lista",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Mover para lista",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3776,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Nova lista"
|
||||
},
|
||||
@@ -3902,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Nenhuma lista"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Nenhuma lista foi criada ainda"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -3925,7 +4077,7 @@
|
||||
"debouncedQuery": ["debouncedQuery"]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 184]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 183]],
|
||||
"translation": "Nenhum resultado encontrado para \"{debouncedQuery}\"."
|
||||
},
|
||||
"Q9pQaX": {
|
||||
@@ -3976,7 +4128,7 @@
|
||||
"message": "Once you delete your account, there is no going back. This action cannot be undone.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 62]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 73]],
|
||||
"translation": "Depois de excluir sua conta, não há como voltar atrás. Esta ação não pode ser desfeita."
|
||||
},
|
||||
"dmKdk0": {
|
||||
@@ -4301,13 +4453,13 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -4357,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Por favor, tente novamente."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Posição na lista (0 = primeira, deixe vazio para o final)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4438,7 +4602,7 @@
|
||||
"message": "Profile picture",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 29]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 30]],
|
||||
"translation": "Foto de perfil"
|
||||
},
|
||||
"7d1a0d": {
|
||||
@@ -4707,11 +4871,13 @@
|
||||
"translation": "SAML SSO"
|
||||
},
|
||||
"+5kO8P": {
|
||||
"translation": "Sábado",
|
||||
"message": "Saturday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]
|
||||
],
|
||||
"translation": "Sábado"
|
||||
},
|
||||
"tfDRzk": {
|
||||
"message": "Save",
|
||||
@@ -4782,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Veja o que nossos usuários estão dizendo sobre o Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Selecione uma lista",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4852,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Definir data de vencimento"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -4876,7 +5055,7 @@
|
||||
"message": "Settings | Account",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 25]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 26]],
|
||||
"translation": "Configurações | Conta"
|
||||
},
|
||||
"iy1wb+": {
|
||||
@@ -5006,6 +5185,13 @@
|
||||
],
|
||||
"translation": "SLA"
|
||||
},
|
||||
"4Revpc": {
|
||||
"message": "Small",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 7]],
|
||||
"translation": "Pequeno"
|
||||
},
|
||||
"++rVAm": {
|
||||
"message": "Social Media",
|
||||
"placeholders": {},
|
||||
@@ -5078,11 +5264,13 @@
|
||||
"translation": "Sucesso"
|
||||
},
|
||||
"DBC3t5": {
|
||||
"translation": "Domingo",
|
||||
"message": "Sunday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]
|
||||
],
|
||||
"translation": "Domingo"
|
||||
},
|
||||
"uZg2+w": {
|
||||
"message": "Supercharge your workspace for just $29/month. Here's what you'll get:",
|
||||
@@ -5143,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Modelo"
|
||||
},
|
||||
@@ -5238,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "O quadro foi desarquivado."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "O cartão foi duplicado.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5420,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Título"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Título (opcional)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5522,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5593,7 +5798,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Não foi possível excluir item da checklist"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5603,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Não foi possível excluir o comentário"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Não foi possível duplicar o cartão",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Não foi possível mover o cartão",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5661,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Não foi possível atualizar o cartão"
|
||||
@@ -5677,7 +5896,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Não foi possível atualizar o item da checklist"
|
||||
},
|
||||
"fYVH36": {
|
||||
@@ -5706,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Não foi possível atualizar a lista"
|
||||
@@ -5876,7 +6095,7 @@
|
||||
"message": "Update label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Atualizar etiqueta"
|
||||
},
|
||||
"L5ZPjz": {
|
||||
@@ -6168,11 +6387,11 @@
|
||||
"translation": "Webhooks"
|
||||
},
|
||||
"xLTZVf": {
|
||||
"translation": "Dia de início da semana",
|
||||
"message": "Week start day",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]]
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]],
|
||||
"translation": "Dia de início da semana"
|
||||
},
|
||||
"9eF5oV": {
|
||||
"message": "Welcome back",
|
||||
@@ -6215,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6394,7 +6613,7 @@
|
||||
"message": "You are about to change your password.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 80]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 91]],
|
||||
"translation": "Você está prestes a alterar sua senha."
|
||||
},
|
||||
"aelko+": {
|
||||
@@ -6426,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,7 +71,7 @@
|
||||
"0": ["isTemplate ? \"Template\" : \"Board\""]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 440]],
|
||||
"origin": [["src/views/board/index.tsx", 556]],
|
||||
"translation": "{0} не найдено"
|
||||
},
|
||||
"0xWkkH": {
|
||||
@@ -238,6 +238,13 @@
|
||||
"origin": [["src/views/home/components/Features.tsx", 110]],
|
||||
"translation": "Журналы активности"
|
||||
},
|
||||
"UGCIzB": {
|
||||
"translation": "Добавить / изменить метку",
|
||||
"message": "Add / edit label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 50]]
|
||||
},
|
||||
"B3xxao": {
|
||||
"message": "Add a card",
|
||||
"placeholders": {},
|
||||
@@ -280,7 +287,7 @@
|
||||
"message": "Add details...",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 200]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 169]],
|
||||
"translation": "Добавить детали..."
|
||||
},
|
||||
"lyqwgn": {
|
||||
@@ -288,8 +295,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/LabelSelector.tsx", 113],
|
||||
["src/views/card/components/LabelSelector.tsx", 118]
|
||||
["src/views/card/components/LabelSelector.tsx", 114],
|
||||
["src/views/card/components/LabelSelector.tsx", 119]
|
||||
],
|
||||
"translation": "Добавить метку"
|
||||
},
|
||||
@@ -298,7 +305,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 134],
|
||||
["src/views/card/components/MemberSelector.tsx", 135],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 257]
|
||||
],
|
||||
"translation": "Добавить участника"
|
||||
@@ -842,7 +849,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/card/index.tsx", 317],
|
||||
["src/views/public/board/index.tsx", 125]
|
||||
],
|
||||
@@ -1235,6 +1242,7 @@
|
||||
"origin": [
|
||||
["src/components/DeleteLabelConfirmation.tsx", 49],
|
||||
["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 176],
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 267],
|
||||
["src/views/board/components/DeleteBoardConfirmation.tsx", 44],
|
||||
["src/views/card/components/Comment.tsx", 195],
|
||||
["src/views/card/components/DeleteCardConfirmation.tsx", 86],
|
||||
@@ -1261,6 +1269,13 @@
|
||||
"origin": [["src/views/card/index.tsx", 317]],
|
||||
"translation": "Карточка"
|
||||
},
|
||||
"sU2uWc": {
|
||||
"translation": "Карточка продублирована",
|
||||
"message": "Card duplicated",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 67]]
|
||||
},
|
||||
"fEY2vP": {
|
||||
"message": "Card not found",
|
||||
"placeholders": {},
|
||||
@@ -1283,6 +1298,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 307],
|
||||
["src/views/card/components/Dropdown.tsx", 44],
|
||||
["src/views/public/board/CardModal.tsx", 38]
|
||||
],
|
||||
@@ -1293,18 +1309,25 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 77],
|
||||
["src/views/settings/AccountSettings.tsx", 87],
|
||||
["src/views/settings/AccountSettings.tsx", 88],
|
||||
["src/views/settings/AccountSettings.tsx", 98],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 109],
|
||||
["src/views/settings/components/ChangePasswordConfirmation.tsx", 178]
|
||||
],
|
||||
"translation": "Сменить пароль"
|
||||
},
|
||||
"fzGyWs": {
|
||||
"message": "Change the application font size.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 63]],
|
||||
"translation": "Изменить размер шрифта приложения."
|
||||
},
|
||||
"yD3ZSw": {
|
||||
"message": "Change your language preferences.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 52]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 53]],
|
||||
"translation": "Измените языковые настройки."
|
||||
},
|
||||
"gZxH/p": {
|
||||
@@ -1654,6 +1677,40 @@
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 58]],
|
||||
"translation": "Скопировать ссылку на карточку"
|
||||
},
|
||||
"0utuU6": {
|
||||
"translation": "Копировать чек-листы",
|
||||
"message": "Copy checklists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 257]
|
||||
]
|
||||
},
|
||||
"fDQ7hA": {
|
||||
"translation": "Копировать метки",
|
||||
"message": "Copy labels",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 221]
|
||||
]
|
||||
},
|
||||
"1bJKhj": {
|
||||
"translation": "Копировать ссылку на карточку",
|
||||
"message": "Copy link to card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 62]]
|
||||
},
|
||||
"uvH2xS": {
|
||||
"translation": "Копировать участников",
|
||||
"message": "Copy members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 239]
|
||||
]
|
||||
},
|
||||
"9PaIxv": {
|
||||
"message": "Core features",
|
||||
"placeholders": {},
|
||||
@@ -1715,7 +1772,7 @@
|
||||
"message": "Create label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Создать метку"
|
||||
},
|
||||
"XTKtey": {
|
||||
@@ -1750,7 +1807,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewCardForm.tsx", 417],
|
||||
["src/views/card/components/LabelSelector.tsx", 100]
|
||||
["src/views/card/components/LabelSelector.tsx", 101]
|
||||
],
|
||||
"translation": "Создать новую метку"
|
||||
},
|
||||
@@ -1759,8 +1816,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 76],
|
||||
["src/views/board/index.tsx", 554]
|
||||
["src/views/board/index.tsx", 91],
|
||||
["src/views/board/index.tsx", 670]
|
||||
],
|
||||
"translation": "Создать новый список"
|
||||
},
|
||||
@@ -1961,8 +2018,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/settings/AccountSettings.tsx", 59],
|
||||
["src/views/settings/AccountSettings.tsx", 69],
|
||||
["src/views/settings/AccountSettings.tsx", 70],
|
||||
["src/views/settings/AccountSettings.tsx", 80],
|
||||
["src/views/settings/components/DeleteAccountConfirmation.tsx", 96]
|
||||
],
|
||||
"translation": "Удалить аккаунт"
|
||||
@@ -1978,7 +2035,10 @@
|
||||
"message": "Delete card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/Dropdown.tsx", 76]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMenu.tsx", 74],
|
||||
["src/views/card/components/Dropdown.tsx", 76]
|
||||
],
|
||||
"translation": "Удалить карточку"
|
||||
},
|
||||
"njJFtc": {
|
||||
@@ -2086,7 +2146,7 @@
|
||||
"message": "Display name",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 35]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 36]],
|
||||
"translation": "Отображаемое имя"
|
||||
},
|
||||
"MlyjJu": {
|
||||
@@ -2164,6 +2224,9 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 36],
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 48],
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 68],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 17],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 23],
|
||||
["src/views/boards/components/TemplateBoards.tsx", 29],
|
||||
@@ -2219,6 +2282,34 @@
|
||||
"origin": [["src/views/board/components/Filters.tsx", 122]],
|
||||
"translation": "Срок — завтра"
|
||||
},
|
||||
"euc6Ns": {
|
||||
"translation": "Дублировать",
|
||||
"message": "Duplicate",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"BTDPLo": {
|
||||
"translation": "Дублировать карточку",
|
||||
"message": "Duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 111],
|
||||
["src/views/board/components/CardContextMenu.tsx", 68]
|
||||
]
|
||||
},
|
||||
"06EQqT": {
|
||||
"translation": "Дублирование…",
|
||||
"message": "Duplicating…",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 279]
|
||||
]
|
||||
},
|
||||
"ePK91l": {
|
||||
"message": "Edit",
|
||||
"placeholders": {},
|
||||
@@ -2304,7 +2395,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/members/components/InviteMemberForm.tsx", 272],
|
||||
["src/views/settings/AccountSettings.tsx", 42]
|
||||
["src/views/settings/AccountSettings.tsx", 43]
|
||||
],
|
||||
"translation": "Email"
|
||||
},
|
||||
@@ -2331,6 +2422,15 @@
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 100]],
|
||||
"translation": "Видимость email"
|
||||
},
|
||||
"bFREhu": {
|
||||
"translation": "Конец списка",
|
||||
"message": "End of list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 200]
|
||||
]
|
||||
},
|
||||
"0+ewPp": {
|
||||
"message": "Enhancement",
|
||||
"placeholders": {},
|
||||
@@ -2768,6 +2868,13 @@
|
||||
"origin": [["src/views/home/components/Faqs.tsx", 150]],
|
||||
"translation": "Найдите ответы на часто задаваемые вопросы о Kan. Не нашли то, что искали? Не стесняйтесь <0>связаться с нами</0>."
|
||||
},
|
||||
"q3il6U": {
|
||||
"message": "Font size",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 60]],
|
||||
"translation": "Размер шрифта"
|
||||
},
|
||||
"+3TYXa": {
|
||||
"message": "For long-term sustainability, we recognise all good open source projects need a source of revenue. Ours comes from the paid cloud offering for workspaces with multiple users and for custom workspace slugs. There's no obligation to use it, and you can self-host the software on your own infrastructure free of charge.",
|
||||
"placeholders": {},
|
||||
@@ -2880,7 +2987,7 @@
|
||||
"message": "Get started by creating a new list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 539]],
|
||||
"origin": [["src/views/board/index.tsx", 655]],
|
||||
"translation": "Начните с создания нового списка"
|
||||
},
|
||||
"oW13KZ": {
|
||||
@@ -3187,7 +3294,7 @@
|
||||
"message": "in",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 168]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 167]],
|
||||
"translation": "в"
|
||||
},
|
||||
"WbTDwg": {
|
||||
@@ -3300,7 +3407,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/card/components/MemberSelector.tsx", 114],
|
||||
["src/views/card/components/MemberSelector.tsx", 115],
|
||||
["src/views/members/components/InviteMemberForm.tsx", 367]
|
||||
],
|
||||
"translation": "Пригласить участника"
|
||||
@@ -3372,6 +3479,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextLabelsModal.tsx", 31],
|
||||
["src/views/board/components/Filters.tsx", 155],
|
||||
["src/views/board/components/NewCardForm.tsx", 421],
|
||||
["src/views/card/index.tsx", 133]
|
||||
@@ -3398,9 +3506,16 @@
|
||||
"message": "Language",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 49]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 50]],
|
||||
"translation": "Язык"
|
||||
},
|
||||
"k7rCa/": {
|
||||
"message": "Large",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 9]],
|
||||
"translation": "Крупный"
|
||||
},
|
||||
"8Is1ih": {
|
||||
"message": "Launch offer",
|
||||
"placeholders": {},
|
||||
@@ -3476,6 +3591,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 80],
|
||||
["src/views/board/index.tsx", 305],
|
||||
["src/views/card/components/Dropdown.tsx", 42],
|
||||
["src/views/public/board/CardModal.tsx", 36],
|
||||
["src/views/public/board/index.tsx", 77]
|
||||
@@ -3486,7 +3602,10 @@
|
||||
"message": "List",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/index.tsx", 124]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 117],
|
||||
["src/views/card/index.tsx", 124]
|
||||
],
|
||||
"translation": "Список"
|
||||
},
|
||||
"zQX5Dj": {
|
||||
@@ -3575,6 +3694,16 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 91]],
|
||||
"translation": "Создать шаблон"
|
||||
},
|
||||
"hB02vO": {
|
||||
"translation": "Управление участниками",
|
||||
"message": "Manage members",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextMembersModal.tsx", 51],
|
||||
["src/views/board/components/CardContextMenu.tsx", 38]
|
||||
]
|
||||
},
|
||||
"IDvohQ": {
|
||||
"message": "marked a checklist item as incomplete",
|
||||
"placeholders": {},
|
||||
@@ -3591,6 +3720,13 @@
|
||||
"origin": [["src/views/card/components/ActivityList.tsx", 258]],
|
||||
"translation": "отметил элемент контрольного списка <0>{0}</0> как невыполненный"
|
||||
},
|
||||
"agPptk": {
|
||||
"message": "Medium",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 8]],
|
||||
"translation": "Средний"
|
||||
},
|
||||
"W/Elkg": {
|
||||
"message": "Medium Priority",
|
||||
"placeholders": {},
|
||||
@@ -3642,11 +3778,13 @@
|
||||
"translation": "Упоминания"
|
||||
},
|
||||
"hty0d5": {
|
||||
"translation": "Понедельник",
|
||||
"message": "Monday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 53]
|
||||
],
|
||||
"translation": "Понедельник"
|
||||
},
|
||||
"+8Nek/": {
|
||||
"message": "Monthly",
|
||||
@@ -3665,6 +3803,20 @@
|
||||
"origin": [["src/views/members/components/InviteMemberForm.tsx", 159]],
|
||||
"translation": "ежемесячная оплата"
|
||||
},
|
||||
"51UCsN": {
|
||||
"translation": "Переместить в другой список",
|
||||
"message": "Move to another list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMenu.tsx", 44]]
|
||||
},
|
||||
"J4+OTA": {
|
||||
"translation": "Переместить в список",
|
||||
"message": "Move to list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 70]]
|
||||
},
|
||||
"BOqTi5": {
|
||||
"message": "moved the card from <0>{0}</0> to<1>{1}</1>",
|
||||
"placeholders": {
|
||||
@@ -3776,7 +3928,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/NewListForm.tsx", 113],
|
||||
["src/views/board/index.tsx", 503]
|
||||
["src/views/board/index.tsx", 619]
|
||||
],
|
||||
"translation": "Новый список"
|
||||
},
|
||||
@@ -3902,14 +4054,14 @@
|
||||
"message": "No lists",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 535]],
|
||||
"origin": [["src/views/board/index.tsx", 651]],
|
||||
"translation": "Нет списков"
|
||||
},
|
||||
"fvLNDy": {
|
||||
"message": "No lists have been created yet",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/index.tsx", 540]],
|
||||
"origin": [["src/views/board/index.tsx", 656]],
|
||||
"translation": "Списки ещё не созданы"
|
||||
},
|
||||
"i30J2U": {
|
||||
@@ -3925,7 +4077,7 @@
|
||||
"debouncedQuery": ["debouncedQuery"]
|
||||
},
|
||||
"comments": [],
|
||||
"origin": [["src/components/CommandPallette.tsx", 184]],
|
||||
"origin": [["src/components/CommandPallette.tsx", 183]],
|
||||
"translation": "По запросу «{debouncedQuery}» ничего не найдено."
|
||||
},
|
||||
"Q9pQaX": {
|
||||
@@ -3976,7 +4128,7 @@
|
||||
"message": "Once you delete your account, there is no going back. This action cannot be undone.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 62]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 73]],
|
||||
"translation": "После удаления аккаунта возврата не будет. Это действие необратимо."
|
||||
},
|
||||
"dmKdk0": {
|
||||
@@ -4301,13 +4453,13 @@
|
||||
["src/views/board/components/NewTemplateForm.tsx", 77],
|
||||
["src/views/board/components/UpdateBoardSlugForm.tsx", 73],
|
||||
["src/views/board/components/VisibilityButton.tsx", 57],
|
||||
["src/views/board/index.tsx", 194],
|
||||
["src/views/board/index.tsx", 250],
|
||||
["src/views/board/index.tsx", 217],
|
||||
["src/views/board/index.tsx", 273],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 243],
|
||||
["src/views/boards/components/ImportBoardsForm.tsx", 395],
|
||||
["src/views/card/components/AttachmentThumbnails.tsx", 74],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 69],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 97],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 67],
|
||||
["src/views/card/components/ChecklistItemRow.tsx", 95],
|
||||
["src/views/card/components/ChecklistNameInput.tsx", 47],
|
||||
["src/views/card/components/Checklists.tsx", 81],
|
||||
["src/views/card/components/Comment.tsx", 93],
|
||||
@@ -4357,12 +4509,24 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 76],
|
||||
["src/views/board/components/CardContextMoveListModal.tsx", 42],
|
||||
["src/views/board/index.tsx", 314],
|
||||
["src/views/card/components/Dropdown.tsx", 51],
|
||||
["src/views/public/board/CardModal.tsx", 45],
|
||||
["src/views/public/board/index.tsx", 86]
|
||||
],
|
||||
"translation": "Пожалуйста, попробуйте ещё раз."
|
||||
},
|
||||
"nYNMZZ": {
|
||||
"translation": "Позиция в списке (0 = первая, оставьте пустым для конца)",
|
||||
"message": "Position in list (0 = first, leave empty for end)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 193]
|
||||
]
|
||||
},
|
||||
"aHCEmh": {
|
||||
"message": "Pricing",
|
||||
"placeholders": {},
|
||||
@@ -4438,7 +4602,7 @@
|
||||
"message": "Profile picture",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 29]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 30]],
|
||||
"translation": "Фото профиля"
|
||||
},
|
||||
"7d1a0d": {
|
||||
@@ -4707,11 +4871,13 @@
|
||||
"translation": "SAML SSO"
|
||||
},
|
||||
"+5kO8P": {
|
||||
"translation": "Суббота",
|
||||
"message": "Saturday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 54]
|
||||
],
|
||||
"translation": "Суббота"
|
||||
},
|
||||
"tfDRzk": {
|
||||
"message": "Save",
|
||||
@@ -4782,6 +4948,15 @@
|
||||
"origin": [["src/views/home/components/Testimonials.tsx", 239]],
|
||||
"translation": "Посмотрите, что наши пользователи говорят о Kan"
|
||||
},
|
||||
"zYRVNp": {
|
||||
"translation": "Выберите список",
|
||||
"message": "Select a list",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 131]
|
||||
]
|
||||
},
|
||||
"wgNoIs": {
|
||||
"message": "Select all",
|
||||
"placeholders": {},
|
||||
@@ -4852,7 +5027,11 @@
|
||||
"message": "Set due date",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/DueDateSelector.tsx", 121]],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDueDateModal.tsx", 20],
|
||||
["src/views/board/components/CardContextMenu.tsx", 56],
|
||||
["src/views/card/components/DueDateSelector.tsx", 121]
|
||||
],
|
||||
"translation": "Установить срок"
|
||||
},
|
||||
"Eit43O": {
|
||||
@@ -4876,7 +5055,7 @@
|
||||
"message": "Settings | Account",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 25]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 26]],
|
||||
"translation": "Настройки | Аккаунт"
|
||||
},
|
||||
"iy1wb+": {
|
||||
@@ -5006,6 +5185,13 @@
|
||||
],
|
||||
"translation": "SLA"
|
||||
},
|
||||
"4Revpc": {
|
||||
"message": "Small",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/FontSizeSelector.tsx", 7]],
|
||||
"translation": "Мелкий"
|
||||
},
|
||||
"++rVAm": {
|
||||
"message": "Social Media",
|
||||
"placeholders": {},
|
||||
@@ -5078,11 +5264,13 @@
|
||||
"translation": "Успех"
|
||||
},
|
||||
"DBC3t5": {
|
||||
"translation": "Воскресенье",
|
||||
"message": "Sunday",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]]
|
||||
"origin": [
|
||||
["src/views/settings/components/UpdateWeekStartDayForm.tsx", 52]
|
||||
],
|
||||
"translation": "Воскресенье"
|
||||
},
|
||||
"uZg2+w": {
|
||||
"message": "Supercharge your workspace for just $29/month. Here's what you'll get:",
|
||||
@@ -5143,8 +5331,8 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 449]
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/board/index.tsx", 565]
|
||||
],
|
||||
"translation": "Шаблон"
|
||||
},
|
||||
@@ -5238,6 +5426,13 @@
|
||||
"origin": [["src/views/board/components/BoardDropdown.tsx", 47]],
|
||||
"translation": "Доска была восстановлена из архива."
|
||||
},
|
||||
"nKBUeF": {
|
||||
"translation": "Карточка была продублирована.",
|
||||
"message": "The card has been duplicated.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 69]]
|
||||
},
|
||||
"jlB2RY": {
|
||||
"message": "The current password you entered is incorrect.",
|
||||
"placeholders": {},
|
||||
@@ -5420,6 +5615,15 @@
|
||||
"origin": [["src/components/YouTubeEmbed/EditYouTubeModal.tsx", 127]],
|
||||
"translation": "Заголовок"
|
||||
},
|
||||
"wK4OTM": {
|
||||
"translation": "Название (необязательно)",
|
||||
"message": "Title (optional)",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/components/CardContextDuplicateModal.tsx", 180]
|
||||
]
|
||||
},
|
||||
"Se/UIp": {
|
||||
"message": "To Do",
|
||||
"placeholders": {},
|
||||
@@ -5522,6 +5726,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 312],
|
||||
["src/views/card/components/Dropdown.tsx", 49],
|
||||
["src/views/public/board/CardModal.tsx", 43],
|
||||
["src/views/public/board/index.tsx", 84]
|
||||
@@ -5593,7 +5798,7 @@
|
||||
"message": "Unable to delete checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 96]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 94]],
|
||||
"translation": "Не удалось удалить элемент контрольного списка"
|
||||
},
|
||||
"2QGEbi": {
|
||||
@@ -5603,6 +5808,20 @@
|
||||
"origin": [["src/views/card/components/DeleteCommentConfirmation.tsx", 45]],
|
||||
"translation": "Не удалось удалить комментарий"
|
||||
},
|
||||
"Oo4E6p": {
|
||||
"translation": "Невозможно дублировать карточку",
|
||||
"message": "Unable to duplicate card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextDuplicateModal.tsx", 75]]
|
||||
},
|
||||
"K7k9u3": {
|
||||
"translation": "Невозможно переместить карточку",
|
||||
"message": "Unable to move card",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/board/components/CardContextMoveListModal.tsx", 41]]
|
||||
},
|
||||
"8yFGGF": {
|
||||
"message": "Unable to remove member",
|
||||
"placeholders": {},
|
||||
@@ -5661,7 +5880,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 249],
|
||||
["src/views/board/index.tsx", 272],
|
||||
["src/views/card/index.tsx", 231]
|
||||
],
|
||||
"translation": "Не удалось обновить карточку"
|
||||
@@ -5677,7 +5896,7 @@
|
||||
"message": "Unable to update checklist item",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 68]],
|
||||
"origin": [["src/views/card/components/ChecklistItemRow.tsx", 66]],
|
||||
"translation": "Не удалось обновить элемент контрольного списка"
|
||||
},
|
||||
"fYVH36": {
|
||||
@@ -5706,7 +5925,7 @@
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/views/board/index.tsx", 193],
|
||||
["src/views/board/index.tsx", 216],
|
||||
["src/views/card/components/ListSelector.tsx", 54]
|
||||
],
|
||||
"translation": "Не удалось обновить список"
|
||||
@@ -5876,7 +6095,7 @@
|
||||
"message": "Update label",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/components/LabelForm.tsx", 238]],
|
||||
"origin": [["src/components/LabelForm.tsx", 236]],
|
||||
"translation": "Обновить метку"
|
||||
},
|
||||
"L5ZPjz": {
|
||||
@@ -6168,11 +6387,11 @@
|
||||
"translation": "Вебхуки"
|
||||
},
|
||||
"xLTZVf": {
|
||||
"translation": "День начала недели",
|
||||
"message": "Week start day",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]]
|
||||
"origin": [["src/views/settings/WorkspaceSettings.tsx", 91]],
|
||||
"translation": "День начала недели"
|
||||
},
|
||||
"9eF5oV": {
|
||||
"message": "Welcome back",
|
||||
@@ -6215,7 +6434,7 @@
|
||||
"comments": [],
|
||||
"origin": [
|
||||
["src/components/SettingsLayout.tsx", 47],
|
||||
["src/views/board/index.tsx", 413],
|
||||
["src/views/board/index.tsx", 529],
|
||||
["src/views/boards/index.tsx", 48],
|
||||
["src/views/members/index.tsx", 258],
|
||||
["src/views/public/board/index.tsx", 125],
|
||||
@@ -6394,7 +6613,7 @@
|
||||
"message": "You are about to change your password.",
|
||||
"placeholders": {},
|
||||
"comments": [],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 80]],
|
||||
"origin": [["src/views/settings/AccountSettings.tsx", 91]],
|
||||
"translation": "Вы собираетесь изменить свой пароль."
|
||||
},
|
||||
"aelko+": {
|
||||
@@ -6426,8 +6645,8 @@
|
||||
["src/views/board/components/List.tsx", 117],
|
||||
["src/views/board/components/UpdateBoardSlugButton.tsx", 58],
|
||||
["src/views/board/components/VisibilityButton.tsx", 72],
|
||||
["src/views/board/index.tsx", 487],
|
||||
["src/views/board/index.tsx", 545],
|
||||
["src/views/board/index.tsx", 603],
|
||||
["src/views/board/index.tsx", 661],
|
||||
["src/views/boards/components/BoardsList.tsx", 71],
|
||||
["src/views/boards/index.tsx", 59],
|
||||
["src/views/boards/index.tsx", 80]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -12,6 +12,7 @@ import posthog from "posthog-js";
|
||||
import { PostHogProvider } from "posthog-js/react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { FontSizeProvider } from "~/providers/font-size";
|
||||
import { KeyboardShortcutProvider } from "~/providers/keyboard-shortcuts";
|
||||
import { LinguiProviderWrapper } from "~/providers/lingui";
|
||||
import { ModalProvider } from "~/providers/modal";
|
||||
@@ -83,19 +84,21 @@ const MyApp: AppType = ({ Component, pageProps }: AppPropsWithLayout) => {
|
||||
<main className="font-sans">
|
||||
<KeyboardShortcutProvider>
|
||||
<LinguiProviderWrapper>
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||
<ModalProvider>
|
||||
<PopupProvider>
|
||||
{posthogKey ? (
|
||||
<PostHogProvider client={posthog}>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</PostHogProvider>
|
||||
) : (
|
||||
getLayout(<Component {...pageProps} />)
|
||||
)}
|
||||
</PopupProvider>
|
||||
</ModalProvider>
|
||||
</ThemeProvider>
|
||||
<FontSizeProvider>
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||
<ModalProvider>
|
||||
<PopupProvider>
|
||||
{posthogKey ? (
|
||||
<PostHogProvider client={posthog}>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</PostHogProvider>
|
||||
) : (
|
||||
getLayout(<Component {...pageProps} />)
|
||||
)}
|
||||
</PopupProvider>
|
||||
</ModalProvider>
|
||||
</ThemeProvider>
|
||||
</FontSizeProvider>
|
||||
</LinguiProviderWrapper>
|
||||
</KeyboardShortcutProvider>
|
||||
</main>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
|
||||
import { env } from "~/env";
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "GET") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
}
|
||||
@@ -32,7 +33,9 @@ export default withRateLimit(
|
||||
try {
|
||||
allowedHost = new URL(s3Endpoint).hostname.toLowerCase();
|
||||
} catch {
|
||||
return res.status(500).json({ message: "Storage endpoint misconfigured" });
|
||||
return res
|
||||
.status(500)
|
||||
.json({ message: "Storage endpoint misconfigured" });
|
||||
}
|
||||
|
||||
if (hostname !== allowedHost && !hostname.endsWith(`.${allowedHost}`)) {
|
||||
@@ -66,10 +69,7 @@ export default withRateLimit(
|
||||
const buffer = await upstream.arrayBuffer();
|
||||
return res.send(Buffer.from(buffer));
|
||||
} catch (error) {
|
||||
console.error("Error downloading attachment:", error);
|
||||
return res
|
||||
.status(500)
|
||||
.json({ message: "Failed to download attachment" });
|
||||
return res.status(500).json({ message: "Failed to download attachment" });
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "GET") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("https://formbricks.com/api/oss-friends");
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch from Formbricks");
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "GET") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
return res.status(200).json(data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching OSS friends:", error);
|
||||
return res.status(500).json({ message: "Failed to fetch OSS friends" });
|
||||
}
|
||||
},
|
||||
try {
|
||||
const response = await fetch("https://formbricks.com/api/oss-friends");
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch from Formbricks");
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
return res.status(200).json(data);
|
||||
} catch (error) {
|
||||
return res.status(500).json({ message: "Failed to fetch OSS friends" });
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -2,34 +2,34 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { env } from "next-runtime-env";
|
||||
|
||||
import { createNextApiContext } from "@kan/api/trpc";
|
||||
import { createStripeClient } from "@kan/stripe";
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
import { createStripeClient } from "@kan/stripe";
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const stripe = createStripeClient();
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const stripe = createStripeClient();
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
try {
|
||||
const { user } = await createNextApiContext(req);
|
||||
|
||||
if (!user?.stripeCustomerId) {
|
||||
return res.status(404).json({ error: "No billing account found" });
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
const session = await stripe.billingPortal.sessions.create({
|
||||
customer: user.stripeCustomerId,
|
||||
return_url: `${env("NEXT_PUBLIC_BASE_URL")}/settings`,
|
||||
});
|
||||
try {
|
||||
const { user } = await createNextApiContext(req);
|
||||
|
||||
return res.status(200).json({ url: session.url });
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
return res.status(500).json({ error: "Error creating portal session" });
|
||||
}
|
||||
},
|
||||
if (!user?.stripeCustomerId) {
|
||||
return res.status(404).json({ error: "No billing account found" });
|
||||
}
|
||||
|
||||
const session = await stripe.billingPortal.sessions.create({
|
||||
customer: user.stripeCustomerId,
|
||||
return_url: `${env("NEXT_PUBLIC_BASE_URL")}/settings`,
|
||||
});
|
||||
|
||||
return res.status(200).json({ url: session.url });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: "Error creating portal session" });
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -3,10 +3,13 @@ import { env } from "next-runtime-env";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNextApiContext } from "@kan/api/trpc";
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { assertPermission } from "@kan/api/utils/permissions";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
import * as subscriptionRepo from "@kan/db/repository/subscription.repo";
|
||||
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
|
||||
import { generateUID } from "@kan/shared/utils";
|
||||
import { createStripeClient } from "@kan/stripe";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
|
||||
const workspaceSlugSchema = z
|
||||
.string()
|
||||
@@ -17,103 +20,135 @@ const workspaceSlugSchema = z
|
||||
interface CheckoutSessionRequest {
|
||||
successUrl: string;
|
||||
cancelUrl: string;
|
||||
slug: string;
|
||||
workspacePublicId: string;
|
||||
stripeCustomerId: string;
|
||||
billing?: string;
|
||||
workspacePublicId?: string;
|
||||
slug?: string;
|
||||
workspaceName?: string;
|
||||
workspaceDescription?: string;
|
||||
workspaceSlug?: string;
|
||||
plan?: string;
|
||||
}
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const stripe = createStripeClient();
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const stripe = createStripeClient();
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
try {
|
||||
const { user, db } = await createNextApiContext(req);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
const body = req.body as CheckoutSessionRequest;
|
||||
const { successUrl, cancelUrl, slug, workspacePublicId } = body;
|
||||
try {
|
||||
const { user, db } = await createNextApiContext(req);
|
||||
|
||||
if (!successUrl || !cancelUrl || !workspacePublicId) {
|
||||
return res.status(400).json({ error: "Missing required fields" });
|
||||
}
|
||||
|
||||
if (slug) {
|
||||
const slugResult = workspaceSlugSchema.safeParse(slug);
|
||||
|
||||
if (!slugResult.success) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Invalid workspace slug" }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
);
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
}
|
||||
|
||||
const workspace = await workspaceRepo.getAllByUserId(db, user.id);
|
||||
|
||||
const isMemberOfWorkspace = workspace.some(
|
||||
({ workspace }) => workspace.publicId === body.workspacePublicId,
|
||||
);
|
||||
|
||||
if (!isMemberOfWorkspace) {
|
||||
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
||||
status: 403,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const subscription = await subscriptionRepo.create(db, {
|
||||
plan: "pro",
|
||||
referenceId: workspacePublicId,
|
||||
userId: user.id,
|
||||
stripeCustomerId: user.stripeCustomerId ?? "",
|
||||
status: "incomplete",
|
||||
});
|
||||
|
||||
const subscriptionId = subscription?.id;
|
||||
|
||||
if (!subscriptionId) {
|
||||
return res.status(500).json({ error: "Error creating subscription" });
|
||||
}
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
mode: "subscription",
|
||||
payment_method_collection: "always",
|
||||
line_items: [
|
||||
{
|
||||
price: process.env.STRIPE_PRO_PLAN_MONTHLY_PRICE_ID,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
subscription_data: {
|
||||
trial_period_days: 14,
|
||||
},
|
||||
success_url: `${env("NEXT_PUBLIC_BASE_URL")}${successUrl}`,
|
||||
cancel_url: `${env("NEXT_PUBLIC_BASE_URL")}${cancelUrl}`,
|
||||
client_reference_id: workspacePublicId,
|
||||
customer: user.stripeCustomerId ?? undefined,
|
||||
metadata: {
|
||||
...(slug && { workspaceSlug: slug }),
|
||||
const body = req.body as CheckoutSessionRequest;
|
||||
const {
|
||||
successUrl,
|
||||
cancelUrl,
|
||||
billing,
|
||||
workspacePublicId,
|
||||
userId: user.id,
|
||||
subscriptionId,
|
||||
},
|
||||
});
|
||||
slug,
|
||||
workspaceName,
|
||||
workspaceDescription,
|
||||
workspaceSlug,
|
||||
} = body;
|
||||
|
||||
return res.status(200).json({ url: session.url });
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
return res.status(500).json({ error: "Error creating checkout session" });
|
||||
}
|
||||
},
|
||||
if (!successUrl || !cancelUrl) {
|
||||
return res.status(400).json({ error: "Missing required fields" });
|
||||
}
|
||||
|
||||
if (!workspacePublicId && !workspaceName) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Must provide workspacePublicId or workspaceName" });
|
||||
}
|
||||
|
||||
const resolvedSlug = slug ?? workspaceSlug;
|
||||
|
||||
if (resolvedSlug) {
|
||||
const slugResult = workspaceSlugSchema.safeParse(resolvedSlug);
|
||||
if (!slugResult.success) {
|
||||
return res.status(400).json({ error: "Invalid workspace slug" });
|
||||
}
|
||||
}
|
||||
|
||||
let resolvedWorkspacePublicId = workspacePublicId;
|
||||
let subscriptionId: number | undefined;
|
||||
|
||||
if (workspacePublicId) {
|
||||
// Existing workspace upgrade
|
||||
const workspace = await workspaceRepo.getByPublicId(
|
||||
db,
|
||||
workspacePublicId,
|
||||
);
|
||||
|
||||
if (!workspace) {
|
||||
return res.status(404).json({ error: "Workspace not found" });
|
||||
}
|
||||
|
||||
try {
|
||||
await assertPermission(db, user.id, workspace.id, "workspace:manage");
|
||||
} catch {
|
||||
return res.status(403).json({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
const subscription = await subscriptionRepo.create(db, {
|
||||
plan: "pro",
|
||||
referenceId: workspacePublicId,
|
||||
userId: user.id,
|
||||
stripeCustomerId: user.stripeCustomerId ?? "",
|
||||
status: "incomplete",
|
||||
});
|
||||
|
||||
subscriptionId = subscription?.id;
|
||||
|
||||
if (!subscriptionId) {
|
||||
return res.status(500).json({ error: "Error creating subscription" });
|
||||
}
|
||||
} else {
|
||||
resolvedWorkspacePublicId = generateUID();
|
||||
}
|
||||
|
||||
const isTeam = body.plan === "team";
|
||||
const annualPriceId = isTeam
|
||||
? (process.env.STRIPE_TEAM_PLAN_ANNUAL_PRICE_ID ??
|
||||
process.env.STRIPE_TEAM_PLAN_MONTHLY_PRICE_ID)
|
||||
: (process.env.STRIPE_PRO_PLAN_ANNUAL_PRICE_ID ??
|
||||
process.env.STRIPE_PRO_PLAN_MONTHLY_PRICE_ID);
|
||||
const monthlyPriceId = isTeam
|
||||
? process.env.STRIPE_TEAM_PLAN_MONTHLY_PRICE_ID
|
||||
: process.env.STRIPE_PRO_PLAN_MONTHLY_PRICE_ID;
|
||||
const priceId = billing === "annual" ? annualPriceId : monthlyPriceId;
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
mode: "subscription",
|
||||
payment_method_collection: "always",
|
||||
line_items: [{ price: priceId, quantity: 1 }],
|
||||
subscription_data: { trial_period_days: 14 },
|
||||
success_url: `${env("NEXT_PUBLIC_BASE_URL")}${successUrl}?workspacePublicId=${resolvedWorkspacePublicId}`,
|
||||
cancel_url: `${env("NEXT_PUBLIC_BASE_URL")}${cancelUrl}`,
|
||||
client_reference_id: resolvedWorkspacePublicId,
|
||||
customer: user.stripeCustomerId ?? undefined,
|
||||
metadata: {
|
||||
workspacePublicId: resolvedWorkspacePublicId!,
|
||||
userId: user.id,
|
||||
userEmail: user.email ?? "",
|
||||
...(resolvedSlug && { workspaceSlug: resolvedSlug }),
|
||||
...(workspaceName && { workspaceName }),
|
||||
...(workspaceDescription && { workspaceDescription }),
|
||||
...(subscriptionId && { subscriptionId: String(subscriptionId) }),
|
||||
plan: body.plan ?? "pro",
|
||||
isNewWorkspace: workspaceName ? "true" : "false",
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({ url: session.url });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: "Error creating checkout session" });
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import type { Readable } from "node:stream";
|
||||
|
||||
import { createNextApiContext } from "@kan/api/trpc";
|
||||
import * as subscriptionRepo from "@kan/db/repository/subscription.repo";
|
||||
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
|
||||
import { createLogger } from "@kan/logger";
|
||||
import { createStripeClient } from "@kan/stripe";
|
||||
|
||||
const log = createLogger("api");
|
||||
|
||||
async function buffer(readable: Readable) {
|
||||
const chunks = [];
|
||||
for await (const chunk of readable) {
|
||||
@@ -18,6 +23,9 @@ export default async function handler(
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
const stripe = createStripeClient();
|
||||
const start = Date.now();
|
||||
const requestId = randomUUID();
|
||||
const procedure = req.url?.split("?")[0] ?? "/api/stripe/webhook";
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
@@ -44,24 +52,82 @@ export default async function handler(
|
||||
switch (event.type) {
|
||||
case "checkout.session.completed": {
|
||||
const checkoutSession = event.data.object;
|
||||
const meta = checkoutSession.metadata;
|
||||
|
||||
const metaData = checkoutSession.metadata;
|
||||
if (!meta?.workspacePublicId) break;
|
||||
|
||||
if (metaData?.workspacePublicId) {
|
||||
await workspaceRepo.update(db, metaData.workspacePublicId, {
|
||||
...(metaData.workspaceSlug && { slug: metaData.workspaceSlug }),
|
||||
plan: "pro",
|
||||
const plan = meta.plan === "team" ? "team" : ("pro" as const);
|
||||
|
||||
if (
|
||||
meta.isNewWorkspace === "true" &&
|
||||
meta.workspaceName &&
|
||||
meta.userId &&
|
||||
meta.userEmail
|
||||
) {
|
||||
const existing = await workspaceRepo.getByPublicId(db, meta.workspacePublicId);
|
||||
|
||||
if (!existing) {
|
||||
const slug = meta.workspaceSlug ?? meta.workspacePublicId;
|
||||
|
||||
await workspaceRepo.create(db, {
|
||||
publicId: meta.workspacePublicId,
|
||||
name: meta.workspaceName,
|
||||
slug,
|
||||
plan,
|
||||
createdBy: meta.userId,
|
||||
createdByEmail: meta.userEmail,
|
||||
...(meta.workspaceDescription && {
|
||||
description: meta.workspaceDescription,
|
||||
}),
|
||||
});
|
||||
|
||||
await subscriptionRepo.create(db, {
|
||||
plan,
|
||||
referenceId: meta.workspacePublicId,
|
||||
userId: meta.userId,
|
||||
stripeCustomerId: checkoutSession.customer as string,
|
||||
status: "active",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Existing workspace upgrade — update plan (and slug for pro)
|
||||
await workspaceRepo.update(db, meta.workspacePublicId, {
|
||||
plan,
|
||||
...(plan === "pro" && meta.workspaceSlug && { slug: meta.workspaceSlug }),
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.log(`Unhandled event type: ${event.type}`);
|
||||
log.warn({ eventType: event.type }, "Unhandled Stripe event type");
|
||||
}
|
||||
|
||||
log.info(
|
||||
{
|
||||
requestId,
|
||||
procedure,
|
||||
transport: "rest",
|
||||
duration: Date.now() - start,
|
||||
status: 200,
|
||||
input: { eventType: event.type, eventId: event.id },
|
||||
},
|
||||
"API OK",
|
||||
);
|
||||
|
||||
return res.status(200).json({ received: true });
|
||||
} catch (err) {
|
||||
console.error("Webhook error:", err);
|
||||
log.error(
|
||||
{
|
||||
requestId,
|
||||
procedure,
|
||||
transport: "rest",
|
||||
duration: Date.now() - start,
|
||||
status: 400,
|
||||
err,
|
||||
},
|
||||
"API error",
|
||||
);
|
||||
return res.status(400).json({ message: "Webhook handler failed" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +1,58 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { addYears } from "date-fns";
|
||||
|
||||
import { createNextApiContext } from "@kan/api/trpc";
|
||||
import { integrations } from "@kan/db/schema";
|
||||
import { addYears } from "date-fns";
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
import { integrations } from "@kan/db/schema";
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
}
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
}
|
||||
|
||||
const { user } = await createNextApiContext(req);
|
||||
const { user } = await createNextApiContext(req);
|
||||
|
||||
if (!user)
|
||||
return res.status(401).json({ message: "User not authenticated" });
|
||||
if (!user)
|
||||
return res.status(401).json({ message: "User not authenticated" });
|
||||
|
||||
const apiKey = process.env.TRELLO_APP_API_KEY;
|
||||
const apiKey = process.env.TRELLO_APP_API_KEY;
|
||||
|
||||
if (!apiKey)
|
||||
return res.status(500).json({ message: "Trello API key not set in Environment Variables" });
|
||||
if (!apiKey)
|
||||
return res
|
||||
.status(500)
|
||||
.json({ message: "Trello API key not set in Environment Variables" });
|
||||
|
||||
const token = req.body.token;
|
||||
const token = req.body.token;
|
||||
|
||||
if (!token)
|
||||
return res.status(400).json({ message: "No token found" });
|
||||
if (!token) return res.status(400).json({ message: "No token found" });
|
||||
|
||||
try {
|
||||
const { db } = await createNextApiContext(req);
|
||||
try {
|
||||
const { db } = await createNextApiContext(req);
|
||||
|
||||
await db.insert(integrations).values({
|
||||
provider: "trello",
|
||||
userId: user.id,
|
||||
accessToken: token,
|
||||
expiresAt: addYears(new Date(), 1),
|
||||
}).onConflictDoUpdate({
|
||||
set: {
|
||||
accessToken: token,
|
||||
expiresAt: addYears(new Date(), 1),
|
||||
},
|
||||
target: [integrations.userId, integrations.provider],
|
||||
});
|
||||
await db
|
||||
.insert(integrations)
|
||||
.values({
|
||||
provider: "trello",
|
||||
userId: user.id,
|
||||
accessToken: token,
|
||||
expiresAt: addYears(new Date(), 1),
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
set: {
|
||||
accessToken: token,
|
||||
expiresAt: addYears(new Date(), 1),
|
||||
},
|
||||
target: [integrations.userId, integrations.provider],
|
||||
});
|
||||
|
||||
return res.status(200).json({ message: "Trello authentication successful" });
|
||||
} catch (err) {
|
||||
console.error("Trello authentication error:", err);
|
||||
return res.status(400).json({ message: "Trello authentication failed" });
|
||||
}
|
||||
},
|
||||
);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ message: "Trello authentication successful" });
|
||||
} catch (err) {
|
||||
return res.status(400).json({ message: "Trello authentication failed" });
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -3,9 +3,11 @@ import { Novu } from "@novu/api";
|
||||
import { jwtVerify } from "jose";
|
||||
import { z } from "zod";
|
||||
|
||||
import { env } from "~/env";
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
|
||||
import { env } from "~/env";
|
||||
|
||||
const requestSchema = z.object({
|
||||
token: z.string().min(1),
|
||||
});
|
||||
@@ -22,84 +24,85 @@ const textEncoder = new TextEncoder();
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse<ResponseData>) => {
|
||||
if (process.env.NEXT_PUBLIC_KAN_ENV !== "cloud") {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: "Unsubscribe endpoint is not available.",
|
||||
code: "UNAVAILABLE",
|
||||
});
|
||||
}
|
||||
withApiLogging(
|
||||
async (req: NextApiRequest, res: NextApiResponse<ResponseData>) => {
|
||||
if (process.env.NEXT_PUBLIC_KAN_ENV !== "cloud") {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: "Unsubscribe endpoint is not available.",
|
||||
code: "UNAVAILABLE",
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method !== "POST") {
|
||||
res.setHeader("Allow", "POST");
|
||||
return res.status(405).json({
|
||||
success: false,
|
||||
error: "Method not allowed.",
|
||||
code: "METHOD_NOT_ALLOWED",
|
||||
});
|
||||
}
|
||||
if (req.method !== "POST") {
|
||||
res.setHeader("Allow", "POST");
|
||||
return res.status(405).json({
|
||||
success: false,
|
||||
error: "Method not allowed.",
|
||||
code: "METHOD_NOT_ALLOWED",
|
||||
});
|
||||
}
|
||||
|
||||
const parsedBody = requestSchema.safeParse(req.body);
|
||||
const parsedBody = requestSchema.safeParse(req.body);
|
||||
|
||||
if (!parsedBody.success) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: "Invalid request payload.",
|
||||
code: "BAD_REQUEST",
|
||||
});
|
||||
}
|
||||
if (!parsedBody.success) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: "Invalid request payload.",
|
||||
code: "BAD_REQUEST",
|
||||
});
|
||||
}
|
||||
|
||||
if (!env.EMAIL_UNSUBSCRIBE_SECRET || !env.NOVU_API_KEY) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: "Unsubscribe service is not configured.",
|
||||
code: "NOT_CONFIGURED",
|
||||
});
|
||||
}
|
||||
if (!env.EMAIL_UNSUBSCRIBE_SECRET || !env.NOVU_API_KEY) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: "Unsubscribe service is not configured.",
|
||||
code: "NOT_CONFIGURED",
|
||||
});
|
||||
}
|
||||
|
||||
let payload: z.infer<typeof tokenPayloadSchema>;
|
||||
let payload: z.infer<typeof tokenPayloadSchema>;
|
||||
|
||||
try {
|
||||
const verified = await jwtVerify(
|
||||
parsedBody.data.token,
|
||||
textEncoder.encode(env.EMAIL_UNSUBSCRIBE_SECRET),
|
||||
{
|
||||
// We intentionally do not use exp/iat claims –
|
||||
// tokens are long-lived and validated only by signature + payload.
|
||||
clockTolerance: "0s",
|
||||
},
|
||||
);
|
||||
payload = tokenPayloadSchema.parse(verified.payload);
|
||||
} catch {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: "Your unsubscribe link is invalid or has expired.",
|
||||
code: "INVALID_TOKEN",
|
||||
});
|
||||
}
|
||||
try {
|
||||
const verified = await jwtVerify(
|
||||
parsedBody.data.token,
|
||||
textEncoder.encode(env.EMAIL_UNSUBSCRIBE_SECRET),
|
||||
{
|
||||
// We intentionally do not use exp/iat claims –
|
||||
// tokens are long-lived and validated only by signature + payload.
|
||||
clockTolerance: "0s",
|
||||
},
|
||||
);
|
||||
payload = tokenPayloadSchema.parse(verified.payload);
|
||||
} catch {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: "Your unsubscribe link is invalid or has expired.",
|
||||
code: "INVALID_TOKEN",
|
||||
});
|
||||
}
|
||||
|
||||
const novu = new Novu({ secretKey: env.NOVU_API_KEY });
|
||||
const novu = new Novu({ secretKey: env.NOVU_API_KEY });
|
||||
|
||||
try {
|
||||
await novu.subscribers.preferences.update(
|
||||
{
|
||||
channels: {
|
||||
email: false,
|
||||
},
|
||||
},
|
||||
payload.subscriberId,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to update Novu preferences", error);
|
||||
return res.status(502).json({
|
||||
success: false,
|
||||
error:
|
||||
"We could not update your email preferences right now. Please try again later.",
|
||||
code: "NOVU_ERROR",
|
||||
});
|
||||
}
|
||||
try {
|
||||
await novu.subscribers.preferences.update(
|
||||
{
|
||||
channels: {
|
||||
email: false,
|
||||
},
|
||||
},
|
||||
payload.subscriberId,
|
||||
);
|
||||
} catch (error) {
|
||||
return res.status(502).json({
|
||||
success: false,
|
||||
error:
|
||||
"We could not update your email preferences right now. Please try again later.",
|
||||
code: "NOVU_ERROR",
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).json({ success: true });
|
||||
},
|
||||
return res.status(200).json({ success: true });
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { Upload } from "@aws-sdk/lib-storage";
|
||||
|
||||
import { createNextApiContext } from "@kan/api/trpc";
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { assertPermission } from "@kan/api/utils/permissions";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
import * as cardRepo from "@kan/db/repository/card.repo";
|
||||
@@ -22,7 +23,7 @@ export const config = {
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
@@ -36,7 +37,9 @@ export default withRateLimit(
|
||||
|
||||
const bucket = env.NEXT_PUBLIC_ATTACHMENTS_BUCKET_NAME;
|
||||
if (!bucket) {
|
||||
return res.status(500).json({ error: "Attachments bucket not configured" });
|
||||
return res
|
||||
.status(500)
|
||||
.json({ error: "Attachments bucket not configured" });
|
||||
}
|
||||
|
||||
const cardPublicId = req.query.cardPublicId;
|
||||
@@ -55,7 +58,9 @@ export default withRateLimit(
|
||||
}
|
||||
|
||||
if (!Number.isFinite(contentLength) || contentLength <= 0) {
|
||||
return res.status(400).json({ error: "Missing or invalid content length" });
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Missing or invalid content length" });
|
||||
}
|
||||
|
||||
if (contentLength > MAX_SIZE_BYTES) {
|
||||
@@ -129,8 +134,7 @@ export default withRateLimit(
|
||||
|
||||
return res.status(200).json({ attachment });
|
||||
} catch (error) {
|
||||
console.error("Attachment upload failed", error);
|
||||
return res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -2,13 +2,17 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
|
||||
import { createNextApiContext } from "@kan/api/trpc";
|
||||
import * as userRepo from "@kan/db/repository/user.repo";
|
||||
|
||||
import { env } from "~/env";
|
||||
import { withApiLogging } from "@kan/api/utils/apiLogging";
|
||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||
import * as userRepo from "@kan/db/repository/user.repo";
|
||||
import { createS3Client } from "@kan/shared/utils";
|
||||
|
||||
const MAX_SIZE_BYTES = parseInt(process.env.S3_AVATAR_UPLOAD_LIMIT || '2097152', 10); // Default 2MB
|
||||
import { env } from "~/env";
|
||||
|
||||
const MAX_SIZE_BYTES = parseInt(
|
||||
process.env.S3_AVATAR_UPLOAD_LIMIT || "2097152",
|
||||
10,
|
||||
); // Default 2MB
|
||||
const allowedContentTypes = ["image/jpeg", "image/png", "image/webp"];
|
||||
|
||||
export const config = {
|
||||
@@ -19,7 +23,7 @@ export const config = {
|
||||
|
||||
export default withRateLimit(
|
||||
{ points: 100, duration: 60 },
|
||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
withApiLogging(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
@@ -51,7 +55,9 @@ export default withRateLimit(
|
||||
}
|
||||
|
||||
if (!Number.isFinite(contentLength) || contentLength <= 0) {
|
||||
return res.status(400).json({ error: "Missing or invalid content length" });
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Missing or invalid content length" });
|
||||
}
|
||||
|
||||
if (contentLength > MAX_SIZE_BYTES) {
|
||||
@@ -93,9 +99,7 @@ export default withRateLimit(
|
||||
user: updatedUser,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Avatar upload failed", error);
|
||||
return res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
31
apps/web/src/pages/onboarding/select-plan.tsx
Normal file
31
apps/web/src/pages/onboarding/select-plan.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { env } from "next-runtime-env";
|
||||
|
||||
import { authClient } from "@kan/auth/client";
|
||||
|
||||
import { PageHead } from "~/components/PageHead";
|
||||
import SelectPlanView from "~/views/onboarding/select-plan";
|
||||
|
||||
export default function SelectPlanPage() {
|
||||
const router = useRouter();
|
||||
const { data: session, isPending } = authClient.useSession();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPending && !session?.user) {
|
||||
router.push("/login");
|
||||
}
|
||||
if (!isPending && env("NEXT_PUBLIC_KAN_ENV") !== "cloud") {
|
||||
router.push("/boards");
|
||||
}
|
||||
}, [session, isPending, router]);
|
||||
|
||||
if (isPending || !session?.user) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title="Select plan | kan.bn" />
|
||||
<SelectPlanView />
|
||||
</>
|
||||
);
|
||||
}
|
||||
28
apps/web/src/pages/onboarding/workspace.tsx
Normal file
28
apps/web/src/pages/onboarding/workspace.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { env } from "next-runtime-env";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { authClient } from "@kan/auth/client";
|
||||
|
||||
import { PageHead } from "~/components/PageHead";
|
||||
import WorkspaceDetailsView from "~/views/onboarding/workspace-details";
|
||||
|
||||
export default function WorkspaceDetailsPage() {
|
||||
const router = useRouter();
|
||||
const { data: session, isPending } = authClient.useSession();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPending && !session?.user) router.push("/login");
|
||||
if (!isPending && env("NEXT_PUBLIC_KAN_ENV") !== "cloud")
|
||||
router.push("/boards");
|
||||
}, [session, isPending, router]);
|
||||
|
||||
if (isPending || !session?.user) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title="Create workspace | kan.bn" />
|
||||
<WorkspaceDetailsView />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,24 +1,25 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function TrelloAuthorize() {
|
||||
useEffect(() => {
|
||||
const hash = window.location.hash;
|
||||
const token = hash.split("=")[1];
|
||||
if (token) {
|
||||
console.log("Posting token to /api/trello/authenticate", token);
|
||||
fetch("/api/trello/authenticate", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ token }),
|
||||
}).then(() => {
|
||||
window.close();
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const hash = window.location.hash;
|
||||
const token = hash.split("=")[1];
|
||||
if (token) {
|
||||
fetch("/api/trello/authenticate", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ token }),
|
||||
}).then(() => {
|
||||
window.close();
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return <div className="flex h-[200px] items-center justify-center">
|
||||
<p className="text-center">Connecting to Trello...</p>
|
||||
</div>;
|
||||
}
|
||||
return (
|
||||
<div className="flex h-[200px] items-center justify-center">
|
||||
<p className="text-center">Connecting to Trello...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
57
apps/web/src/providers/font-size.tsx
Normal file
57
apps/web/src/providers/font-size.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
|
||||
export type FontSize = "small" | "medium" | "large";
|
||||
|
||||
const fontSizeMap: Record<FontSize, string> = {
|
||||
small: "14px",
|
||||
medium: "16px",
|
||||
large: "18px",
|
||||
};
|
||||
|
||||
interface FontSizeContextProps {
|
||||
fontSize: FontSize;
|
||||
setFontSize: (size: FontSize) => void;
|
||||
}
|
||||
|
||||
const FontSizeContext = createContext<FontSizeContextProps | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export const FontSizeProvider: React.FC<{ children: ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [fontSize, setFontSizeState] = useState<FontSize>("medium");
|
||||
|
||||
const setFontSize = (size: FontSize) => {
|
||||
document.documentElement.style.fontSize = fontSizeMap[size];
|
||||
localStorage.setItem("fontSize", size);
|
||||
setFontSizeState(size);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem("fontSize") as FontSize | null;
|
||||
const size = stored && fontSizeMap[stored] ? stored : "medium";
|
||||
document.documentElement.style.fontSize = fontSizeMap[size];
|
||||
setFontSizeState(size);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FontSizeContext.Provider value={{ fontSize, setFontSize }}>
|
||||
{children}
|
||||
</FontSizeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useFontSize = (): FontSizeContextProps => {
|
||||
const context = useContext(FontSizeContext);
|
||||
if (!context) {
|
||||
throw new Error("useFontSize must be used within a FontSizeProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -17,7 +17,7 @@ interface Workspace {
|
||||
description: string | null | undefined;
|
||||
publicId: string;
|
||||
slug: string | undefined;
|
||||
plan: "free" | "pro" | "enterprise" | undefined;
|
||||
plan: "free" | "team" | "pro" | "enterprise" | undefined;
|
||||
role: "admin" | "member" | "guest";
|
||||
weekStartDay: 0 | 1 | 6;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ const initialWorkspace: Workspace = {
|
||||
description: null,
|
||||
publicId: "",
|
||||
slug: "",
|
||||
plan: "free",
|
||||
plan: "free" as const,
|
||||
role: "member",
|
||||
weekStartDay: 1,
|
||||
};
|
||||
@@ -50,7 +50,15 @@ export const WorkspaceProvider: React.FC<{ children: ReactNode }> = ({
|
||||
|
||||
const workspacePublicId = useSearchParams().get("workspacePublicId");
|
||||
|
||||
const { data, isLoading } = api.workspace.all.useQuery();
|
||||
const [pendingWorkspaceId, setPendingWorkspaceId] = useState<string | null>(
|
||||
workspacePublicId,
|
||||
);
|
||||
const pollAttemptsRef = React.useRef(0);
|
||||
const MAX_POLL_ATTEMPTS = 5;
|
||||
|
||||
const { data, isLoading } = api.workspace.all.useQuery(undefined, {
|
||||
refetchInterval: pendingWorkspaceId ? 2000 : false,
|
||||
});
|
||||
const utils = api.useUtils();
|
||||
|
||||
const switchWorkspace = (_workspace: Workspace) => {
|
||||
@@ -94,7 +102,16 @@ export const WorkspaceProvider: React.FC<{ children: ReactNode }> = ({
|
||||
({ workspace }) => workspace.publicId === storedWorkspaceId,
|
||||
);
|
||||
|
||||
if (!selectedWorkspace?.workspace) return;
|
||||
if (!selectedWorkspace?.workspace) {
|
||||
pollAttemptsRef.current += 1;
|
||||
if (pollAttemptsRef.current >= MAX_POLL_ATTEMPTS) {
|
||||
setPendingWorkspaceId(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
pollAttemptsRef.current = 0;
|
||||
setPendingWorkspaceId(null);
|
||||
|
||||
setWorkspace({
|
||||
publicId: selectedWorkspace.workspace.publicId,
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
[contenteditable="true"]:empty:before {
|
||||
content: attr(placeholder);
|
||||
display: block;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { env } from "next-runtime-env";
|
||||
|
||||
export const formatToArray = (
|
||||
value: string | string[] | undefined,
|
||||
): string[] => {
|
||||
@@ -50,5 +52,21 @@ export const getAvatarUrl = (imageOrKey: string | null) => {
|
||||
return imageOrKey;
|
||||
}
|
||||
|
||||
// Construct URL from S3 key
|
||||
const useVirtualHosted = env("NEXT_PUBLIC_USE_VIRTUAL_HOSTED_URLS") === "true";
|
||||
const storageDomain = env("NEXT_PUBLIC_STORAGE_DOMAIN");
|
||||
const storageUrl = env("NEXT_PUBLIC_STORAGE_URL");
|
||||
const bucket = env("NEXT_PUBLIC_AVATAR_BUCKET_NAME");
|
||||
|
||||
if (useVirtualHosted && storageDomain && bucket) {
|
||||
// Virtual-hosted style: https://{bucket}.{domain}/{key}
|
||||
return `https://${bucket}.${storageDomain}/${imageOrKey}`;
|
||||
}
|
||||
|
||||
if (storageUrl && bucket) {
|
||||
// Path-style: {storageUrl}/{bucket}/{key}
|
||||
return `${storageUrl}/${bucket}/${imageOrKey}`;
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { t } from "@lingui/core/macro";
|
||||
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { api } from "~/utils/api";
|
||||
import { DueDateSelector } from "~/views/card/components/DueDateSelector";
|
||||
|
||||
export function CardContextDueDateModal() {
|
||||
const { entityId: cardPublicId, closeModal } = useModal();
|
||||
|
||||
const { data: card, isLoading } = api.card.byId.useQuery(
|
||||
{ cardPublicId: cardPublicId ?? "" },
|
||||
{ enabled: !!cardPublicId && cardPublicId.length >= 12 },
|
||||
);
|
||||
|
||||
if (!cardPublicId) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2 className="mb-4 text-lg font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{t`Set due date`}
|
||||
</h2>
|
||||
{isLoading ? (
|
||||
<div className="h-10 w-full animate-pulse rounded bg-light-200 dark:bg-dark-300" />
|
||||
) : (
|
||||
<DueDateSelector
|
||||
cardPublicId={cardPublicId}
|
||||
dueDate={card?.dueDate ?? null}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="rounded-md border border-light-300 bg-light-50 px-3 py-1.5 text-sm font-medium text-light-1000 hover:bg-light-200 dark:border-dark-400 dark:bg-dark-200 dark:text-dark-1000 dark:hover:bg-dark-300"
|
||||
>
|
||||
{t`Done`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxOption,
|
||||
ListboxOptions,
|
||||
Transition,
|
||||
} from "@headlessui/react";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { useState } from "react";
|
||||
import { HiChevronDown } from "react-icons/hi2";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import Input from "~/components/Input";
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
interface CardContextDuplicateModalProps {
|
||||
boardPublicId?: string;
|
||||
isTemplate?: boolean;
|
||||
}
|
||||
|
||||
export function CardContextDuplicateModal({
|
||||
boardPublicId: boardPublicIdProp,
|
||||
isTemplate: isTemplateProp,
|
||||
}: CardContextDuplicateModalProps = {}) {
|
||||
const { entityId: cardPublicId, closeModal, getModalState } = useModal();
|
||||
const { showPopup } = usePopup();
|
||||
const utils = api.useUtils();
|
||||
|
||||
const modalState = getModalState("CARD_CONTEXT_DUPLICATE") as
|
||||
| { boardPublicId: string; isTemplate?: boolean }
|
||||
| undefined;
|
||||
const boardPublicId =
|
||||
boardPublicIdProp ?? modalState?.boardPublicId ?? "";
|
||||
const isTemplate = isTemplateProp ?? modalState?.isTemplate ?? false;
|
||||
|
||||
const [listPublicId, setListPublicId] = useState("");
|
||||
const [copyLabels, setCopyLabels] = useState(true);
|
||||
const [copyMembers, setCopyMembers] = useState(true);
|
||||
const [copyChecklists, setCopyChecklists] = useState(true);
|
||||
const [position, setPosition] = useState<string>("");
|
||||
const [title, setTitle] = useState("");
|
||||
|
||||
const { data: card, isLoading: isCardLoading } = api.card.byId.useQuery(
|
||||
{ cardPublicId: cardPublicId ?? "" },
|
||||
{ enabled: !!cardPublicId && cardPublicId.length >= 12 },
|
||||
);
|
||||
|
||||
const boardType = isTemplate ? "template" : "regular";
|
||||
const { data: board } = api.board.byId.useQuery(
|
||||
{ boardPublicId, type: boardType },
|
||||
{ enabled: !!boardPublicId },
|
||||
);
|
||||
const lists = board?.lists ?? [];
|
||||
const listOptions = lists.map((l) => ({ publicId: l.publicId, name: l.name }));
|
||||
const currentListPublicId = card?.list?.publicId;
|
||||
const hasLabels = (card?.labels?.length ?? 0) > 0;
|
||||
const hasMembers = (card?.members?.length ?? 0) > 0;
|
||||
const hasChecklists = (card?.checklists?.length ?? 0) > 0;
|
||||
const hasAnyCopyOption = hasLabels || hasMembers || hasChecklists;
|
||||
|
||||
const duplicateCard = api.card.duplicate.useMutation({
|
||||
onSuccess: () => {
|
||||
showPopup({
|
||||
header: t`Card duplicated`,
|
||||
icon: "success",
|
||||
message: t`The card has been duplicated.`,
|
||||
});
|
||||
closeModal();
|
||||
},
|
||||
onError: () => {
|
||||
showPopup({
|
||||
header: t`Unable to duplicate card`,
|
||||
message: t`Please try again.`,
|
||||
icon: "error",
|
||||
});
|
||||
},
|
||||
onSettled: async () => {
|
||||
await utils.board.byId.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!cardPublicId || !listPublicId) return;
|
||||
const indexNum =
|
||||
position === "" ? undefined : parseInt(position, 10);
|
||||
if (
|
||||
position !== "" &&
|
||||
(indexNum === undefined || isNaN(indexNum) || indexNum < 0)
|
||||
)
|
||||
return;
|
||||
duplicateCard.mutate({
|
||||
cardPublicId,
|
||||
listPublicId,
|
||||
copyLabels,
|
||||
copyMembers,
|
||||
copyChecklists,
|
||||
...(typeof indexNum === "number" && { index: indexNum }),
|
||||
title: title.trim() || undefined,
|
||||
});
|
||||
};
|
||||
|
||||
if (!cardPublicId) return null;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="p-4">
|
||||
<h2 className="mb-4 text-lg font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{t`Duplicate card`}
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-light-900 dark:text-dark-900">
|
||||
{t`List`}
|
||||
</label>
|
||||
<Listbox value={listPublicId} onChange={setListPublicId}>
|
||||
<div className="relative">
|
||||
<ListboxButton
|
||||
className={twMerge(
|
||||
"relative w-full cursor-pointer rounded-md border border-light-300 bg-white py-2 pl-3 pr-10 text-left text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-light-400 focus:ring-offset-0 dark:border-dark-400 dark:bg-dark-200 dark:text-dark-1000 dark:focus:ring-dark-500",
|
||||
!listPublicId && "text-light-600 dark:text-dark-600",
|
||||
)}
|
||||
>
|
||||
<span className="block truncate">
|
||||
{listPublicId
|
||||
? listOptions.find((o) => o.publicId === listPublicId)
|
||||
?.name
|
||||
: t`Select a list`}
|
||||
</span>
|
||||
<span className="pointer-events-none absolute right-2 top-1/2 flex -translate-y-1/2 items-center">
|
||||
<HiChevronDown
|
||||
className="h-4 w-4 text-light-600 dark:text-dark-600"
|
||||
aria-hidden
|
||||
/>
|
||||
</span>
|
||||
</ListboxButton>
|
||||
<Transition
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<ListboxOptions className="absolute z-50 mt-1 w-full overflow-hidden rounded-md border border-light-200 bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-400 dark:bg-dark-200">
|
||||
<div className="max-h-60 overflow-y-auto py-1 pr-1 scrollbar scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-w-[8px] scrollbar-track-light-200 scrollbar-thumb-light-400 dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-600">
|
||||
{listOptions.map((option) => {
|
||||
const isCurrentList =
|
||||
option.publicId === currentListPublicId;
|
||||
return (
|
||||
<ListboxOption
|
||||
key={option.publicId}
|
||||
value={option.publicId}
|
||||
disabled={isCurrentList}
|
||||
className={({ focus }) =>
|
||||
twMerge(
|
||||
"relative select-none py-2 pl-3 pr-9 text-sm",
|
||||
isCurrentList
|
||||
? "cursor-default opacity-50"
|
||||
: "cursor-pointer",
|
||||
!isCurrentList && focus
|
||||
? "bg-light-200 text-light-1000 dark:bg-dark-400 dark:text-dark-1000"
|
||||
: "text-light-900 dark:text-dark-900",
|
||||
)
|
||||
}
|
||||
>
|
||||
{option.name}
|
||||
</ListboxOption>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ListboxOptions>
|
||||
</Transition>
|
||||
</div>
|
||||
</Listbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-light-900 dark:text-dark-900">
|
||||
{t`Title (optional)`}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder={card?.title ?? ""}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-light-900 dark:text-dark-900">
|
||||
{t`Position in list (0 = first, leave empty for end)`}
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
value={position}
|
||||
onChange={(e) => setPosition(e.target.value)}
|
||||
placeholder={t`End of list`}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hasAnyCopyOption && (
|
||||
<div className="flex flex-col gap-2">
|
||||
{hasLabels && (
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="relative mt-[2px] inline-flex h-[16px] w-[16px] flex-shrink-0 cursor-pointer items-center justify-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={copyLabels}
|
||||
onChange={(e) => setCopyLabels(e.target.checked)}
|
||||
className={twMerge(
|
||||
"h-[16px] w-[16px] appearance-none rounded-md border border-light-500 bg-transparent outline-none ring-0 checked:bg-blue-600 focus:shadow-none focus:ring-0 focus:ring-offset-0 focus-visible:outline-none dark:border-dark-500 dark:hover:border-dark-500",
|
||||
"cursor-pointer",
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
<span className="text-sm text-light-900 dark:text-dark-900">
|
||||
{t`Copy labels`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{hasMembers && (
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="relative mt-[2px] inline-flex h-[16px] w-[16px] flex-shrink-0 cursor-pointer items-center justify-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={copyMembers}
|
||||
onChange={(e) => setCopyMembers(e.target.checked)}
|
||||
className={twMerge(
|
||||
"h-[16px] w-[16px] appearance-none rounded-md border border-light-500 bg-transparent outline-none ring-0 checked:bg-blue-600 focus:shadow-none focus:ring-0 focus:ring-offset-0 focus-visible:outline-none dark:border-dark-500 dark:hover:border-dark-500",
|
||||
"cursor-pointer",
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
<span className="text-sm text-light-900 dark:text-dark-900">
|
||||
{t`Copy members`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{hasChecklists && (
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="relative mt-[2px] inline-flex h-[16px] w-[16px] flex-shrink-0 cursor-pointer items-center justify-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={copyChecklists}
|
||||
onChange={(e) => setCopyChecklists(e.target.checked)}
|
||||
className={twMerge(
|
||||
"h-[16px] w-[16px] appearance-none rounded-md border border-light-500 bg-transparent outline-none ring-0 checked:bg-blue-600 focus:shadow-none focus:ring-0 focus:ring-offset-0 focus-visible:outline-none dark:border-dark-500 dark:hover:border-dark-500",
|
||||
"cursor-pointer",
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
<span className="text-sm text-light-900 dark:text-dark-900">
|
||||
{t`Copy checklists`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-end gap-2">
|
||||
<Button type="button" variant="secondary" onClick={closeModal}>
|
||||
{t`Cancel`}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={
|
||||
!listPublicId ||
|
||||
duplicateCard.isPending ||
|
||||
isCardLoading ||
|
||||
lists.length === 0
|
||||
}
|
||||
>
|
||||
{duplicateCard.isPending ? t`Duplicating…` : t`Duplicate`}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { t } from "@lingui/core/macro";
|
||||
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { api } from "~/utils/api";
|
||||
import LabelIcon from "~/components/LabelIcon";
|
||||
import LabelSelector from "~/views/card/components/LabelSelector";
|
||||
|
||||
export function CardContextLabelsModal() {
|
||||
const { entityId: cardPublicId, closeModal } = useModal();
|
||||
|
||||
const { data: card, isLoading } = api.card.byId.useQuery(
|
||||
{ cardPublicId: cardPublicId ?? "" },
|
||||
{ enabled: !!cardPublicId && cardPublicId.length >= 12 },
|
||||
);
|
||||
|
||||
const boardLabels = card?.list?.board?.labels ?? [];
|
||||
const selectedLabels = card?.labels ?? [];
|
||||
|
||||
const formattedLabels = boardLabels.map((label) => ({
|
||||
key: label.publicId,
|
||||
value: label.name,
|
||||
selected: selectedLabels.some((l) => l.publicId === label.publicId),
|
||||
leftIcon: <LabelIcon colourCode={label.colourCode} />,
|
||||
}));
|
||||
|
||||
if (!cardPublicId) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2 className="mb-4 text-lg font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{t`Labels`}
|
||||
</h2>
|
||||
{isLoading ? (
|
||||
<div className="h-10 w-full animate-pulse rounded bg-light-200 dark:bg-dark-300" />
|
||||
) : (
|
||||
<LabelSelector
|
||||
cardPublicId={cardPublicId}
|
||||
labels={formattedLabels}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="rounded-md border border-light-300 bg-light-50 px-3 py-1.5 text-sm font-medium text-light-1000 hover:bg-light-200 dark:border-dark-400 dark:bg-dark-200 dark:text-dark-1000 dark:hover:bg-dark-300"
|
||||
>
|
||||
{t`Done`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { t } from "@lingui/core/macro";
|
||||
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { api } from "~/utils/api";
|
||||
import { formatMemberDisplayName, getAvatarUrl } from "~/utils/helpers";
|
||||
import Avatar from "~/components/Avatar";
|
||||
import MemberSelector from "~/views/card/components/MemberSelector";
|
||||
|
||||
export function CardContextMembersModal() {
|
||||
const { entityId: cardPublicId, closeModal } = useModal();
|
||||
|
||||
const { data: card, isLoading } = api.card.byId.useQuery(
|
||||
{ cardPublicId: cardPublicId ?? "" },
|
||||
{ enabled: !!cardPublicId && cardPublicId.length >= 12 },
|
||||
);
|
||||
|
||||
const board = card?.list?.board;
|
||||
const workspaceMembers = board?.workspace?.members ?? [];
|
||||
const selectedMembers = card?.members ?? [];
|
||||
|
||||
const formattedMembers = workspaceMembers.map((member) => {
|
||||
const isSelected = selectedMembers.some(
|
||||
(m) => m.publicId === member.publicId,
|
||||
);
|
||||
return {
|
||||
key: member.publicId,
|
||||
value: formatMemberDisplayName(
|
||||
member.user?.name ?? null,
|
||||
member.user?.email ?? member.email,
|
||||
),
|
||||
imageUrl: member.user?.image ? getAvatarUrl(member.user.image) : undefined,
|
||||
selected: isSelected,
|
||||
leftIcon: (
|
||||
<Avatar
|
||||
size="xs"
|
||||
name={member.user?.name ?? ""}
|
||||
imageUrl={
|
||||
member.user?.image ? getAvatarUrl(member.user.image) : undefined
|
||||
}
|
||||
email={member.user?.email ?? member.email}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
if (!cardPublicId) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2 className="mb-4 text-lg font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{t`Manage members`}
|
||||
</h2>
|
||||
{isLoading ? (
|
||||
<div className="h-10 w-full animate-pulse rounded bg-light-200 dark:bg-dark-300" />
|
||||
) : (
|
||||
<MemberSelector
|
||||
cardPublicId={cardPublicId}
|
||||
members={formattedMembers}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="rounded-md border border-light-300 bg-light-50 px-3 py-1.5 text-sm font-medium text-light-1000 hover:bg-light-200 dark:border-dark-400 dark:bg-dark-200 dark:text-dark-1000 dark:hover:bg-dark-300"
|
||||
>
|
||||
{t`Done`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
130
apps/web/src/views/board/components/CardContextMenu.tsx
Normal file
130
apps/web/src/views/board/components/CardContextMenu.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { useEffect, useRef } from "react";
|
||||
import {
|
||||
HiLink,
|
||||
HiOutlineCalendar,
|
||||
HiOutlineDocumentDuplicate,
|
||||
HiOutlineTag,
|
||||
HiOutlineTrash,
|
||||
HiOutlineUserGroup,
|
||||
HiOutlineArrowRightCircle,
|
||||
} from "react-icons/hi2";
|
||||
|
||||
export type CardContextMenuAction =
|
||||
| "members"
|
||||
| "move"
|
||||
| "labels"
|
||||
| "dueDate"
|
||||
| "copyLink"
|
||||
| "duplicate"
|
||||
| "delete";
|
||||
|
||||
interface CardContextMenuProps {
|
||||
x: number;
|
||||
y: number;
|
||||
onClose: () => void;
|
||||
onAction: (action: CardContextMenuAction) => void;
|
||||
canEdit: boolean;
|
||||
}
|
||||
|
||||
const MENU_ITEMS: {
|
||||
action: CardContextMenuAction;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
requiresEdit: boolean;
|
||||
}[] = [
|
||||
{
|
||||
action: "members",
|
||||
label: t`Manage members`,
|
||||
icon: <HiOutlineUserGroup className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: true,
|
||||
},
|
||||
{
|
||||
action: "move",
|
||||
label: t`Move to another list`,
|
||||
icon: <HiOutlineArrowRightCircle className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: true,
|
||||
},
|
||||
{
|
||||
action: "labels",
|
||||
label: t`Add / edit label`,
|
||||
icon: <HiOutlineTag className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: true,
|
||||
},
|
||||
{
|
||||
action: "dueDate",
|
||||
label: t`Set due date`,
|
||||
icon: <HiOutlineCalendar className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: true,
|
||||
},
|
||||
{
|
||||
action: "copyLink",
|
||||
label: t`Copy link to card`,
|
||||
icon: <HiLink className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: false,
|
||||
},
|
||||
{
|
||||
action: "duplicate",
|
||||
label: t`Duplicate card`,
|
||||
icon: <HiOutlineDocumentDuplicate className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: true,
|
||||
},
|
||||
{
|
||||
action: "delete",
|
||||
label: t`Delete card`,
|
||||
icon: <HiOutlineTrash className="h-4 w-4 shrink-0" />,
|
||||
requiresEdit: true,
|
||||
},
|
||||
];
|
||||
|
||||
export function CardContextMenu({
|
||||
x,
|
||||
y,
|
||||
onClose,
|
||||
onAction,
|
||||
canEdit,
|
||||
}: CardContextMenuProps) {
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
document.removeEventListener("keydown", handleEscape);
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
const items = MENU_ITEMS.filter((item) => !item.requiresEdit || canEdit);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="fixed z-[200] min-w-[200px] rounded-md border border-light-200 bg-white py-1 shadow-lg dark:border-dark-400 dark:bg-dark-200"
|
||||
style={{ left: x, top: y }}
|
||||
>
|
||||
{items.map(({ action, label, icon }) => (
|
||||
<button
|
||||
key={action}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onAction(action);
|
||||
onClose();
|
||||
}}
|
||||
className="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-neutral-900 hover:bg-light-200 dark:text-dark-1000 dark:hover:bg-dark-400"
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
101
apps/web/src/views/board/components/CardContextMoveListModal.tsx
Normal file
101
apps/web/src/views/board/components/CardContextMoveListModal.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { t } from "@lingui/core/macro";
|
||||
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
import { invalidateCard } from "~/utils/cardInvalidation";
|
||||
|
||||
export function CardContextMoveListModal() {
|
||||
const { entityId: cardPublicId, closeModal } = useModal();
|
||||
const { showPopup } = usePopup();
|
||||
const utils = api.useUtils();
|
||||
|
||||
const { data: card, isLoading } = api.card.byId.useQuery(
|
||||
{ cardPublicId: cardPublicId ?? "" },
|
||||
{ enabled: !!cardPublicId && cardPublicId.length >= 12 },
|
||||
);
|
||||
|
||||
const updateCardList = api.card.update.useMutation({
|
||||
onMutate: async (vars) => {
|
||||
if (!cardPublicId) return undefined;
|
||||
await utils.card.byId.cancel();
|
||||
const previous = utils.card.byId.getData({ cardPublicId });
|
||||
utils.card.byId.setData({ cardPublicId }, (old) => {
|
||||
if (!old) return old;
|
||||
const list = old.list.board?.lists?.find(
|
||||
(l) => l.publicId === vars.listPublicId,
|
||||
);
|
||||
if (!list) return old;
|
||||
return {
|
||||
...old,
|
||||
list: { ...old.list, publicId: list.publicId, name: list.name },
|
||||
};
|
||||
});
|
||||
return { previous };
|
||||
},
|
||||
onError: (_err, _vars, ctx) => {
|
||||
if (cardPublicId && ctx?.previous) {
|
||||
utils.card.byId.setData({ cardPublicId }, ctx.previous);
|
||||
}
|
||||
showPopup({
|
||||
header: t`Unable to move card`,
|
||||
message: t`Please try again.`,
|
||||
icon: "error",
|
||||
});
|
||||
},
|
||||
onSettled: async () => {
|
||||
if (cardPublicId) {
|
||||
await invalidateCard(utils, cardPublicId);
|
||||
await utils.board.byId.invalidate();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const lists = card?.list?.board?.lists ?? [];
|
||||
const currentListPublicId = card?.list?.publicId;
|
||||
|
||||
const handleSelectList = (listPublicId: string) => {
|
||||
if (listPublicId === currentListPublicId || !cardPublicId) return;
|
||||
updateCardList.mutate(
|
||||
{ cardPublicId, listPublicId, index: 0 },
|
||||
{ onSuccess: closeModal },
|
||||
);
|
||||
};
|
||||
|
||||
if (!cardPublicId) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2 className="mb-4 text-lg font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{t`Move to list`}
|
||||
</h2>
|
||||
{isLoading ? (
|
||||
<div className="space-y-2">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="h-10 w-full animate-pulse rounded bg-light-200 dark:bg-dark-300"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-h-[60vh] overflow-y-auto pr-1 scrollbar scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-w-[8px] scrollbar-track-light-200 scrollbar-thumb-light-400 dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-600">
|
||||
<ul className="space-y-1">
|
||||
{lists.map((list) => (
|
||||
<li key={list.publicId}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSelectList(list.publicId)}
|
||||
disabled={list.publicId === currentListPublicId}
|
||||
className="w-full rounded-md px-3 py-2 text-left text-sm hover:bg-light-200 disabled:opacity-50 dark:hover:bg-dark-400"
|
||||
>
|
||||
{list.name}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
|
||||
import type { UpdateBoardInput } from "@kan/api/types";
|
||||
|
||||
import type { CardContextMenuAction } from "./components/CardContextMenu";
|
||||
import Button from "~/components/Button";
|
||||
import { DeleteLabelConfirmation } from "~/components/DeleteLabelConfirmation";
|
||||
import { LabelForm } from "~/components/LabelForm";
|
||||
@@ -26,16 +27,23 @@ import { StrictModeDroppable as Droppable } from "~/components/StrictModeDroppab
|
||||
import { Tooltip } from "~/components/Tooltip";
|
||||
import { EditYouTubeModal } from "~/components/YouTubeEmbed/EditYouTubeModal";
|
||||
import { useDragToScroll } from "~/hooks/useDragToScroll";
|
||||
import { useScrollRestore } from "~/hooks/useScrollRestore";
|
||||
import { usePermissions } from "~/hooks/usePermissions";
|
||||
import { useScrollRestore } from "~/hooks/useScrollRestore";
|
||||
import { useKeyboardShortcut } from "~/providers/keyboard-shortcuts";
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { useWorkspace } from "~/providers/workspace";
|
||||
import { api } from "~/utils/api";
|
||||
import { formatToArray } from "~/utils/helpers";
|
||||
import { DeleteCardConfirmation } from "~/views/card/components/DeleteCardConfirmation";
|
||||
import BoardDropdown from "./components/BoardDropdown";
|
||||
import Card from "./components/Card";
|
||||
import { CardContextDueDateModal } from "./components/CardContextDueDateModal";
|
||||
import { CardContextDuplicateModal } from "./components/CardContextDuplicateModal";
|
||||
import { CardContextLabelsModal } from "./components/CardContextLabelsModal";
|
||||
import { CardContextMembersModal } from "./components/CardContextMembersModal";
|
||||
import { CardContextMenu } from "./components/CardContextMenu";
|
||||
import { CardContextMoveListModal } from "./components/CardContextMoveListModal";
|
||||
import { DeleteBoardConfirmation } from "./components/DeleteBoardConfirmation";
|
||||
import { DeleteListConfirmation } from "./components/DeleteListConfirmation";
|
||||
import Filters from "./components/Filters";
|
||||
@@ -55,11 +63,18 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
const utils = api.useUtils();
|
||||
const { showPopup } = usePopup();
|
||||
const { workspace } = useWorkspace();
|
||||
const { openModal, modalContentType, entityId, isOpen } = useModal();
|
||||
const { openModal, modalContentType, entityId, isOpen, setModalState } =
|
||||
useModal();
|
||||
const [selectedPublicListId, setSelectedPublicListId] =
|
||||
useState<PublicListId>("");
|
||||
const [isInitialLoading, setIsInitialLoading] = useState(true);
|
||||
|
||||
const [contextMenu, setContextMenu] = useState<{
|
||||
x: number;
|
||||
y: number;
|
||||
cardPublicId: string;
|
||||
} | null>(null);
|
||||
|
||||
const { ref: scrollRef, onMouseDown } = useDragToScroll({
|
||||
enabled: true,
|
||||
direction: "horizontal",
|
||||
@@ -134,7 +149,10 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
// Redirect to 404 if board doesn't exist
|
||||
useEffect(() => {
|
||||
if (router.isReady && boardId && !isQueryLoading) {
|
||||
if (error?.data?.code === "NOT_FOUND" || (!boardData && !isQueryLoading)) {
|
||||
if (
|
||||
error?.data?.code === "NOT_FOUND" ||
|
||||
(!boardData && !isQueryLoading)
|
||||
) {
|
||||
router.replace("/404");
|
||||
}
|
||||
}
|
||||
@@ -152,7 +170,12 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
|
||||
const isLoading = isInitialLoading || isQueryLoading;
|
||||
|
||||
useScrollRestore(boardId, scrollRef, router, !isLoading && (boardData?.lists.length ?? 0) > 0);
|
||||
useScrollRestore(
|
||||
boardId,
|
||||
scrollRef,
|
||||
router,
|
||||
!isLoading && (boardData?.lists.length ?? 0) > 0,
|
||||
);
|
||||
|
||||
const updateListMutation = api.list.update.useMutation({
|
||||
onMutate: async (args) => {
|
||||
@@ -267,6 +290,56 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
setSelectedPublicListId(publicBoardId);
|
||||
};
|
||||
|
||||
const handleCardContextMenuAction = (action: CardContextMenuAction) => {
|
||||
const cardPublicId = contextMenu?.cardPublicId;
|
||||
if (!cardPublicId) return;
|
||||
setContextMenu(null);
|
||||
if (action === "copyLink") {
|
||||
const path = isTemplate
|
||||
? `/templates/${boardId}/cards/${cardPublicId}`
|
||||
: `/cards/${cardPublicId}`;
|
||||
const url = `${typeof window !== "undefined" ? window.location.origin : ""}${path}`;
|
||||
void navigator.clipboard.writeText(url).then(
|
||||
() => {
|
||||
showPopup({
|
||||
header: t`Link copied`,
|
||||
icon: "success",
|
||||
message: t`Card URL copied to clipboard`,
|
||||
});
|
||||
},
|
||||
() => {
|
||||
showPopup({
|
||||
header: t`Unable to copy link`,
|
||||
icon: "error",
|
||||
message: t`Please try again.`,
|
||||
});
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (action === "duplicate") {
|
||||
setModalState("CARD_CONTEXT_DUPLICATE", {
|
||||
boardPublicId: boardId ?? "",
|
||||
isTemplate: !!isTemplate,
|
||||
});
|
||||
openModal("CARD_CONTEXT_DUPLICATE", cardPublicId);
|
||||
return;
|
||||
}
|
||||
if (action === "delete") {
|
||||
openModal("DELETE_CARD", cardPublicId);
|
||||
return;
|
||||
}
|
||||
const modalType =
|
||||
action === "members"
|
||||
? "CARD_CONTEXT_MEMBERS"
|
||||
: action === "move"
|
||||
? "CARD_CONTEXT_MOVE_LIST"
|
||||
: action === "labels"
|
||||
? "CARD_CONTEXT_LABELS"
|
||||
: "CARD_CONTEXT_DUE_DATE";
|
||||
openModal(modalType, cardPublicId);
|
||||
};
|
||||
|
||||
const onDragEnd = ({
|
||||
source: _source,
|
||||
destination,
|
||||
@@ -403,6 +476,49 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
>
|
||||
<EditYouTubeModal />
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
modalSize="sm"
|
||||
isVisible={isOpen && modalContentType === "CARD_CONTEXT_MEMBERS"}
|
||||
>
|
||||
<CardContextMembersModal />
|
||||
</Modal>
|
||||
<Modal
|
||||
modalSize="sm"
|
||||
isVisible={isOpen && modalContentType === "CARD_CONTEXT_MOVE_LIST"}
|
||||
>
|
||||
<CardContextMoveListModal />
|
||||
</Modal>
|
||||
<Modal
|
||||
modalSize="sm"
|
||||
isVisible={isOpen && modalContentType === "CARD_CONTEXT_LABELS"}
|
||||
>
|
||||
<CardContextLabelsModal />
|
||||
</Modal>
|
||||
<Modal
|
||||
modalSize="sm"
|
||||
isVisible={isOpen && modalContentType === "CARD_CONTEXT_DUE_DATE"}
|
||||
>
|
||||
<CardContextDueDateModal />
|
||||
</Modal>
|
||||
<Modal
|
||||
modalSize="md"
|
||||
isVisible={isOpen && modalContentType === "CARD_CONTEXT_DUPLICATE"}
|
||||
>
|
||||
<CardContextDuplicateModal
|
||||
boardPublicId={boardId ?? ""}
|
||||
isTemplate={!!isTemplate}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
modalSize="sm"
|
||||
isVisible={isOpen && modalContentType === "DELETE_CARD"}
|
||||
>
|
||||
<DeleteCardConfirmation
|
||||
cardPublicId={entityId}
|
||||
boardPublicId={boardId ?? ""}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -605,18 +721,33 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
)
|
||||
e.preventDefault();
|
||||
}}
|
||||
onContextMenu={(e) => {
|
||||
if (
|
||||
card.publicId.startsWith(
|
||||
"PLACEHOLDER",
|
||||
)
|
||||
)
|
||||
return;
|
||||
e.preventDefault();
|
||||
setContextMenu({
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
cardPublicId: card.publicId,
|
||||
});
|
||||
}}
|
||||
key={card.publicId}
|
||||
href={
|
||||
isTemplate
|
||||
? `/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}
|
||||
@@ -653,6 +784,15 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
{contextMenu && (
|
||||
<CardContextMenu
|
||||
x={contextMenu.x}
|
||||
y={contextMenu.y}
|
||||
onClose={() => setContextMenu(null)}
|
||||
onAction={handleCardContextMenuAction}
|
||||
canEdit={!!canEditCard}
|
||||
/>
|
||||
)}
|
||||
{renderModalContent()}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -234,7 +234,7 @@ const ImportGithub: React.FC = () => {
|
||||
await refetchBoards();
|
||||
closeModal();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
@@ -386,7 +386,7 @@ const ImportTrello: React.FC = () => {
|
||||
await refetchBoards();
|
||||
closeModal();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { DraggableProvided } from "react-beautiful-dnd";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { useEffect, useState } from "react";
|
||||
import ContentEditable from "react-contenteditable";
|
||||
import { useState } from "react";
|
||||
import { HiXMark } from "react-icons/hi2";
|
||||
import { RiDraggable } from "react-icons/ri";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import PlainTextEditor from "~/components/PlainTextEditor";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
import { invalidateCard } from "~/utils/cardInvalidation";
|
||||
@@ -33,9 +33,7 @@ export default function ChecklistItemRow({
|
||||
}: ChecklistItemRowProps) {
|
||||
const utils = api.useUtils();
|
||||
const { showPopup } = usePopup();
|
||||
|
||||
const [title, setTitle] = useState("");
|
||||
const [completed, setCompleted] = useState(false);
|
||||
const [completed, setCompleted] = useState(item.completed);
|
||||
|
||||
const updateItem = api.checklist.updateItem.useMutation({
|
||||
onMutate: async (vars) => {
|
||||
@@ -103,21 +101,6 @@ export default function ChecklistItemRow({
|
||||
},
|
||||
});
|
||||
|
||||
// Only resync from props when switching items to avoid clobbering edits
|
||||
useEffect(() => {
|
||||
setTitle(item.title);
|
||||
setCompleted(item.completed);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item.publicId]);
|
||||
|
||||
const sanitizeHtmlToPlainText = (html: string): string =>
|
||||
html
|
||||
.replace(/<br\s*\/?>(\n)?/gi, "\n")
|
||||
.replace(/<div><br\s*\/?><\/div>/gi, "")
|
||||
.replace(/<[^>]*>/g, "")
|
||||
.replace(/ /g, " ")
|
||||
.trim();
|
||||
|
||||
const handleToggleCompleted = () => {
|
||||
if (viewOnly) return;
|
||||
setCompleted((prev) => !prev);
|
||||
@@ -127,14 +110,8 @@ export default function ChecklistItemRow({
|
||||
});
|
||||
};
|
||||
|
||||
const commitTitle = (rawHtml: string) => {
|
||||
if (viewOnly) return;
|
||||
const plain = sanitizeHtmlToPlainText(rawHtml);
|
||||
if (!plain || plain === item.title) {
|
||||
setTitle(item.title);
|
||||
return;
|
||||
}
|
||||
setTitle(plain);
|
||||
const commitTitle = (plain: string) => {
|
||||
if (!plain || plain === item.title) return;
|
||||
updateItem.mutate({
|
||||
checklistItemPublicId: item.publicId,
|
||||
title: plain,
|
||||
@@ -183,36 +160,26 @@ export default function ChecklistItemRow({
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="flex-1 pr-7">
|
||||
<ContentEditable
|
||||
html={title}
|
||||
disabled={viewOnly}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
// @ts-expect-error - valid event
|
||||
onBlur={(e: Event) => {
|
||||
const innerHTML = (e.target as HTMLElement).innerHTML;
|
||||
commitTitle(innerHTML);
|
||||
<PlainTextEditor
|
||||
key={item.publicId}
|
||||
content={item.title}
|
||||
readOnly={viewOnly}
|
||||
placeholder={t`Add details...`}
|
||||
onBlur={commitTitle}
|
||||
onEnter={(plain) => {
|
||||
commitTitle(plain);
|
||||
onCreateNewItem?.();
|
||||
}}
|
||||
onEscape={() => undefined}
|
||||
className={twMerge(
|
||||
"m-0 min-h-[20px] w-full p-0 text-sm leading-[20px] text-light-950 outline-none focus-visible:outline-none dark:text-dark-950",
|
||||
"m-0 min-h-[20px] w-full p-0 text-sm leading-[20px] text-light-950 dark:text-dark-950",
|
||||
viewOnly && "cursor-default",
|
||||
)}
|
||||
placeholder={t`Add details...`}
|
||||
onKeyDown={(e) => {
|
||||
if (viewOnly) return;
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const innerHTML = (e.currentTarget as HTMLElement).innerHTML;
|
||||
commitTitle(innerHTML);
|
||||
onCreateNewItem?.();
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
setTitle(item.title);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!viewOnly && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -78,6 +78,7 @@ export default function LabelSelector({
|
||||
},
|
||||
onSettled: async () => {
|
||||
await invalidateCard(utils, cardPublicId);
|
||||
await utils.board.byId.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ export default function ListSelector({
|
||||
},
|
||||
onSettled: async () => {
|
||||
await invalidateCard(utils, cardPublicId);
|
||||
await utils.board.byId.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ export default function MemberSelector({
|
||||
},
|
||||
onSettled: async () => {
|
||||
await invalidateCard(utils, cardPublicId);
|
||||
await utils.board.byId.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
289
apps/web/src/views/onboarding/select-plan/index.tsx
Normal file
289
apps/web/src/views/onboarding/select-plan/index.tsx
Normal file
@@ -0,0 +1,289 @@
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { Radio, RadioGroup } from "@headlessui/react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
import { HiUser } from "react-icons/hi2";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { authClient } from "@kan/auth/client";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
type PlanId = "solo" | "team" | "pro";
|
||||
type Billing = "monthly" | "annual";
|
||||
|
||||
// Each user orbits via a zero-size pivot div at center (144,144) that rotates.
|
||||
// The icon is offset from the pivot by `radius` pixels along the x-axis.
|
||||
// A counter-rotation on the icon keeps it upright.
|
||||
const ORBIT_USERS: Record<
|
||||
PlanId,
|
||||
{ id: string; angle: number; radius: number; duration: number }[]
|
||||
> = {
|
||||
solo: [],
|
||||
team: [
|
||||
{ id: "t1", angle: 45, radius: 104, duration: 50 },
|
||||
{ id: "t2", angle: 170, radius: 104, duration: 50 },
|
||||
{ id: "t3", angle: 290, radius: 104, duration: 50 },
|
||||
],
|
||||
pro: [
|
||||
{ id: "t1", angle: 45, radius: 104, duration: 50 },
|
||||
{ id: "t2", angle: 170, radius: 104, duration: 50 },
|
||||
{ id: "t3", angle: 290, radius: 104, duration: 50 },
|
||||
{ id: "p1", angle: 10, radius: 144, duration: 70 },
|
||||
{ id: "p2", angle: 120, radius: 144, duration: 70 },
|
||||
{ id: "p4", angle: 240, radius: 144, duration: 70 },
|
||||
{ id: "p3", angle: 230, radius: 64, duration: 30 },
|
||||
],
|
||||
};
|
||||
|
||||
export default function SelectPlanView() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [selected, setSelected] = useState<PlanId>(
|
||||
(searchParams.get("plan") as PlanId | null) ?? "solo",
|
||||
);
|
||||
const [billing, setBilling] = useState<Billing>(
|
||||
(searchParams.get("billing") as Billing | null) ?? "annual",
|
||||
);
|
||||
const returnUrl = searchParams.get("returnUrl") ?? "/boards";
|
||||
const { data: workspaces } = api.workspace.all.useQuery();
|
||||
const { data: session } = authClient.useSession();
|
||||
const { data: user } = api.user.getUser.useQuery(undefined, {
|
||||
enabled: !!session?.user,
|
||||
});
|
||||
|
||||
const userImage = user?.image ?? session?.user.image ?? null;
|
||||
|
||||
const hasExistingWorkspace = !!workspaces?.length;
|
||||
|
||||
const FREQUENCIES: { value: Billing; label: string }[] = [
|
||||
{ value: "monthly", label: t`Monthly` },
|
||||
{ value: "annual", label: t`Annual` },
|
||||
];
|
||||
|
||||
const PLANS: {
|
||||
id: PlanId;
|
||||
name: string;
|
||||
monthly: string;
|
||||
annual: string;
|
||||
description: string;
|
||||
trial?: boolean;
|
||||
}[] = [
|
||||
{
|
||||
id: "solo",
|
||||
name: t`Solo`,
|
||||
monthly: t`Free`,
|
||||
annual: t`Free`,
|
||||
description: t`Good for individuals starting out who just need the essentials.`,
|
||||
},
|
||||
{
|
||||
id: "team",
|
||||
name: t`Team`,
|
||||
monthly: "$10/user/mo",
|
||||
annual: "$8/user/mo",
|
||||
description: t`Best for small teams who want to collaborate and move faster together.`,
|
||||
trial: true,
|
||||
},
|
||||
{
|
||||
id: "pro",
|
||||
name: t`Pro`,
|
||||
monthly: "$29/mo",
|
||||
annual: "$23/mo",
|
||||
description: t`Unlimited members and a custom workspace username for teams ready to scale.`,
|
||||
trial: true,
|
||||
},
|
||||
];
|
||||
|
||||
const handleSelectPlan = (plan: PlanId) => {
|
||||
setSelected(plan);
|
||||
router.replace(`/onboarding/select-plan?plan=${plan}&billing=${billing}&returnUrl=${encodeURIComponent(returnUrl)}`);
|
||||
};
|
||||
|
||||
const handleSetBilling = (b: Billing) => {
|
||||
setBilling(b);
|
||||
router.replace(`/onboarding/select-plan?plan=${selected}&billing=${b}&returnUrl=${encodeURIComponent(returnUrl)}`);
|
||||
};
|
||||
|
||||
const handleContinue = () =>
|
||||
router.push(`/onboarding/workspace?plan=${selected}&billing=${billing}&returnUrl=${encodeURIComponent(returnUrl)}`);
|
||||
|
||||
const handleCancel = () => router.push(returnUrl);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center gap-4 bg-light-100 px-4 py-8 dark:bg-dark-50 md:px-6">
|
||||
<div className="w-full max-w-3xl overflow-hidden rounded-xl border border-light-400 bg-light-200 shadow-xl dark:border-dark-400 dark:bg-dark-100">
|
||||
<div className="flex flex-col md:h-[520px] md:flex-row">
|
||||
{/* Left panel */}
|
||||
<div className="flex flex-col p-6 md:w-[55%] md:p-8">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-xl font-bold text-light-1000 dark:text-dark-1000">
|
||||
{t`Choose a plan`}
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-light-800 dark:text-dark-800">
|
||||
{t`Pick a plan to get started. All paid plans include a 14-day free trial.`}
|
||||
</p>
|
||||
|
||||
{/* Billing toggle */}
|
||||
<div className="mt-4 flex justify-end">
|
||||
<RadioGroup
|
||||
value={billing}
|
||||
onChange={handleSetBilling}
|
||||
className="grid grid-cols-2 gap-x-1 rounded-full p-1 text-center text-xs font-semibold ring-1 ring-inset ring-light-600 dark:ring-dark-600"
|
||||
>
|
||||
{FREQUENCIES.map((f) => (
|
||||
<Radio
|
||||
key={f.value}
|
||||
value={f.value}
|
||||
className={twMerge(
|
||||
"cursor-pointer rounded-full px-2.5 py-0.5 text-xs transition-colors",
|
||||
billing === f.value
|
||||
? "bg-dark-50 text-white dark:bg-light-50 dark:text-dark-50"
|
||||
: "text-light-900 hover:bg-light-200 dark:text-dark-900 dark:hover:bg-dark-200",
|
||||
)}
|
||||
>
|
||||
{f.label}
|
||||
</Radio>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 space-y-3">
|
||||
{PLANS.map((plan) => {
|
||||
const badge =
|
||||
billing === "annual" ? plan.annual : plan.monthly;
|
||||
return (
|
||||
<button
|
||||
key={plan.id}
|
||||
type="button"
|
||||
onClick={() => handleSelectPlan(plan.id)}
|
||||
className={`relative w-full rounded-lg border px-4 py-3 text-left transition-colors ${
|
||||
selected === plan.id
|
||||
? "border-light-700 bg-light-300 dark:border-dark-600 dark:bg-dark-200"
|
||||
: "border-light-500 bg-light-200 hover:border-light-600 dark:border-dark-500 dark:bg-dark-100 dark:hover:border-dark-600"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1 pr-8">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{plan.name}
|
||||
</span>
|
||||
<span className="rounded-full bg-neutral-700 px-2 py-px text-[11px] font-medium text-neutral-200">
|
||||
{badge}
|
||||
</span>
|
||||
{plan.trial && billing === "annual" && (
|
||||
<span className="rounded-full bg-emerald-500/10 px-2 py-px text-[11px] font-medium text-emerald-600 ring-1 ring-inset ring-emerald-500/20 dark:text-emerald-400">
|
||||
-20%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-light-800 dark:text-dark-800">
|
||||
{plan.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-0.5 flex-shrink-0">
|
||||
<div
|
||||
className={`flex h-4 w-4 items-center justify-center rounded-full border-2 ${
|
||||
selected === plan.id
|
||||
? "border-light-900 bg-light-900 dark:border-dark-900 dark:bg-dark-900"
|
||||
: "border-light-700 dark:border-dark-700"
|
||||
}`}
|
||||
>
|
||||
{selected === plan.id && (
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-light-100 dark:bg-dark-100" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-end gap-2 md:mt-8">
|
||||
{hasExistingWorkspace && (
|
||||
<Button variant="ghost" onClick={handleCancel}>
|
||||
{t`Cancel`}
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={handleContinue}>{t`Continue`}</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right panel */}
|
||||
<div className="hidden items-center justify-center bg-light-200 dark:bg-dark-200 md:flex md:w-[45%]">
|
||||
<div className="relative h-72 w-72">
|
||||
{/* Orbit rings */}
|
||||
<div className="absolute inset-0 m-auto h-72 w-72 rounded-full border border-light-400 dark:border-dark-400" />
|
||||
<div className="absolute inset-0 m-auto h-52 w-52 rounded-full border border-light-400 dark:border-dark-400" />
|
||||
<div className="absolute inset-0 m-auto h-32 w-32 rounded-full border border-light-400 dark:border-dark-400" />
|
||||
|
||||
{/* Centre person */}
|
||||
<div className="absolute inset-0 m-auto flex h-16 w-16 items-center justify-center overflow-hidden rounded-full bg-light-300 dark:bg-dark-300">
|
||||
{userImage ? (
|
||||
<img
|
||||
src={userImage}
|
||||
alt={t`Your avatar`}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<HiUser className="h-7 w-7 text-light-700 dark:text-dark-700" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Orbiting users — zero-size pivot at center rotates; icon offset by radius stays on the ring */}
|
||||
<AnimatePresence>
|
||||
{ORBIT_USERS[selected].map((u) => (
|
||||
<motion.div
|
||||
key={u.id}
|
||||
style={{ position: "absolute", left: 144, top: 144 }}
|
||||
initial={{ opacity: 0, rotate: u.angle }}
|
||||
animate={{ opacity: 1, rotate: u.angle + 360 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{
|
||||
opacity: { duration: 0.3 },
|
||||
rotate: {
|
||||
duration: u.duration,
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
repeatType: "loop",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Counter-rotate icon so it stays upright */}
|
||||
<motion.div
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: u.radius - 12,
|
||||
top: -12,
|
||||
}}
|
||||
initial={{ rotate: -u.angle }}
|
||||
animate={{ rotate: -(u.angle + 360) }}
|
||||
transition={{
|
||||
duration: u.duration,
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
repeatType: "loop",
|
||||
}}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-full bg-light-400 dark:bg-dark-400"
|
||||
>
|
||||
<HiUser className="h-3.5 w-3.5 text-light-800 dark:text-dark-800" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!hasExistingWorkspace && (
|
||||
<Button variant="ghost" onClick={() => authClient.signOut()}>
|
||||
{t`Sign out`}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
367
apps/web/src/views/onboarding/workspace-details/index.tsx
Normal file
367
apps/web/src/views/onboarding/workspace-details/index.tsx
Normal file
@@ -0,0 +1,367 @@
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { motion } from "framer-motion";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
HiArrowLeft,
|
||||
HiArrowPath,
|
||||
HiArrowRight,
|
||||
HiCheck,
|
||||
HiEllipsisVertical,
|
||||
HiInformationCircle,
|
||||
HiLockClosed,
|
||||
} from "react-icons/hi2";
|
||||
|
||||
import { authClient } from "@kan/auth/client";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import Input from "~/components/Input";
|
||||
import LoadingSpinner from "~/components/LoadingSpinner";
|
||||
import Toggle from "~/components/Toggle";
|
||||
import { Tooltip } from "~/components/Tooltip";
|
||||
import { useDebounce } from "~/hooks/useDebounce";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
function slugify(value: string) {
|
||||
return value
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9\s-]/g, "")
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/-+/g, "-")
|
||||
.slice(0, 60);
|
||||
}
|
||||
|
||||
export default function WorkspaceNameView() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const plan = searchParams.get("plan") ?? "solo";
|
||||
const billing = searchParams.get("billing") ?? "annual";
|
||||
const returnUrl = searchParams.get("returnUrl") ?? "/boards";
|
||||
const { showPopup } = usePopup();
|
||||
const [isProToggle, setIsProToggle] = useState(plan === "pro");
|
||||
|
||||
const [name, setName] = useState("");
|
||||
const [slug, setSlug] = useState("");
|
||||
const [slugManuallyEdited, setSlugManuallyEdited] = useState(false);
|
||||
const [description, setDescription] = useState("");
|
||||
|
||||
const handleNameChange = (value: string) => {
|
||||
setName(value);
|
||||
if (isProToggle && !slugManuallyEdited) {
|
||||
setSlug(slugify(value));
|
||||
}
|
||||
};
|
||||
|
||||
const [debouncedSlug] = useDebounce(slug, 500);
|
||||
const isTyping = slug !== debouncedSlug;
|
||||
|
||||
const slugAvailability = api.workspace.checkSlugAvailability.useQuery(
|
||||
{ workspaceSlug: debouncedSlug },
|
||||
{ enabled: isProToggle && debouncedSlug.length >= 3 && !isTyping },
|
||||
);
|
||||
|
||||
const isSlugAvailable =
|
||||
slugAvailability.data?.isAvailable && !slugAvailability.data?.isReserved;
|
||||
const isSlugTaken =
|
||||
slugAvailability.data?.isAvailable === false &&
|
||||
!slugAvailability.data?.isReserved;
|
||||
const isSlugReserved = slugAvailability.data?.isReserved === true;
|
||||
|
||||
const slugError = isSlugTaken
|
||||
? t`This URL has already been taken`
|
||||
: isSlugReserved
|
||||
? t`This URL is reserved`
|
||||
: undefined;
|
||||
|
||||
const { data: session } = authClient.useSession();
|
||||
const { data: user } = api.user.getUser.useQuery(undefined, {
|
||||
enabled: !!session?.user,
|
||||
});
|
||||
const { data: workspaces } = api.workspace.all.useQuery();
|
||||
const hasExistingWorkspace = !!workspaces?.length;
|
||||
|
||||
const utils = api.useUtils();
|
||||
|
||||
const previewSlug = isProToggle
|
||||
? slugify(slug) || slugify(name) || "your-workspace"
|
||||
: "your-workspace";
|
||||
|
||||
const createWorkspace = api.workspace.create.useMutation({
|
||||
onSuccess: async (workspace) => {
|
||||
if (!workspace.publicId) return;
|
||||
localStorage.setItem("workspacePublicId", workspace.publicId);
|
||||
void utils.workspace.all.invalidate();
|
||||
router.push("/boards");
|
||||
},
|
||||
onError: () => {
|
||||
showPopup({
|
||||
header: t`Unable to create workspace`,
|
||||
message: t`Please try again later, or contact customer support.`,
|
||||
icon: "error",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [isRedirectingToCheckout, setIsRedirectingToCheckout] = useState(false);
|
||||
|
||||
const handleContinue = async () => {
|
||||
if (!name.trim()) return;
|
||||
|
||||
if (plan === "solo") {
|
||||
createWorkspace.mutate({
|
||||
name: name.trim(),
|
||||
...(description.trim() && { description: description.trim() }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// team/pro: redirect to Stripe — workspace created on checkout_success
|
||||
setIsRedirectingToCheckout(true);
|
||||
try {
|
||||
const response = await fetch("/api/stripe/create_checkout_session", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
workspaceName: name.trim(),
|
||||
...(description.trim() && {
|
||||
workspaceDescription: description.trim(),
|
||||
}),
|
||||
...(plan === "pro" && slug ? { workspaceSlug: slug } : {}),
|
||||
cancelUrl: window.location.pathname + window.location.search,
|
||||
successUrl: "/boards",
|
||||
billing,
|
||||
plan,
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
const url = (data as { url: string }).url;
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
setIsRedirectingToCheckout(false);
|
||||
showPopup({
|
||||
header: t`Unable to start checkout`,
|
||||
message: t`Please try again later, or contact customer support.`,
|
||||
icon: "error",
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.getElementById("workspace-name-input")?.focus();
|
||||
}, []);
|
||||
|
||||
const displayName = user?.name ?? session?.user.name ?? "";
|
||||
|
||||
const BOARDS = [t`Roadmap`, t`Engineering`, t`Marketing`];
|
||||
const [visibleCount, setVisibleCount] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
const timers = BOARDS.slice(1).map((_, i) =>
|
||||
setTimeout(() => setVisibleCount(i + 2), (i + 1) * 2000),
|
||||
);
|
||||
return () => timers.forEach(clearTimeout);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center gap-4 bg-light-100 px-4 py-8 dark:bg-dark-50 md:px-6">
|
||||
<div className="w-full max-w-3xl overflow-hidden rounded-xl border border-light-400 bg-light-200 shadow-xl dark:border-dark-400 dark:bg-dark-100">
|
||||
<div className="flex flex-col md:h-[520px] md:flex-row">
|
||||
{/* Left panel */}
|
||||
<div className="flex flex-col p-6 md:w-[55%] md:p-8">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-xl font-bold text-light-1000 dark:text-dark-1000">
|
||||
{t`Set up your workspace`}
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-light-800 dark:text-dark-800">
|
||||
{t`You can always change these later in settings.`}
|
||||
</p>
|
||||
|
||||
<div className="mt-6 space-y-3">
|
||||
<Input
|
||||
id="workspace-name-input"
|
||||
placeholder={t`Workspace name`}
|
||||
value={name}
|
||||
onChange={(e) => handleNameChange(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleContinue()}
|
||||
maxLength={64}
|
||||
/>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Input
|
||||
placeholder={t`your-workspace`}
|
||||
value={isProToggle ? slug : t`your-workspace`}
|
||||
onChange={(e) => {
|
||||
setSlugManuallyEdited(true);
|
||||
setSlug(
|
||||
e.target.value
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, "")
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/-+/g, "-")
|
||||
.slice(0, 60),
|
||||
);
|
||||
}}
|
||||
disabled={!isProToggle}
|
||||
prefix="kan.bn/"
|
||||
className={
|
||||
!isProToggle ? "cursor-not-allowed opacity-50" : ""
|
||||
}
|
||||
errorMessage={slugError}
|
||||
iconRight={
|
||||
!isProToggle ? (
|
||||
<Tooltip
|
||||
content={
|
||||
<span className="text-xs">{t`Custom usernames require upgrading to a Pro plan`}</span>
|
||||
}
|
||||
placement="top"
|
||||
delay={0}
|
||||
>
|
||||
<HiInformationCircle className="h-4 w-4 leading-[0] text-dark-700 dark:text-dark-700" />
|
||||
</Tooltip>
|
||||
) : isProToggle && slug.length >= 3 ? (
|
||||
isTyping || slugAvailability.isPending ? (
|
||||
<LoadingSpinner />
|
||||
) : isSlugAvailable ? (
|
||||
<HiCheck className="h-4 w-4 text-white" />
|
||||
) : null
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{plan !== "pro" && (
|
||||
<div className="pb-2">
|
||||
<Toggle
|
||||
isChecked={isProToggle}
|
||||
onChange={() => setIsProToggle((v) => !v)}
|
||||
label={t`Upgrade to Pro ($29/month)`}
|
||||
labelPosition="after"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder={t`Workspace description`}
|
||||
maxLength={280}
|
||||
rows={3}
|
||||
className="block w-full resize-none rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 text-sm shadow-sm ring-1 ring-inset ring-light-600 placeholder:text-dark-800 focus:ring-2 focus:ring-inset focus:ring-light-700 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:leading-6"
|
||||
/>
|
||||
<p className="mt-1 text-right text-[10px] text-light-700 dark:text-dark-700">
|
||||
{description.length}/280
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="ml-auto flex gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() =>
|
||||
router.replace(
|
||||
`/onboarding/select-plan?plan=${plan}&billing=${billing}&returnUrl=${encodeURIComponent(returnUrl)}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
{t`Back`}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => void handleContinue()}
|
||||
disabled={
|
||||
!name.trim() ||
|
||||
createWorkspace.isPending ||
|
||||
isRedirectingToCheckout ||
|
||||
(isProToggle &&
|
||||
slug.length >= 3 &&
|
||||
(isTyping || slugAvailability.isPending || !!slugError))
|
||||
}
|
||||
isLoading={
|
||||
createWorkspace.isPending || isRedirectingToCheckout
|
||||
}
|
||||
>
|
||||
{t`Continue`}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right panel — browser mockup */}
|
||||
<div className="hidden flex-col bg-light-300 dark:bg-dark-200 md:flex md:w-[45%]">
|
||||
{/* Browser chrome */}
|
||||
<div className="flex items-center gap-2 border-b border-light-400 px-3 py-2.5 dark:border-dark-400">
|
||||
<div className="flex items-center gap-1.5 text-light-700 dark:text-dark-700">
|
||||
<HiArrowLeft className="h-3.5 w-3.5" />
|
||||
<HiArrowRight className="h-3.5 w-3.5" />
|
||||
<HiArrowPath className="h-3.5 w-3.5" />
|
||||
</div>
|
||||
<div className="flex flex-1 items-center gap-1.5 rounded-md bg-light-200 px-2.5 py-1 dark:bg-dark-300">
|
||||
<HiLockClosed className="h-3 w-3 flex-shrink-0 text-light-700 dark:text-dark-700" />
|
||||
<span className="truncate text-xs text-light-900 dark:text-dark-900">
|
||||
kan.bn/
|
||||
<span className="text-light-1000 dark:text-dark-1000">
|
||||
{previewSlug}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<HiEllipsisVertical className="h-4 w-4 flex-shrink-0 text-light-700 dark:text-dark-700" />
|
||||
</div>
|
||||
|
||||
{/* Browser content */}
|
||||
<div className="flex flex-1 flex-col items-center overflow-hidden px-14 py-8">
|
||||
{/* Workspace name + description */}
|
||||
<p className="text-center text-xs font-bold text-light-1000 dark:text-dark-1000">
|
||||
{name || t`Your workspace`}
|
||||
</p>
|
||||
<p className="mt-0.5 line-clamp-2 break-all text-center text-[10px] text-light-800 dark:text-dark-800">
|
||||
{description || t`Your workspace description`}
|
||||
</p>
|
||||
|
||||
{/* Boards area */}
|
||||
<div className="mt-3 flex w-full flex-1 flex-col rounded-lg bg-light-200 p-2 dark:bg-dark-300">
|
||||
<div className="flex flex-1 flex-col gap-1.5">
|
||||
{BOARDS.map((board, i) => (
|
||||
<motion.div
|
||||
key={board}
|
||||
initial={{ opacity: 0, y: -12 }}
|
||||
animate={
|
||||
i < visibleCount
|
||||
? { opacity: 1, y: 0 }
|
||||
: { opacity: 0, y: -12 }
|
||||
}
|
||||
transition={{
|
||||
opacity: { duration: 0.2 },
|
||||
y: { duration: 0.25, ease: "easeOut" },
|
||||
}}
|
||||
className="flex w-full flex-1 items-center justify-center rounded border border-dashed border-light-400 bg-light-50 px-3 text-[10px] font-medium text-light-900 dark:border-dark-400 dark:bg-dark-100 dark:text-dark-900"
|
||||
>
|
||||
{board}
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<p className="mt-2 text-[10px] font-semibold text-light-900 dark:text-dark-900">
|
||||
kan.bn
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!hasExistingWorkspace && (
|
||||
<Button variant="ghost" onClick={() => authClient.signOut()}>
|
||||
{t`Sign out`}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { env } from "next-runtime-env";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import FeedbackModal from "~/components/FeedbackModal";
|
||||
import { FontSizeSelector } from "~/components/FontSizeSelector";
|
||||
import { LanguageSelector } from "~/components/LanguageSelector";
|
||||
import Modal from "~/components/modal";
|
||||
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
|
||||
@@ -54,6 +55,16 @@ export default function AccountSettings() {
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
|
||||
<div className="mb-8 border-t border-light-300 dark:border-dark-300">
|
||||
<h2 className="mb-4 mt-8 text-[14px] font-bold text-neutral-900 dark:text-dark-1000">
|
||||
{t`Font size`}
|
||||
</h2>
|
||||
<p className="mb-8 text-sm text-neutral-500 dark:text-dark-900">
|
||||
{t`Change the application font size.`}
|
||||
</p>
|
||||
<FontSizeSelector />
|
||||
</div>
|
||||
|
||||
<div className="mb-8 border-t border-light-300 dark:border-dark-300">
|
||||
<h2 className="mb-4 mt-8 text-[14px] font-bold text-neutral-900 dark:text-dark-1000">
|
||||
{t`Delete account`}
|
||||
|
||||
@@ -110,18 +110,17 @@ export default function Avatar({
|
||||
const cropYpx = (crop.y / 100) * image.naturalHeight;
|
||||
const cropWpx = (crop.width / 100) * image.naturalWidth;
|
||||
const cropHpx = (crop.height / 100) * image.naturalHeight;
|
||||
canvas.width = Math.max(1, Math.floor(cropWpx));
|
||||
canvas.height = Math.max(1, Math.floor(cropHpx));
|
||||
|
||||
// Cap output at 512x512 — avatars display at 64x64, so higher res is wasteful
|
||||
// and causes "File too large" errors on HiDPI screens
|
||||
const maxSize = 512;
|
||||
const scale = Math.min(maxSize / cropWpx, maxSize / cropHpx, 1);
|
||||
canvas.width = Math.max(1, Math.floor(cropWpx * scale));
|
||||
canvas.height = Math.max(1, Math.floor(cropHpx * scale));
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) throw new Error("Canvas not supported");
|
||||
|
||||
// For better quality on HiDPI screens
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
canvas.width = canvas.width * pixelRatio;
|
||||
canvas.height = canvas.height * pixelRatio;
|
||||
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
||||
ctx.imageSmoothingQuality = "high";
|
||||
|
||||
ctx.drawImage(
|
||||
image,
|
||||
cropXpx,
|
||||
@@ -130,8 +129,8 @@ export default function Avatar({
|
||||
cropHpx,
|
||||
0,
|
||||
0,
|
||||
canvas.width / pixelRatio,
|
||||
canvas.height / pixelRatio,
|
||||
canvas.width,
|
||||
canvas.height,
|
||||
);
|
||||
|
||||
const mime = selectedFile?.type ?? "image/jpeg";
|
||||
@@ -139,6 +138,7 @@ export default function Avatar({
|
||||
canvas.toBlob(
|
||||
(b) => (b ? resolve(b) : reject(new Error("toBlob failed"))),
|
||||
mime,
|
||||
0.85,
|
||||
);
|
||||
});
|
||||
return blob;
|
||||
|
||||
@@ -37,6 +37,11 @@ services:
|
||||
- NEXT_PUBLIC_USE_STANDALONE_OUTPUT=${NEXT_PUBLIC_USE_STANDALONE_OUTPUT}
|
||||
- REDIS_URL=${REDIS_URL}
|
||||
|
||||
# Logging (optional - debug, info, warn, error; defaults to debug in dev, info in prod)
|
||||
- LOG_LEVEL=${LOG_LEVEL}
|
||||
- AXIOM_TOKEN=${AXIOM_TOKEN}
|
||||
- AXIOM_DATASET=${AXIOM_DATASET}
|
||||
|
||||
# Stripe
|
||||
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
|
||||
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
|
||||
|
||||
@@ -37,6 +37,9 @@ services:
|
||||
# Redis (optional - for rate limiting)
|
||||
- REDIS_URL=${REDIS_URL}
|
||||
|
||||
# Logging (optional - debug, info, warn, error; defaults to debug in dev, info in prod)
|
||||
- LOG_LEVEL=${LOG_LEVEL}
|
||||
|
||||
# Admin API key (optional)
|
||||
- KAN_ADMIN_API_KEY=${KAN_ADMIN_API_KEY}
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
"types": "./dist/utils/rateLimit.d.ts",
|
||||
"default": "./src/utils/rateLimit.ts"
|
||||
},
|
||||
"./utils/apiLogging": {
|
||||
"types": "./dist/utils/apiLogging.d.ts",
|
||||
"default": "./src/utils/apiLogging.ts"
|
||||
},
|
||||
"./utils/permissions": {
|
||||
"types": "./dist/utils/permissions.d.ts",
|
||||
"default": "./src/utils/permissions.ts"
|
||||
@@ -48,6 +52,7 @@
|
||||
"@kan/auth": "workspace:*",
|
||||
"@kan/db": "workspace:*",
|
||||
"@kan/email": "workspace:^",
|
||||
"@kan/logger": "workspace:^",
|
||||
"@kan/shared": "workspace:^",
|
||||
"@kan/stripe": "workspace:^",
|
||||
"@trpc/server": "catalog:",
|
||||
|
||||
@@ -227,7 +227,7 @@ export const boardRouter = createTRPCRouter({
|
||||
boardSlug: z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(24)
|
||||
.max(60)
|
||||
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/),
|
||||
members: z.array(z.string().min(12)).optional(),
|
||||
labels: z.array(z.string().min(12)).optional(),
|
||||
@@ -657,7 +657,7 @@ export const boardRouter = createTRPCRouter({
|
||||
boardSlug: z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(24)
|
||||
.max(60)
|
||||
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/),
|
||||
boardPublicId: z.string().min(12),
|
||||
}),
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from "zod";
|
||||
import * as cardRepo from "@kan/db/repository/card.repo";
|
||||
import * as cardActivityRepo from "@kan/db/repository/cardActivity.repo";
|
||||
import * as cardCommentRepo from "@kan/db/repository/cardComment.repo";
|
||||
import * as checklistRepo from "@kan/db/repository/checklist.repo";
|
||||
import * as labelRepo from "@kan/db/repository/label.repo";
|
||||
import * as listRepo from "@kan/db/repository/list.repo";
|
||||
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
|
||||
@@ -1184,4 +1185,176 @@ export const cardRouter = createTRPCRouter({
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
duplicate: protectedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
summary: "Duplicate a card",
|
||||
method: "POST",
|
||||
path: "/cards/{cardPublicId}/duplicate",
|
||||
description: "Duplicates a card to a target list with optional options",
|
||||
tags: ["Cards"],
|
||||
protect: true,
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
cardPublicId: z.string().min(12),
|
||||
listPublicId: z.string().min(12),
|
||||
index: z.number().int().min(0).optional(),
|
||||
title: z.string().min(1).max(2000).optional(),
|
||||
copyLabels: z.boolean(),
|
||||
copyMembers: z.boolean(),
|
||||
copyChecklists: z.boolean(),
|
||||
}),
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
publicId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const userId = ctx.user?.id;
|
||||
|
||||
if (!userId)
|
||||
throw new TRPCError({
|
||||
message: `User not authenticated`,
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
|
||||
const sourceCardMeta = await cardRepo.getWorkspaceAndCardIdByCardPublicId(
|
||||
ctx.db,
|
||||
input.cardPublicId,
|
||||
);
|
||||
|
||||
if (!sourceCardMeta)
|
||||
throw new TRPCError({
|
||||
message: `Card with public ID ${input.cardPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
await assertPermission(
|
||||
ctx.db,
|
||||
userId,
|
||||
sourceCardMeta.workspaceId,
|
||||
"card:create",
|
||||
);
|
||||
|
||||
const targetList = await listRepo.getWorkspaceAndListIdByListPublicId(
|
||||
ctx.db,
|
||||
input.listPublicId,
|
||||
);
|
||||
|
||||
if (!targetList)
|
||||
throw new TRPCError({
|
||||
message: `List with public ID ${input.listPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
if (targetList.workspaceId !== sourceCardMeta.workspaceId)
|
||||
throw new TRPCError({
|
||||
message: `Target list must be in the same workspace`,
|
||||
code: "BAD_REQUEST",
|
||||
});
|
||||
|
||||
const sourceCard = await cardRepo.getWithListAndMembersByPublicId(
|
||||
ctx.db,
|
||||
input.cardPublicId,
|
||||
);
|
||||
|
||||
if (!sourceCard)
|
||||
throw new TRPCError({
|
||||
message: `Card with public ID ${input.cardPublicId} not found`,
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
|
||||
const newCard = await cardRepo.create(ctx.db, {
|
||||
title: input.title ?? sourceCard.title,
|
||||
description: sourceCard.description ?? "",
|
||||
createdBy: userId,
|
||||
listId: targetList.id,
|
||||
position: "end",
|
||||
dueDate: sourceCard.dueDate ?? null,
|
||||
});
|
||||
|
||||
if (input.index !== undefined && input.index >= 0) {
|
||||
await cardRepo.reorder(ctx.db, {
|
||||
cardId: newCard.id,
|
||||
newIndex: input.index,
|
||||
newListId: targetList.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (input.copyLabels && sourceCard.labels?.length) {
|
||||
const labelPublicIds = sourceCard.labels.map((l) => l.publicId);
|
||||
const labels = await labelRepo.getAllByPublicIds(ctx.db, labelPublicIds);
|
||||
if (labels.length) {
|
||||
const labelsInsert = labels.map((label) => ({
|
||||
cardId: newCard.id,
|
||||
labelId: label.id,
|
||||
}));
|
||||
await cardRepo.bulkCreateCardLabelRelationships(ctx.db, labelsInsert);
|
||||
const cardActivitesInsert = labels.map((cardLabel) => ({
|
||||
type: "card.updated.label.added" as const,
|
||||
cardId: newCard.id,
|
||||
labelId: cardLabel.id,
|
||||
createdBy: userId,
|
||||
}));
|
||||
await cardActivityRepo.bulkCreate(ctx.db, cardActivitesInsert);
|
||||
}
|
||||
}
|
||||
|
||||
if (input.copyMembers && sourceCard.members?.length) {
|
||||
const memberPublicIds = sourceCard.members.map((m) => m.publicId);
|
||||
const members = await workspaceRepo.getAllMembersByPublicIds(
|
||||
ctx.db,
|
||||
memberPublicIds,
|
||||
);
|
||||
if (members.length) {
|
||||
const membersInsert = members.map((member) => ({
|
||||
cardId: newCard.id,
|
||||
workspaceMemberId: member.id,
|
||||
}));
|
||||
await cardRepo.bulkCreateCardWorkspaceMemberRelationships(
|
||||
ctx.db,
|
||||
membersInsert,
|
||||
);
|
||||
const cardActivitesInsert = members.map((member) => ({
|
||||
type: "card.updated.member.added" as const,
|
||||
cardId: newCard.id,
|
||||
workspaceMemberId: member.id,
|
||||
createdBy: userId,
|
||||
}));
|
||||
await cardActivityRepo.bulkCreate(ctx.db, cardActivitesInsert);
|
||||
}
|
||||
}
|
||||
|
||||
if (input.copyChecklists && sourceCard.checklists?.length) {
|
||||
for (const checklist of sourceCard.checklists) {
|
||||
const newChecklist = await checklistRepo.create(ctx.db, {
|
||||
cardId: newCard.id,
|
||||
name: checklist.name,
|
||||
createdBy: userId,
|
||||
});
|
||||
if (!newChecklist?.id) continue;
|
||||
if (checklist.items?.length) {
|
||||
for (const item of checklist.items) {
|
||||
await checklistRepo.createItem(ctx.db, {
|
||||
checklistId: newChecklist.id,
|
||||
title: item.title,
|
||||
createdBy: userId,
|
||||
completed: item.completed ?? false,
|
||||
});
|
||||
}
|
||||
}
|
||||
await cardActivityRepo.create(ctx.db, {
|
||||
type: "card.updated.checklist.added",
|
||||
cardId: newCard.id,
|
||||
toTitle: newChecklist.name,
|
||||
createdBy: userId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { publicId: newCard.publicId };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from "zod";
|
||||
import * as cardRepo from "@kan/db/repository/card.repo";
|
||||
import * as cardActivityRepo from "@kan/db/repository/cardActivity.repo";
|
||||
import * as checklistRepo from "@kan/db/repository/checklist.repo";
|
||||
import { stripHtml } from "@kan/shared/utils";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { assertPermission } from "../utils/permissions";
|
||||
@@ -223,7 +224,7 @@ export const checklistRouter = createTRPCRouter({
|
||||
.input(
|
||||
z.object({
|
||||
checklistPublicId: z.string().length(12),
|
||||
title: z.string().min(1).max(500),
|
||||
title: z.string().min(1).max(500).transform(stripHtml),
|
||||
}),
|
||||
)
|
||||
.output(checklistItemSchema)
|
||||
@@ -288,7 +289,7 @@ export const checklistRouter = createTRPCRouter({
|
||||
.input(
|
||||
z.object({
|
||||
checklistItemPublicId: z.string().length(12),
|
||||
title: z.string().min(1).max(500).optional(),
|
||||
title: z.string().min(1).max(500).transform(stripHtml).optional(),
|
||||
completed: z.boolean().optional(),
|
||||
index: z.number().int().min(0).optional(),
|
||||
}),
|
||||
@@ -322,30 +323,29 @@ export const checklistRouter = createTRPCRouter({
|
||||
|
||||
const previousTitle = item.title;
|
||||
|
||||
let updatedItem;
|
||||
let updatedItem;
|
||||
|
||||
if (input.title !== undefined || input.completed !== undefined) {
|
||||
updatedItem = await checklistRepo.updateItemById(ctx.db, {
|
||||
id: item.id,
|
||||
title: input.title,
|
||||
completed: input.completed,
|
||||
});
|
||||
}
|
||||
if (input.title !== undefined || input.completed !== undefined) {
|
||||
updatedItem = await checklistRepo.updateItemById(ctx.db, {
|
||||
id: item.id,
|
||||
title: input.title,
|
||||
completed: input.completed,
|
||||
});
|
||||
}
|
||||
|
||||
if (input.index !== undefined) {
|
||||
updatedItem = await checklistRepo.reorderItem(ctx.db, {
|
||||
itemId: item.id,
|
||||
newIndex: input.index,
|
||||
});
|
||||
}
|
||||
|
||||
if (!updatedItem) {
|
||||
throw new TRPCError({
|
||||
message: `Failed to update checklist item`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
}
|
||||
if (input.index !== undefined) {
|
||||
updatedItem = await checklistRepo.reorderItem(ctx.db, {
|
||||
itemId: item.id,
|
||||
newIndex: input.index,
|
||||
});
|
||||
}
|
||||
|
||||
if (!updatedItem) {
|
||||
throw new TRPCError({
|
||||
message: `Failed to update checklist item`,
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
});
|
||||
}
|
||||
|
||||
// Log completion toggle
|
||||
if (input.completed !== undefined) {
|
||||
@@ -371,7 +371,6 @@ export const checklistRouter = createTRPCRouter({
|
||||
}
|
||||
|
||||
return updatedItem;
|
||||
|
||||
}),
|
||||
deleteItem: protectedProcedure
|
||||
.meta({
|
||||
|
||||
@@ -19,6 +19,7 @@ import { encryptToken } from "../utils/encryption";
|
||||
export const integrationRouter = createTRPCRouter({
|
||||
saveGitHubToken: protectedProcedure
|
||||
.input(z.object({ token: z.string() }))
|
||||
.output(z.object({ success: z.boolean() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const user = ctx.user;
|
||||
|
||||
@@ -43,7 +44,9 @@ export const integrationRouter = createTRPCRouter({
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
disconnectGitHub: protectedProcedure.mutation(async ({ ctx }) => {
|
||||
disconnectGitHub: protectedProcedure
|
||||
.output(z.object({ success: z.boolean() }))
|
||||
.mutation(async ({ ctx }) => {
|
||||
const user = ctx.user;
|
||||
|
||||
if (!user)
|
||||
@@ -56,7 +59,9 @@ export const integrationRouter = createTRPCRouter({
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
getGitHubStatus: protectedProcedure.query(async ({ ctx }) => {
|
||||
getGitHubStatus: protectedProcedure
|
||||
.output(z.object({ connected: z.boolean() }))
|
||||
.query(async ({ ctx }) => {
|
||||
const user = ctx.user;
|
||||
|
||||
if (!user)
|
||||
|
||||
@@ -31,7 +31,6 @@ export const userRouter = createTRPCRouter({
|
||||
.object({
|
||||
id: z.number(),
|
||||
prefix: z.string().nullable(),
|
||||
key: z.string(),
|
||||
})
|
||||
.nullable(),
|
||||
}),
|
||||
@@ -62,7 +61,7 @@ export const userRouter = createTRPCRouter({
|
||||
return {
|
||||
...result,
|
||||
image: imageUrl,
|
||||
apiKey: apiKey ?? null,
|
||||
apiKey: apiKey ? { id: apiKey.id, prefix: apiKey.prefix } : null,
|
||||
};
|
||||
}),
|
||||
update: protectedProcedure
|
||||
|
||||
@@ -202,6 +202,7 @@ export const workspaceRouter = createTRPCRouter({
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string().min(1).max(64),
|
||||
description: z.string().max(280).optional(),
|
||||
slug: z
|
||||
.string()
|
||||
.min(3)
|
||||
@@ -260,6 +261,7 @@ export const workspaceRouter = createTRPCRouter({
|
||||
slug: workspaceSlug,
|
||||
createdBy: userId,
|
||||
createdByEmail: userEmail,
|
||||
...(input.description && { description: input.description }),
|
||||
});
|
||||
|
||||
if (!result.publicId)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
|
||||
import type { NextApiRequest } from "next";
|
||||
import type { OpenApiMeta } from "trpc-to-openapi";
|
||||
@@ -9,6 +10,30 @@ import { ZodError } from "zod";
|
||||
import type { dbClient } from "@kan/db/client";
|
||||
import { initAuth } from "@kan/auth/server";
|
||||
import { createDrizzleClient } from "@kan/db/client";
|
||||
import { createLogger } from "@kan/logger";
|
||||
|
||||
const log = createLogger("api");
|
||||
|
||||
const TRPC_STATUS_MAP: Partial<Record<TRPCError["code"], number>> = {
|
||||
PARSE_ERROR: 400,
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
METHOD_NOT_SUPPORTED: 405,
|
||||
TIMEOUT: 408,
|
||||
CONFLICT: 409,
|
||||
PRECONDITION_FAILED: 412,
|
||||
PAYLOAD_TOO_LARGE: 413,
|
||||
UNPROCESSABLE_CONTENT: 422,
|
||||
TOO_MANY_REQUESTS: 429,
|
||||
CLIENT_CLOSED_REQUEST: 499,
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
NOT_IMPLEMENTED: 501,
|
||||
BAD_GATEWAY: 502,
|
||||
SERVICE_UNAVAILABLE: 503,
|
||||
GATEWAY_TIMEOUT: 504,
|
||||
};
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
@@ -47,6 +72,7 @@ interface CreateContextOptions {
|
||||
db: dbClient;
|
||||
auth: ReturnType<typeof createAuthWithHeaders>;
|
||||
headers: Headers;
|
||||
transport?: "trpc" | "rest";
|
||||
}
|
||||
|
||||
export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
||||
@@ -55,6 +81,8 @@ export const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
||||
db: opts.db,
|
||||
auth: opts.auth,
|
||||
headers: opts.headers,
|
||||
transport: opts.transport ?? "trpc",
|
||||
requestId: randomUUID(),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -66,7 +94,13 @@ export const createTRPCContext = async ({ req }: CreateNextContextOptions) => {
|
||||
|
||||
const session = await auth.api.getSession();
|
||||
|
||||
return createInnerTRPCContext({ db, user: session?.user, auth, headers });
|
||||
return createInnerTRPCContext({
|
||||
db,
|
||||
user: session?.user,
|
||||
auth,
|
||||
headers,
|
||||
transport: "trpc",
|
||||
});
|
||||
};
|
||||
|
||||
export const createNextApiContext = async (req: NextApiRequest) => {
|
||||
@@ -77,7 +111,13 @@ export const createNextApiContext = async (req: NextApiRequest) => {
|
||||
|
||||
const session = await auth.api.getSession();
|
||||
|
||||
return createInnerTRPCContext({ db, user: session?.user, auth, headers });
|
||||
return createInnerTRPCContext({
|
||||
db,
|
||||
user: session?.user,
|
||||
auth,
|
||||
headers,
|
||||
transport: "trpc",
|
||||
});
|
||||
};
|
||||
|
||||
export const createRESTContext = async ({ req }: CreateNextContextOptions) => {
|
||||
@@ -90,11 +130,16 @@ export const createRESTContext = async ({ req }: CreateNextContextOptions) => {
|
||||
try {
|
||||
session = await auth.api.getSession();
|
||||
} catch (error) {
|
||||
console.error("Error getting session, ", error);
|
||||
throw error;
|
||||
log.warn({ err: error }, "Failed to get session, treating as unauthenticated");
|
||||
}
|
||||
|
||||
return createInnerTRPCContext({ db, user: session?.user, auth, headers });
|
||||
return createInnerTRPCContext({
|
||||
db,
|
||||
user: session?.user,
|
||||
auth,
|
||||
headers,
|
||||
transport: "rest",
|
||||
});
|
||||
};
|
||||
|
||||
const t = initTRPC
|
||||
@@ -118,10 +163,46 @@ export const createTRPCRouter = t.router;
|
||||
|
||||
export const createCallerFactory = t.createCallerFactory;
|
||||
|
||||
export const publicProcedure = t.procedure.meta({
|
||||
openapi: { method: "GET", path: "/public" },
|
||||
const loggingMiddleware = t.middleware(async ({ path, type, next, ctx, getRawInput }) => {
|
||||
const start = Date.now();
|
||||
const [result, input] = await Promise.all([next(), getRawInput().catch(() => undefined)]);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
const { user, transport, requestId } = ctx as {
|
||||
user?: { id: string; email: string };
|
||||
transport?: string;
|
||||
requestId?: string;
|
||||
};
|
||||
const isCloud = process.env.NEXT_PUBLIC_KAN_ENV === "cloud";
|
||||
const meta = {
|
||||
requestId,
|
||||
procedure: path,
|
||||
type,
|
||||
transport,
|
||||
duration,
|
||||
userId: user?.id,
|
||||
...(isCloud && { email: user?.email }),
|
||||
input,
|
||||
};
|
||||
|
||||
const label = transport === "rest" ? "REST" : "tRPC";
|
||||
|
||||
if (result.ok) {
|
||||
log.info({ ...meta, status: 200 }, `${label} OK`);
|
||||
} else {
|
||||
const status = TRPC_STATUS_MAP[result.error.code] ?? 500;
|
||||
const errorCode = result.error.code;
|
||||
log.error(
|
||||
{ ...meta, status, errorCode, err: result.error },
|
||||
`${label} error`,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
export const publicProcedure = t.procedure.use(loggingMiddleware);
|
||||
|
||||
const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => {
|
||||
if (!ctx.user) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
@@ -142,14 +223,12 @@ const enforceUserIsAdmin = t.middleware(async ({ ctx, next }) => {
|
||||
});
|
||||
});
|
||||
|
||||
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed).meta({
|
||||
openapi: {
|
||||
method: "GET",
|
||||
path: "/protected",
|
||||
},
|
||||
});
|
||||
export const protectedProcedure = t.procedure
|
||||
.use(loggingMiddleware)
|
||||
.use(enforceUserIsAuthed);
|
||||
|
||||
export const adminProtectedProcedure = t.procedure
|
||||
.use(loggingMiddleware)
|
||||
.use(enforceUserIsAdmin)
|
||||
.meta({
|
||||
openapi: {
|
||||
|
||||
64
packages/api/src/utils/apiLogging.ts
Normal file
64
packages/api/src/utils/apiLogging.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { createLogger } from "@kan/logger";
|
||||
|
||||
import { createNextApiContext } from "../trpc";
|
||||
|
||||
const log = createLogger("api");
|
||||
|
||||
const isCloud = process.env.NEXT_PUBLIC_KAN_ENV === "cloud";
|
||||
|
||||
export function withApiLogging(
|
||||
handler: (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) => Promise<unknown> | unknown,
|
||||
) {
|
||||
return async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const start = Date.now();
|
||||
const requestId = randomUUID();
|
||||
const route = req.url?.split("?")[0] ?? "unknown";
|
||||
const input = {
|
||||
...(req.query && Object.keys(req.query).length > 0 && { query: req.query }),
|
||||
...(req.body && typeof req.body === "object" && Object.keys(req.body).length > 0 && { body: req.body }),
|
||||
};
|
||||
|
||||
let statusCode = 200;
|
||||
const originalStatus = res.status.bind(res);
|
||||
res.status = (code: number) => {
|
||||
statusCode = code;
|
||||
return originalStatus(code);
|
||||
};
|
||||
|
||||
let userId: string | undefined;
|
||||
let email: string | undefined;
|
||||
try {
|
||||
const ctx = await createNextApiContext(req);
|
||||
userId = ctx.user?.id;
|
||||
email = ctx.user?.email ?? undefined;
|
||||
} catch {
|
||||
// unauthenticated or auth unavailable
|
||||
}
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
const duration = Date.now() - start;
|
||||
const meta = {
|
||||
requestId,
|
||||
procedure: route,
|
||||
transport: "rest",
|
||||
duration,
|
||||
userId,
|
||||
...(isCloud && email && { email }),
|
||||
...(Object.keys(input).length > 0 && { input }),
|
||||
status: statusCode,
|
||||
};
|
||||
|
||||
if (statusCode < 400) {
|
||||
log.info(meta, "API OK");
|
||||
} else {
|
||||
log.error(meta, "API error");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { env } from "next-runtime-env";
|
||||
|
||||
import type { dbClient } from "@kan/db/client";
|
||||
import { createLogger } from "@kan/logger";
|
||||
|
||||
const log = createLogger("notifications");
|
||||
import * as cardRepo from "@kan/db/repository/card.repo";
|
||||
import * as memberRepo from "@kan/db/repository/member.repo";
|
||||
import * as notificationRepo from "@kan/db/repository/notification.repo";
|
||||
@@ -72,6 +75,7 @@ export async function sendMentionEmails({
|
||||
const baseUrl = env("NEXT_PUBLIC_BASE_URL");
|
||||
const cardUrl = `${baseUrl}/cards/${cardPublicId}`;
|
||||
|
||||
log.info({ cardPublicId, mentionCount: membersToNotify.length, commenterUserId }, "Sending mention emails");
|
||||
// Send emails to all mentioned members (only if notification doesn't exist)
|
||||
await Promise.all(
|
||||
membersToNotify.map(async (member) => {
|
||||
@@ -91,6 +95,7 @@ export async function sendMentionEmails({
|
||||
|
||||
// If notification already exists, skip sending email
|
||||
if (notificationExists) {
|
||||
log.debug({ email, cardPublicId }, "Skipping duplicate mention email");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,20 +119,14 @@ export async function sendMentionEmails({
|
||||
cardUrl,
|
||||
},
|
||||
);
|
||||
log.info({ email, cardPublicId }, "Mention email sent");
|
||||
} catch (error) {
|
||||
console.error("Failed to send mention email:", {
|
||||
email,
|
||||
cardPublicId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
log.error({ err: error, email, cardPublicId }, "Failed to send mention email");
|
||||
}
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error sending mention emails:", {
|
||||
cardPublicId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
log.error({ err: error, cardPublicId }, "Error sending mention emails");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ import {
|
||||
} from "rate-limiter-flexible";
|
||||
|
||||
import { getRedisClient } from "@kan/db/redis";
|
||||
import { createLogger } from "@kan/logger";
|
||||
|
||||
const log = createLogger("rateLimit");
|
||||
|
||||
export interface RateLimitOptions {
|
||||
points?: number;
|
||||
@@ -45,7 +48,7 @@ function createRateLimiter(options: RateLimitOptions = {}) {
|
||||
|
||||
// Use Redis if available, otherwise fall back to in-memory storage
|
||||
if (redis) {
|
||||
console.log("Using Redis for rate limiting");
|
||||
log.debug("Using Redis for rate limiting");
|
||||
return new RateLimiterRedis({
|
||||
storeClient: redis,
|
||||
points,
|
||||
@@ -53,7 +56,7 @@ function createRateLimiter(options: RateLimitOptions = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Using in-memory for rate limiting");
|
||||
log.debug("Redis unavailable, falling back to in-memory rate limiting");
|
||||
return new RateLimiterMemory({
|
||||
points,
|
||||
duration,
|
||||
|
||||
@@ -4,6 +4,9 @@ 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";
|
||||
import { createLogger } from "@kan/logger";
|
||||
|
||||
const log = createLogger("webhook");
|
||||
|
||||
export type WebhookEventType = WebhookEvent;
|
||||
|
||||
@@ -199,9 +202,9 @@ export async function sendWebhooksForWorkspace(
|
||||
sendWebhookToUrl(webhook.url, webhook.secret ?? undefined, payload).then(
|
||||
(result) => {
|
||||
if (!result.success) {
|
||||
console.error(
|
||||
`Webhook delivery failed to ${webhook.url}: ${result.error}`,
|
||||
);
|
||||
log.error({ url: webhook.url, event: payload.event, error: result.error, statusCode: result.statusCode }, "Webhook delivery failed");
|
||||
} else {
|
||||
log.info({ url: webhook.url, event: payload.event, statusCode: result.statusCode }, "Webhook delivered");
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -210,7 +213,7 @@ export async function sendWebhooksForWorkspace(
|
||||
// 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);
|
||||
log.error({ err: error, workspaceId }, "Failed to send webhooks for workspace");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"prettier": "@kan/prettier-config",
|
||||
"dependencies": {
|
||||
"@better-auth/stripe": "^1.4.6",
|
||||
"@kan/logger": "workspace:^",
|
||||
"better-auth": "^1.4.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@ import type { dbClient } from "@kan/db/client";
|
||||
import * as memberRepo from "@kan/db/repository/member.repo";
|
||||
import * as userRepo from "@kan/db/repository/user.repo";
|
||||
import { notificationClient } from "@kan/email";
|
||||
import { createLogger } from "@kan/logger";
|
||||
import { createEmailUnsubscribeLink, createS3Client } from "@kan/shared";
|
||||
|
||||
const log = createLogger("auth");
|
||||
|
||||
import { downloadImage } from "./utils";
|
||||
|
||||
type BetterAuthUser = {
|
||||
@@ -103,6 +106,7 @@ export function createDatabaseHooks(db: dbClient) {
|
||||
|
||||
const unsubscribeUrl = await createEmailUnsubscribeLink(user.id);
|
||||
|
||||
log.info({ workflowId: "user-signup", userId: user.id, email: user.email }, "Triggering Novu workflow");
|
||||
await notificationClient.trigger({
|
||||
to: {
|
||||
subscriberId: user.id,
|
||||
@@ -122,6 +126,7 @@ export function createDatabaseHooks(db: dbClient) {
|
||||
},
|
||||
workflowId: "user-signup",
|
||||
});
|
||||
log.info({ workflowId: "user-signup", userId: user.id }, "Novu workflow triggered");
|
||||
|
||||
await notificationClient.subscribers.credentials.update(
|
||||
{
|
||||
@@ -134,7 +139,7 @@ export function createDatabaseHooks(db: dbClient) {
|
||||
user.id,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error adding user to notification client", error);
|
||||
log.error({ err: error }, "Error adding user to notification client");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,7 +8,10 @@ import * as subscriptionRepo from "@kan/db/repository/subscription.repo";
|
||||
import * as userRepo from "@kan/db/repository/user.repo";
|
||||
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
|
||||
import { sendEmail } from "@kan/email";
|
||||
import { createLogger } from "@kan/logger";
|
||||
import { generateUID } from "@kan/shared/utils";
|
||||
|
||||
const log = createLogger("auth");
|
||||
import { createStripeClient } from "@kan/stripe";
|
||||
|
||||
import { socialProvidersPlugin } from "./providers";
|
||||
@@ -101,9 +104,7 @@ export function createPlugins(db: dbClient) {
|
||||
unlimitedSeats: true,
|
||||
},
|
||||
);
|
||||
console.log(
|
||||
`Pro subscription ${stripeSubscription.id} activated with unlimited seats`,
|
||||
);
|
||||
log.info({ subscriptionId: stripeSubscription.id }, "Pro subscription activated with unlimited seats");
|
||||
|
||||
const workspace = await workspaceRepo.getByPublicId(
|
||||
db,
|
||||
@@ -164,6 +165,13 @@ export function createPlugins(db: dbClient) {
|
||||
: []),
|
||||
apiKey({
|
||||
enableSessionForAPIKeys: true,
|
||||
customAPIKeyGetter: (ctx) => {
|
||||
const authorization = ctx.headers?.get("authorization");
|
||||
if (authorization?.startsWith("Bearer ")) {
|
||||
return authorization.slice(7);
|
||||
}
|
||||
return ctx.headers?.get("x-api-key") ?? undefined;
|
||||
},
|
||||
rateLimit: {
|
||||
enabled: true,
|
||||
timeWindow: 1000 * 60, // 1 minute
|
||||
@@ -175,11 +183,7 @@ export function createPlugins(db: dbClient) {
|
||||
sendMagicLink: async ({ email, url }) => {
|
||||
try {
|
||||
const decodedUrl = decodeURIComponent(url);
|
||||
console.log("Sending magic link to:", email, "URL:", url);
|
||||
console.log(
|
||||
"Magic link contains invite:",
|
||||
decodedUrl.includes("type=invite"),
|
||||
);
|
||||
log.info({ email, isInvite: decodedUrl.includes("type=invite") }, "Sending magic link");
|
||||
if (decodedUrl.includes("type=invite")) {
|
||||
let inviterName = "";
|
||||
let workspaceName = "";
|
||||
@@ -211,7 +215,7 @@ export function createPlugins(db: dbClient) {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch invite details:", error);
|
||||
log.error({ err: error }, "Failed to fetch invite details");
|
||||
}
|
||||
|
||||
await sendEmail(
|
||||
@@ -239,11 +243,7 @@ export function createPlugins(db: dbClient) {
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error sending magic link:", {
|
||||
email,
|
||||
url,
|
||||
error,
|
||||
});
|
||||
log.error({ err: error, email }, "Error sending magic link");
|
||||
}
|
||||
},
|
||||
}),
|
||||
@@ -273,7 +273,7 @@ export function createPlugins(db: dbClient) {
|
||||
picture?: string;
|
||||
avatar?: string;
|
||||
}) => {
|
||||
console.log("OIDC profile:", profile);
|
||||
log.debug({ profile }, "OIDC profile received");
|
||||
|
||||
const name =
|
||||
profile.name ??
|
||||
|
||||
@@ -4,8 +4,11 @@ import type Stripe from "stripe";
|
||||
import type { dbClient } from "@kan/db/client";
|
||||
import * as userRepo from "@kan/db/repository/user.repo";
|
||||
import { notificationClient } from "@kan/email";
|
||||
import { createLogger } from "@kan/logger";
|
||||
import { createEmailUnsubscribeLink } from "@kan/shared";
|
||||
|
||||
const log = createLogger("auth");
|
||||
|
||||
export async function downloadImage(url: string): Promise<Buffer> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
@@ -32,6 +35,7 @@ export async function triggerWorkflow(
|
||||
|
||||
const unsubscribeUrl = await createEmailUnsubscribeLink(user.id);
|
||||
|
||||
log.info({ workflowId, userId: user.id }, "Triggering Novu workflow");
|
||||
await notificationClient.trigger({
|
||||
to: {
|
||||
subscriberId: user.id,
|
||||
@@ -43,7 +47,8 @@ export async function triggerWorkflow(
|
||||
},
|
||||
workflowId,
|
||||
});
|
||||
log.info({ workflowId, userId: user.id }, "Novu workflow triggered");
|
||||
} catch (error) {
|
||||
console.error("Error triggering workflow", error);
|
||||
log.error({ err: error, workflowId }, "Error triggering workflow");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TYPE "public"."workspace_plan" ADD VALUE 'team' BEFORE 'pro';
|
||||
3869
packages/db/migrations/meta/20260402225628_snapshot.json
Normal file
3869
packages/db/migrations/meta/20260402225628_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -218,6 +218,13 @@
|
||||
"when": 1773212242728,
|
||||
"tag": "20260311065722_AddWeekStartDay",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 31,
|
||||
"version": "7",
|
||||
"when": 1775170588247,
|
||||
"tag": "20260402225628_AddTeamWorkspacePlan",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -44,6 +44,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@electric-sql/pglite": "^0.3.7",
|
||||
"@kan/logger": "workspace:^",
|
||||
"@kan/shared": "workspace:^",
|
||||
"drizzle-orm": "^0.42.0",
|
||||
"drizzle-zod": "^0.5.1",
|
||||
|
||||
@@ -6,8 +6,12 @@ import { drizzle as drizzlePgLite } from "drizzle-orm/pglite";
|
||||
import { migrate } from "drizzle-orm/pglite/migrator";
|
||||
import { Pool } from "pg";
|
||||
|
||||
import { createLogger } from "@kan/logger";
|
||||
|
||||
import * as schema from "./schema";
|
||||
|
||||
const log = createLogger("db");
|
||||
|
||||
export type dbClient = NodePgDatabase<typeof schema> & {
|
||||
$client: Pool;
|
||||
};
|
||||
@@ -16,7 +20,7 @@ export const createDrizzleClient = (): dbClient => {
|
||||
const connectionString = process.env.POSTGRES_URL;
|
||||
|
||||
if (!connectionString) {
|
||||
console.log("POSTGRES_URL environment variable is not set, using PGLite");
|
||||
log.warn("POSTGRES_URL not set, falling back to PGLite");
|
||||
|
||||
const client = new PGlite({
|
||||
dataDir: "./pgdata",
|
||||
|
||||
@@ -402,8 +402,6 @@ export const softDeleteById = async (
|
||||
.groupBy(lists.index)
|
||||
.having(gt(countExpr, 1));
|
||||
|
||||
console.log(duplicateIndices);
|
||||
|
||||
if (duplicateIndices.length > 0) {
|
||||
throw new Error(
|
||||
`Duplicate indices found after reordering in board ${result.boardId}`,
|
||||
|
||||
@@ -64,6 +64,8 @@ export const create = async (
|
||||
slug: string;
|
||||
createdBy: string;
|
||||
createdByEmail: string;
|
||||
description?: string;
|
||||
plan?: "free" | "team" | "pro" | "enterprise";
|
||||
},
|
||||
) => {
|
||||
const [workspace] = await db
|
||||
@@ -73,6 +75,8 @@ export const create = async (
|
||||
name: workspaceInput.name,
|
||||
slug: workspaceInput.slug,
|
||||
createdBy: workspaceInput.createdBy,
|
||||
...(workspaceInput.description && { description: workspaceInput.description }),
|
||||
...(workspaceInput.plan && { plan: workspaceInput.plan }),
|
||||
})
|
||||
.returning({
|
||||
id: workspaces.id,
|
||||
@@ -124,7 +128,7 @@ export const update = async (
|
||||
workspaceInput: {
|
||||
name?: string;
|
||||
slug?: string;
|
||||
plan?: "free" | "pro" | "enterprise";
|
||||
plan?: "free" | "team" | "pro" | "enterprise";
|
||||
description?: string;
|
||||
showEmailsToMembers?: boolean;
|
||||
weekStartDay?: number;
|
||||
|
||||
@@ -34,7 +34,7 @@ export const slugTypes = ["reserved", "premium"] as const;
|
||||
export type SlugType = (typeof slugTypes)[number];
|
||||
export const slugTypeEnum = pgEnum("slug_type", slugTypes);
|
||||
|
||||
export const workspacePlans = ["free", "pro", "enterprise"] as const;
|
||||
export const workspacePlans = ["free", "team", "pro", "enterprise"] as const;
|
||||
export type WorkspacePlan = (typeof workspacePlans)[number];
|
||||
export const workspacePlanEnum = pgEnum("workspace_plan", workspacePlans);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kan/logger": "workspace:^",
|
||||
"@novu/api": "^3.11.0",
|
||||
"@react-email/components": "^1.0.1",
|
||||
"nodemailer": "^7.0.3",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { render } from "@react-email/render";
|
||||
import nodemailer from "nodemailer";
|
||||
import { createLogger } from "@kan/logger";
|
||||
|
||||
const log = createLogger("email");
|
||||
|
||||
import JoinWorkspaceTemplate from "./templates/join-workspace";
|
||||
import MagicLinkTemplate from "./templates/magic-link";
|
||||
@@ -44,6 +47,7 @@ export const sendEmail = async (
|
||||
template: Templates,
|
||||
data: Record<string, string>,
|
||||
) => {
|
||||
log.info({ to, subject, template }, "Sending email");
|
||||
try {
|
||||
const EmailTemplate = emailTemplates[template];
|
||||
|
||||
@@ -62,16 +66,10 @@ export const sendEmail = async (
|
||||
throw new Error(`Failed to send email: ${response.response}`);
|
||||
}
|
||||
|
||||
log.info({ to, subject, template, messageId: response.messageId }, "Email sent");
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Email sending failed:", {
|
||||
to,
|
||||
from: process.env.EMAIL_FROM,
|
||||
subject,
|
||||
template,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
log.error({ err: error, to, from: process.env.EMAIL_FROM, subject, template }, "Email sending failed");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
35
packages/logger/package.json
Normal file
35
packages/logger/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@kan/logger",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
||||
"dev": "tsc",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint",
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@axiomhq/js": "^1.4.0",
|
||||
"pino": "^9.14.0",
|
||||
"pino-pretty": "^13.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kan/eslint-config": "workspace:*",
|
||||
"@kan/prettier-config": "workspace:*",
|
||||
"@kan/tsconfig": "workspace:*",
|
||||
"eslint": "catalog:",
|
||||
"prettier": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"prettier": "@kan/prettier-config"
|
||||
}
|
||||
43
packages/logger/src/index.ts
Normal file
43
packages/logger/src/index.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Axiom } from "@axiomhq/js";
|
||||
import pino from "pino";
|
||||
|
||||
const isDev = process.env.NODE_ENV !== "production";
|
||||
const isCloud = process.env.NEXT_PUBLIC_KAN_ENV === "cloud";
|
||||
const level = process.env.LOG_LEVEL || (isDev ? "debug" : "info");
|
||||
|
||||
const axiomToken = process.env.AXIOM_TOKEN;
|
||||
const axiomDataset = process.env.AXIOM_DATASET;
|
||||
const useAxiom = isCloud && !!axiomToken && !!axiomDataset;
|
||||
|
||||
function createAxiomStream(token: string, dataset: string): pino.DestinationStream {
|
||||
const client = new Axiom({ token });
|
||||
return {
|
||||
write(msg: string) {
|
||||
try {
|
||||
client.ingest(dataset, [JSON.parse(msg) as Record<string, unknown>]);
|
||||
} catch {
|
||||
// ignore malformed log lines
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const logger = useAxiom
|
||||
? pino(
|
||||
{ level },
|
||||
pino.multistream([
|
||||
{ stream: process.stdout, level },
|
||||
{ stream: createAxiomStream(axiomToken, axiomDataset), level },
|
||||
]),
|
||||
)
|
||||
: pino({
|
||||
level,
|
||||
...(isDev && {
|
||||
transport: {
|
||||
target: "pino-pretty",
|
||||
options: { colorize: true, ignore: "pid,hostname", translateTime: "HH:MM:ss" },
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
export const createLogger = (module: string) => logger.child({ module });
|
||||
6
packages/logger/tsconfig.json
Normal file
6
packages/logger/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "@kan/tsconfig/internal-package.json",
|
||||
"compilerOptions": {},
|
||||
"include": ["*.ts", "src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -5,3 +5,4 @@ export * from "./email";
|
||||
export * from "./dueDateFilters";
|
||||
export * from "./s3";
|
||||
export * from "./mentions";
|
||||
export * from "./sanitize";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user