mirror of
https://github.com/wekan/wekan.git
synced 2026-11-08 03:22:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
246ba44838 | ||
|
|
1eda4f256e | ||
|
|
f83ee4d320 | ||
|
|
e43dc0070f | ||
|
|
579de7d6af | ||
|
|
fb8021ba96 | ||
|
|
fe0e187fa0 | ||
|
|
d1d3beb12d | ||
|
|
e33b3f26d3 | ||
|
|
1211221a0f | ||
|
|
c00c602441 | ||
|
|
47a19e3842 | ||
|
|
73db2a28c3 | ||
|
|
11bed26788 | ||
|
|
6a64d374c6 | ||
|
|
8f15879816 | ||
|
|
74339a8a82 | ||
|
|
36a1939653 | ||
|
|
a61a40a5db | ||
|
|
4fd3217aee | ||
|
|
5b436a18dc | ||
|
|
2e87a775ae | ||
|
|
f653d619f5 | ||
|
|
da731aa2fe | ||
|
|
a42b63d711 | ||
|
|
a14923cac3 | ||
|
|
fdcb26096b | ||
|
|
b59152a079 | ||
|
|
1595155af2 | ||
|
|
f4f4c7f2cb | ||
|
|
97cc91cd67 | ||
|
|
33c2ad8fb0 | ||
|
|
c936e7708d | ||
|
|
5c1f6088f4 | ||
|
|
ceb20a81fc | ||
|
|
2128e6e02f | ||
|
|
49ee37a3a8 | ||
|
|
19350f2611 | ||
|
|
2306a36814 | ||
|
|
a4d2313a68 | ||
|
|
570cc34552 | ||
|
|
7ae45651dc | ||
|
|
db52764592 | ||
|
|
2b99ffe288 | ||
|
|
b4b130858f | ||
|
|
0275363043 | ||
|
|
42d8341d47 | ||
|
|
a9aaa22592 | ||
|
|
4d5fe63fc4 | ||
|
|
1f12b20291 | ||
|
|
bf8ecbbc19 | ||
|
|
e89a3450cd | ||
|
|
7e4483b1a9 | ||
|
|
a4eacfd9bf | ||
|
|
b4cb989391 | ||
|
|
60377a4dc2 | ||
|
|
5c45d55d70 | ||
|
|
e5a84cbcef | ||
|
|
1ef7a57ce4 | ||
|
|
9caf0c8653 | ||
|
|
adb73fd8ee | ||
|
|
ea5724babd | ||
|
|
b895e61237 | ||
|
|
3108e017fc | ||
|
|
081690875d | ||
|
|
68b575637d | ||
|
|
6d5f660c27 | ||
|
|
f54aa93dc1 | ||
|
|
e3458004ec | ||
|
|
6ae4ad3545 | ||
|
|
b0a71ba57b | ||
|
|
f3d013339c | ||
|
|
95ea04b049 | ||
|
|
3b33c7d7ba | ||
|
|
4762bed8c1 | ||
|
|
029ecb6b39 | ||
|
|
49c309b324 | ||
|
|
e09267cc90 | ||
|
|
b46f155fa3 | ||
|
|
0c0288a6c5 | ||
|
|
c61a1b7f41 | ||
|
|
9ce35869a7 | ||
|
|
2017d07e37 | ||
|
|
9ea5c81932 | ||
|
|
45ccdd9c3c | ||
|
|
5a21b074d4 | ||
|
|
e9c5888485 | ||
|
|
09c9d72398 | ||
|
|
4f3d45f3de | ||
|
|
3aaa46fdae | ||
|
|
59f1b705e0 | ||
|
|
22e5df55d7 | ||
|
|
016b4bf3bd | ||
|
|
4d44349ab6 | ||
|
|
d3edc56170 | ||
|
|
5134e3d2a8 | ||
|
|
f686b76023 | ||
|
|
cd2adcad02 | ||
|
|
0766a65fef | ||
|
|
58ad81e301 | ||
|
|
2912f4b080 | ||
|
|
97d1eca09d | ||
|
|
95b75177b8 | ||
|
|
880ec2d707 | ||
|
|
dbc7cccea3 | ||
|
|
d8b3579117 | ||
|
|
c7e53e9f38 | ||
|
|
6012521ccc | ||
|
|
0195a5b3d4 | ||
|
|
b12e761afb | ||
|
|
0c0d666ffa | ||
|
|
fc2ad0a48b | ||
|
|
dad03959d9 | ||
|
|
ac960e7d81 | ||
|
|
3213d7fa1d | ||
|
|
4757676abf | ||
|
|
efaf94242e | ||
|
|
d87baa0244 | ||
|
|
006d24502c | ||
|
|
8f4efbca57 | ||
|
|
dde09dead5 | ||
|
|
3621413d6f | ||
|
|
3c3fc07b3c | ||
|
|
c1e8976b28 | ||
|
|
dcce935310 | ||
|
|
1b05673200 | ||
|
|
dafb63299f | ||
|
|
84b0480aa8 | ||
|
|
f6fc97e77a | ||
|
|
d7105e531d | ||
|
|
f25957dcf3 | ||
|
|
984ea93aa8 | ||
|
|
2441563008 | ||
|
|
f3950b9adc | ||
|
|
5e07b8db6c | ||
|
|
eeeb0dbec9 | ||
|
|
96d56ecc6c | ||
|
|
aadfd9b6b3 | ||
|
|
1c44876beb | ||
|
|
bd86b518ea | ||
|
|
f576a0cca9 | ||
|
|
46c0ad43e8 | ||
|
|
499772e4c2 | ||
|
|
90fe133363 | ||
|
|
b812d36871 | ||
|
|
d05a42a5af | ||
|
|
42507de1d0 | ||
|
|
131b0cc509 | ||
|
|
93ae5ac6ee | ||
|
|
34bb91d1f5 | ||
|
|
d231df56fc | ||
|
|
e422ab691e | ||
|
|
3f712a0445 | ||
|
|
d99dadadee | ||
|
|
a58b7bee2c | ||
|
|
62da564a1e |
No files matched your search
@@ -174,7 +174,7 @@ jobs:
|
||||
|
||||
- name: Build and start in NetBSD
|
||||
if: steps.node.outputs.skip != 'true' && matrix.os == 'netbsd'
|
||||
uses: vmactions/netbsd-vm@c8a0d7ddb84619a7a8cc4e652efa7950b3fd9f92 # v1.5.2
|
||||
uses: vmactions/netbsd-vm@5957cf330aef2e6ddc63c604364c99ce2e02b91d # v1.5.3
|
||||
with:
|
||||
release: ${{ matrix.release }}
|
||||
sync: rsync
|
||||
|
||||
@@ -2624,7 +2624,12 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
needs: [prepare, build-amd64, release]
|
||||
runs-on: macos-14 # macos-14 runners are Apple Silicon (arm64)
|
||||
# Apple Silicon (arm64). It was `macos-14`, and GitHub RETIRED that image:
|
||||
# the v12.98 run's build-mac-arm64 was cancelled with "The configuration
|
||||
# 'macos-14-arm64-us-default' is not supported", so v12.98 shipped no
|
||||
# mac-arm64 bundle. macos-15 is Apple Silicon too (the same label mac.yml
|
||||
# already builds on).
|
||||
runs-on: macos-15
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
@@ -2900,7 +2905,8 @@ jobs:
|
||||
contents: write
|
||||
needs: [prepare, build-amd64, release]
|
||||
# Intel (x86_64) macOS. It was `macos-13`, and GitHub RETIRED that image -
|
||||
# actions/runner-images publishes macos-14, macos-15 and macos-26 only, and
|
||||
# actions/runner-images publishes macos-15 and macos-26 only (macos-14 was
|
||||
# retired later too), and
|
||||
# macos-13 is gone from the hosted-runner table. A retired label does not
|
||||
# fail: the job QUEUES FOREVER waiting for a runner that will never come, so
|
||||
# every release since then had a build-mac-x64 sitting there for hours until
|
||||
@@ -2908,8 +2914,8 @@ jobs:
|
||||
# what skipped the charts jobs (they had not started yet).
|
||||
# No release has ever carried a wekan-<version>-mac-x64.zip because of it.
|
||||
# Intel macOS is still offered, under new labels: macos-15-intel and
|
||||
# macos-26-intel (macos-14/15/26 without the suffix are Apple Silicon). This
|
||||
# is the same label .tools/TSC already builds its Intel Mac on.
|
||||
# macos-26-intel (macos-15/26 without the suffix are Apple Silicon). This
|
||||
# is the same label .tools/Secretchronicles/TSC already builds its Intel Mac on.
|
||||
runs-on: macos-15-intel
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -5070,13 +5076,29 @@ jobs:
|
||||
[ -e "$1" ] || return 1
|
||||
case "$logs_before" in *" $1 "*) [ "$1" -nt "$attempt_marker" ] ;; *) return 0 ;; esac
|
||||
}
|
||||
# snapcraft's execution logs. v12.96 s390x: every attempt printed only
|
||||
# "Could not find repository at path ...", because snapcraft reports
|
||||
# the LAST exception; the cause before it - "HTTP Error 503: Service
|
||||
# Unavailable" / "Error: Timeout" creating that repository - was in
|
||||
# the "Full execution log" alone. Unread, three Launchpad outages
|
||||
# counted as three WeKan failures and the arch was not published.
|
||||
snapcraft_log_dir="$HOME/.local/state/snapcraft/log"
|
||||
infra_failure() { # did THIS attempt fail on Launchpad, not on WeKan?
|
||||
grep -qsE 'HTTP Error 5[0-9][0-9]|Error: Timeout|Service Unavailable' "$remote_log" && return 0
|
||||
# The same v12.96 s390x run's last attempt: the repository the
|
||||
# timed-out requests had half-created refused our push with
|
||||
# "You do not have permission to create refs/heads/main" - the
|
||||
# same credentials pushed every other architecture in that run.
|
||||
grep -qsE 'remote: You do not have permission to create refs/' "$remote_log" && return 0
|
||||
local log
|
||||
for log in snapcraft-wekan-*.txt; do
|
||||
this_attempts_log "$log" || continue
|
||||
grep -qsE 'RPC failed;|GnuTLS recv error|fatal: early EOF|Gateway error \[IP: ' "$log" && return 0
|
||||
done
|
||||
for log in "$snapcraft_log_dir"/*.log; do
|
||||
this_attempts_log "$log" || continue
|
||||
grep -qsE 'HTTP Error 5[0-9][0-9]|Error: Timeout|Service Unavailable' "$log" && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
# Set once an attempt ends with Launchpad reporting the build as
|
||||
@@ -5099,7 +5121,7 @@ jobs:
|
||||
# earlier attempts' logs stay in the directory for the diagnostics
|
||||
# below, but must not be printed again as if they were this one's.
|
||||
touch "$attempt_marker"
|
||||
logs_before=" $(ls snapcraft-wekan-*.txt 2>/dev/null | tr '\n' ' ')"
|
||||
logs_before=" $(ls snapcraft-wekan-*.txt "$snapcraft_log_dir"/*.log 2>/dev/null | tr '\n' ' ')"
|
||||
if [ "$fresh_next" = 1 ]; then
|
||||
echo "The previous attempt's Launchpad build finished without a snap; submitting a new build instead of recovering it."
|
||||
run_remote_build && rc=0 || rc=$?
|
||||
|
||||
@@ -32,6 +32,6 @@ jobs:
|
||||
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
|
||||
- name: Run chart-releaser
|
||||
uses: helm/chart-releaser-action@df287104677278f869ca5480bd260358397cc3f9 # v1.8.0
|
||||
uses: helm/chart-releaser-action@92c6b97fa9f664492a2592e0d1d80bc6bf378957 # v1.9.0
|
||||
env:
|
||||
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
+2
-2
@@ -72,8 +72,8 @@ test-results/
|
||||
# of them - it replaced an entry per clone (/FerretDB/, /node/, /mongo-tools/,
|
||||
# /mongosh/, /sandstorm/, /projects/, and the WeKan variants), which had to be
|
||||
# extended by hand for every new one. build.sh's ensure_tool_repo clones what a
|
||||
# test run needs into here: .tools/FerretDB, .tools/node-patches,
|
||||
# .tools/mongo-tools-patches. Nothing in here is WeKan's source, and Meteor is
|
||||
# test run needs into here: .tools/w/FerretDB, .tools/w/node-patches,
|
||||
# .tools/w/mongo-tools-patches. Nothing in here is WeKan's source, and Meteor is
|
||||
# told the same by /.tools/ in .meteorignore.
|
||||
.tools/
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Work out which you are from the git identity (the next section says how), then:
|
||||
| Who | Where the work goes |
|
||||
| --- | --- |
|
||||
| **The maintainer** — `Lauri Ojansivu <x@xet7.org>` | **Directly on `main`.** Never a feature branch, never a topic branch, never a pull request. |
|
||||
| **A contributor** — anybody else | **A branch in your own fork, then a pull request** for the maintainer to review. Never a commit on `main`. |
|
||||
| **A contributor** — anybody else | **A branch in your own fork, then one pull request per feature or fix** for the maintainer to review. Never a commit on `main`, and no changelog changes. |
|
||||
|
||||
Nothing else is a third option. If a tool, a task runner or an agent harness offers
|
||||
some other arrangement — a topic branch for the maintainer, a direct push for a
|
||||
@@ -41,7 +41,9 @@ released section links a commit that release does not contain.
|
||||
|
||||
The contributor's half is review. Nobody but the maintainer commits to `main` in
|
||||
wekan/wekan, so a change from anyone else arrives as a pull request — which is also
|
||||
the only place it can be discussed before it lands.
|
||||
the only place it can be discussed before it lands. That is why each pull request
|
||||
holds one feature or one fix, and leaves the changelogs to the maintainer: see
|
||||
"Contributor mode" below.
|
||||
|
||||
Check which branch you are on before committing:
|
||||
|
||||
@@ -143,12 +145,29 @@ git config user.name && git config user.email
|
||||
already exists, on <https://wekan.fi/sponsors>, and belongs nowhere else. The **publishing / release
|
||||
steps** below are available to a human maintainer only: an AI stops after
|
||||
committing locally and never runs a step that pushes or publishes.
|
||||
|
||||
**Maintainer mode is the only mode that commits directly and the only mode that
|
||||
updates changelogs**, in WeKan and in the maintainer's repositories under
|
||||
`.tools/` alike. It applies to the changes the WeKan maintainer makes, and to the
|
||||
changes of an agent working as the maintainer according to this file.
|
||||
- **Contributor mode** — the identity is somebody ELSE, in a fork or a clone of your
|
||||
own — a person, or an AI that files pull requests on its own account. Then: do
|
||||
**not** commit to `main` and do **not** run any release/publishing step. Make the
|
||||
changes on a branch and open a **pull request** for the maintainer to review. The
|
||||
"commit as Lauri Ojansivu", "commit directly to `main`", and all release
|
||||
instructions below are **maintainer-only and do not apply to you**.
|
||||
**not** commit directly to the repository or to `main`, and do **not** run any
|
||||
release/publishing step. Make the changes on a branch and open a **pull request**
|
||||
for the maintainer to review. The "commit as Lauri Ojansivu", "commit directly to
|
||||
`main`", changelog and release instructions below are **maintainer-only and do
|
||||
not apply to you**.
|
||||
|
||||
Every pull request, to wekan/wekan or to a maintainer's repository under `.tools/`,
|
||||
follows two more rules:
|
||||
- **One feature or one fix per pull request.** Changes share a pull request only
|
||||
when they are related to that one feature or fix. Anything unrelated, however
|
||||
small, goes in a pull request of its own, so each can be reviewed, merged or
|
||||
declined without holding up the others.
|
||||
- **No changelog changes.** Do not edit `CHANGELOG-big.md`, `CHANGELOG.md`, or the
|
||||
`CHANGELOG.md` or `CHANGELOG` of a repository under `.tools/` — not an Upcoming
|
||||
entry, not TODO Later. Describe the change in the pull request instead. The
|
||||
maintainer writes the entry after merging, when there is a commit for it to link.
|
||||
|
||||
Who that pull request credits follows the attribution rule above, and the two
|
||||
cases differ:
|
||||
@@ -171,8 +190,8 @@ The one repository under `.tools/` that is somebody else's is `sandstorm-io/sand
|
||||
cloned for reference only.
|
||||
|
||||
Everything below marked as maintainer-specific (committing directly, the exact commit
|
||||
author, and the entire "Making a release" / publishing flow) applies only in maintainer
|
||||
mode. The rest (translation policy, CHANGELOG structure, tests, validating from code) is
|
||||
author, updating changelogs, and the entire "Making a release" / publishing flow) applies
|
||||
only in maintainer mode. The rest (translation policy, tests, validating from code) is
|
||||
good practice for everyone.
|
||||
|
||||
## Process: "Fix open issues"
|
||||
@@ -206,6 +225,9 @@ When asked to fix an open issue (one issue at a time):
|
||||
|
||||
Fixes #1234,
|
||||
```
|
||||
|
||||
A contributor instead commits on a branch for one pull request that fixes this
|
||||
one issue, with no changelog changes (see "Contributor mode" above).
|
||||
6. **If the bug is already fixed**, still add a commit that CLOSES the issue (a commit
|
||||
whose message ends `Fixes #1234,`), noting where/when it was fixed.
|
||||
|
||||
@@ -357,7 +379,7 @@ directly after the merge.
|
||||
tool that reads it sees every file twice and reports the bundler's rewritten
|
||||
code as if it were source. That is not hypothetical — a source-scanning test
|
||||
did exactly that and failed on generated code nobody can edit.
|
||||
- `.tools/wekan.fi` — the WeKan website companion repository.
|
||||
- `.tools/w/wekan.fi` — the WeKan website companion repository.
|
||||
- `.tools/` — everything that is NOT part of this repository but is needed to
|
||||
build, test and release it, in ONE directory that `.gitignore` and
|
||||
`.meteorignore` already exclude (each used to need its own ignore entry at
|
||||
@@ -371,10 +393,10 @@ directly after the merge.
|
||||
|
||||
| Path | Repository | Branch | What it is |
|
||||
| --- | --- | --- | --- |
|
||||
| `.tools/FerretDB` | wekan/FerretDB | `main-v1` | the FerretDB v1 fork WeKan ships as its default database |
|
||||
| `.tools/node-patches` | wekan/node-patches | `main` | patches to upstream Node.js; builds the `node-<platform>` binaries the bundles, the Docker image and the snap embed |
|
||||
| `.tools/mongo-tools-patches` | wekan/mongo-tools-patches | `main` | patches to the MongoDB Database Tools; builds `mongo-tools-<arch>`, all eight tools as one program |
|
||||
| `.tools/TSC` | Secretchronicles/TSC | `devel` | the game xet7 also maintains — same author and maintainer mode, its own GNU ChangeLog and release process |
|
||||
| `.tools/w/FerretDB` | wekan/FerretDB | `main-v1` | the FerretDB v1 fork WeKan ships as its default database |
|
||||
| `.tools/w/node-patches` | wekan/node-patches | `main` | patches to upstream Node.js; builds the `node-<platform>` binaries the bundles, the Docker image and the snap embed |
|
||||
| `.tools/w/mongo-tools-patches` | wekan/mongo-tools-patches | `main` | patches to the MongoDB Database Tools; builds `mongo-tools-<arch>`, all eight tools as one program |
|
||||
| `.tools/Secretchronicles/TSC` | Secretchronicles/TSC | `devel` | the game xet7 also maintains — same author and maintainer mode, its own GNU ChangeLog and release process |
|
||||
| `.tools/sandstorm` | sandstorm-io/sandstorm | — | upstream Sandstorm, when present — somebody else's project, cloned for reference |
|
||||
|
||||
**Unpacked toolchains and caches.** Downloads, not repositories — put there by
|
||||
@@ -401,12 +423,16 @@ directly after the merge.
|
||||
of those repositories goes in that repository's own `README.md` or `docs/`,
|
||||
never in a new instruction file — and if you find one there, remove it rather
|
||||
than updating it.
|
||||
- `.tools/FerretDB` specifics — FerretDB `.go` files must contain **no**
|
||||
- `.tools/w/FerretDB` specifics — FerretDB `.go` files must contain **no**
|
||||
application-specific names (say "the client" / "a Meteor 3 driver" / a bare
|
||||
`#NNNN`); its `CHANGELOG.md` may use `wekan/wekan#NNNN`.
|
||||
|
||||
### CHANGELOG
|
||||
|
||||
- **[maintainer only] Changelogs are updated only in maintainer mode.** This whole
|
||||
section is for the maintainer and for an agent working as the maintainer, in WeKan
|
||||
and in the repositories under `.tools/`. A pull request contributor changes no
|
||||
changelog in any of them (see "Contributor mode" above).
|
||||
- **Every repository writes its changelog in the format that repository's own file
|
||||
already uses.** Open its changelog, read the entries above the place you are
|
||||
adding one, and match them — never import another project's shape into it. The
|
||||
@@ -808,7 +834,7 @@ have cost a released section its accuracy:
|
||||
during ordinary development, and the next run takes the next number.
|
||||
What decides it is the GitHub Release, not the tag: `prepare` pushes the tag early,
|
||||
so a tag can exist for a release that never published.
|
||||
- FerretDB: run `./build.sh release-ferretdb` from `.tools/FerretDB` (no
|
||||
- FerretDB: run `./build.sh release-ferretdb` from `.tools/w/FerretDB` (no
|
||||
version). It renames `## Upcoming FerretDB release` to the next version with the
|
||||
correct git-tag link, commits + tags + pushes, then triggers `release-all.yml` (which
|
||||
in turn triggers `docker.yml` for the multi-arch image). It refuses to re-release an
|
||||
@@ -817,7 +843,7 @@ have cost a released section its accuracy:
|
||||
### Security issues
|
||||
|
||||
- Fix the vulnerability, add a CRITICAL section to the WeKan CHANGELOG like previous
|
||||
entries, and update `.tools/wekan.fi/hall-of-fame/index.html` and the vuln-name
|
||||
entries (maintainer mode only, as with every changelog change), and update `.tools/w/wekan.fi/hall-of-fame/index.html` and the vuln-name
|
||||
subdirectory `index.html` like previous security issues.
|
||||
- **Every security fix gets a TEST and a NEGATIVE TEST, and they are written so the
|
||||
fault cannot exist ANYWHERE in the codebase — not just at the place it was
|
||||
@@ -916,7 +942,7 @@ have cost a released section its accuracy:
|
||||
(they are one icon) — the table itself stays percentage-sized with no fixed or
|
||||
minimum width, so it fills the page at every browser width.
|
||||
- **A report that arrives WITHOUT a GHSA is upgraded when its GHSA arrives.** The
|
||||
stars follow `.tools/wekan.fi/hall-of-fame/Audit-Stars.md`: a GHSA with a
|
||||
stars follow `.tools/w/wekan.fi/hall-of-fame/Audit-Stars.md`: a GHSA with a
|
||||
suggested fix is 5, a report sent to security@wekan.fi or any other way, with a
|
||||
suggested fix, is 3. When that researcher is later able to add a GitHub security
|
||||
advisory for the same report, the maintainer requests the CVE through the GHSA
|
||||
|
||||
+1012
File diff suppressed because it is too large.
Load diff
+36
-26
@@ -5,43 +5,53 @@ each one changed and the project status are in
|
||||
Written by `node releases/changelog-short.mjs` from CHANGELOG-big.md:
|
||||
make changes there.
|
||||
|
||||
# v12.90 2026-11-03 WeKan ® release
|
||||
# v13.01 2026-11-08 WeKan ® release
|
||||
|
||||
The login page can have its own **background color**, Member
|
||||
Settings
|
||||
has a per-member switch, **Show focus outline**, to hide the blue focus ring,
|
||||
and
|
||||
**Escape closes the add-list composer**.
|
||||
Translations: the strings still in English are written in **33
|
||||
more languages**, **Sardinian** is Sardinian instead of Italian and **Moroccan
|
||||
Arabic** is Darija instead of Persian. Dependabot's updates of **mailparser**,
|
||||
**isomorphic-git**, **pdfjs-dist** and the S3 libraries are checked and kept,
|
||||
with the S3 client brought to the version the upload library needs.
|
||||
|
||||
[All changes in v12.90](CHANGELOG-big.md#v1290-2026-11-03-wekan--release)
|
||||
[All changes in v13.01](CHANGELOG-big.md#v1301-2026-11-08-wekan--release)
|
||||
|
||||
# v12.89 2026-11-03 WeKan ® release
|
||||
# v13.00 2026-11-07 WeKan ® release
|
||||
|
||||
Keyboard **shortcuts run once on a non-Latin keyboard**: `d` no
|
||||
longer opens the Due date popup and closes it again on a Farsi layout.
|
||||
**CreateBleed** is fixed: "Only admins can create boards" now
|
||||
holds on every path that creates a board - the browser console, the REST API,
|
||||
imports, copies, clones and cards - not only on the Add Board form. The **REST
|
||||
API documentation** spells the name **WeKan**, so the API pages published on
|
||||
wekan.fi pass its brand-spelling check again.
|
||||
|
||||
[All changes in v12.89](CHANGELOG-big.md#v1289-2026-11-03-wekan--release)
|
||||
[All changes in v13.00](CHANGELOG-big.md#v1300-2026-11-07-wekan--release)
|
||||
|
||||
# v12.88 2026-11-03 WeKan ® release
|
||||
# v12.99 2026-11-06 WeKan ® release
|
||||
|
||||
**Every board theme** and **every color a member chooses** is
|
||||
readable: the dark themes' **Card Settings**, popups and sidebars, the header of
|
||||
eight themes, and a custom accent, text or label color made legible. A padded
|
||||
**REST label** name no longer creates a second label.
|
||||
The **mac-arm64** release bundle builds again: its job asked for
|
||||
GitHub's retired **macos-14** runner and was cancelled in the v12.98 release
|
||||
run. The **weba** instructions now live in the weba repository itself.
|
||||
|
||||
[All changes in v12.88](CHANGELOG-big.md#v1288-2026-11-03-wekan--release)
|
||||
[All changes in v12.99](CHANGELOG-big.md#v1299-2026-11-06-wekan--release)
|
||||
|
||||
# v12.87 2026-11-03 WeKan ® release
|
||||
# v12.98 2026-11-06 WeKan ® release
|
||||
|
||||
A **REST API** "get one" that finds nothing answers **404**
|
||||
instead of 200 with an empty body.
|
||||
**GroupLinkBleed** is fixed: a board admin can no longer skip the
|
||||
board methods by writing the board directly, to attach an organization or team
|
||||
they do not belong to or to add a member past the invitation checks.
|
||||
**Public board CSV and TSV exports** honour the selected columns, and the REST
|
||||
**card archive, restore and custom-field** endpoints answer an unmatched card
|
||||
with a JSON 404 instead of 500, and a REST **card update** can set sort 0, turn
|
||||
overtime off and clear a description. The **notifications drawer** gets its
|
||||
attachments again, and the developer instructions cover the **weba** Gambas
|
||||
companion repository.
|
||||
|
||||
[All changes in v12.87](CHANGELOG-big.md#v1287-2026-11-03-wekan--release)
|
||||
[All changes in v12.98](CHANGELOG-big.md#v1298-2026-11-06-wekan--release)
|
||||
|
||||
# v12.86 2026-11-02 WeKan ® release
|
||||
# v12.97 2026-11-06 WeKan ® release
|
||||
|
||||
The daily **notification cleanup** and the **file integrity
|
||||
audit**
|
||||
run once for the whole installation, not once per server.
|
||||
`build.sh` and the `releases/` scripts now work on **Arch Linux**
|
||||
and **Omarchy**, and the release builds no longer lose the **OpenBSD arm64
|
||||
bundle** to a smoke test that left WeKan running, or the **s390x snap** to
|
||||
Launchpad outages that were counted as build failures.
|
||||
|
||||
[All changes in v12.86](CHANGELOG-big.md#v1286-2026-11-02-wekan--release)
|
||||
[All changes in v12.97](CHANGELOG-big.md#v1297-2026-11-06-wekan--release)
|
||||
@@ -26,7 +26,7 @@ Work out which you are from the git identity (the next section says how), then:
|
||||
| Who | Where the work goes |
|
||||
| --- | --- |
|
||||
| **The maintainer** — `Lauri Ojansivu <x@xet7.org>` | **Directly on `main`.** Never a feature branch, never a topic branch, never a pull request. |
|
||||
| **A contributor** — anybody else | **A branch in your own fork, then a pull request** for the maintainer to review. Never a commit on `main`. |
|
||||
| **A contributor** — anybody else | **A branch in your own fork, then one pull request per feature or fix** for the maintainer to review. Never a commit on `main`, and no changelog changes. |
|
||||
|
||||
Nothing else is a third option. If a tool, a task runner or an agent harness offers
|
||||
some other arrangement — a topic branch for the maintainer, a direct push for a
|
||||
@@ -41,7 +41,9 @@ released section links a commit that release does not contain.
|
||||
|
||||
The contributor's half is review. Nobody but the maintainer commits to `main` in
|
||||
wekan/wekan, so a change from anyone else arrives as a pull request — which is also
|
||||
the only place it can be discussed before it lands.
|
||||
the only place it can be discussed before it lands. That is why each pull request
|
||||
holds one feature or one fix, and leaves the changelogs to the maintainer: see
|
||||
"Contributor mode" below.
|
||||
|
||||
Check which branch you are on before committing:
|
||||
|
||||
@@ -143,12 +145,29 @@ git config user.name && git config user.email
|
||||
already exists, on <https://wekan.fi/sponsors>, and belongs nowhere else. The **publishing / release
|
||||
steps** below are available to a human maintainer only: an AI stops after
|
||||
committing locally and never runs a step that pushes or publishes.
|
||||
|
||||
**Maintainer mode is the only mode that commits directly and the only mode that
|
||||
updates changelogs**, in WeKan and in the maintainer's repositories under
|
||||
`.tools/` alike. It applies to the changes the WeKan maintainer makes, and to the
|
||||
changes of an agent working as the maintainer according to this file.
|
||||
- **Contributor mode** — the identity is somebody ELSE, in a fork or a clone of your
|
||||
own — a person, or an AI that files pull requests on its own account. Then: do
|
||||
**not** commit to `main` and do **not** run any release/publishing step. Make the
|
||||
changes on a branch and open a **pull request** for the maintainer to review. The
|
||||
"commit as Lauri Ojansivu", "commit directly to `main`", and all release
|
||||
instructions below are **maintainer-only and do not apply to you**.
|
||||
**not** commit directly to the repository or to `main`, and do **not** run any
|
||||
release/publishing step. Make the changes on a branch and open a **pull request**
|
||||
for the maintainer to review. The "commit as Lauri Ojansivu", "commit directly to
|
||||
`main`", changelog and release instructions below are **maintainer-only and do
|
||||
not apply to you**.
|
||||
|
||||
Every pull request, to wekan/wekan or to a maintainer's repository under `.tools/`,
|
||||
follows two more rules:
|
||||
- **One feature or one fix per pull request.** Changes share a pull request only
|
||||
when they are related to that one feature or fix. Anything unrelated, however
|
||||
small, goes in a pull request of its own, so each can be reviewed, merged or
|
||||
declined without holding up the others.
|
||||
- **No changelog changes.** Do not edit `CHANGELOG-big.md`, `CHANGELOG.md`, or the
|
||||
`CHANGELOG.md` or `CHANGELOG` of a repository under `.tools/` — not an Upcoming
|
||||
entry, not TODO Later. Describe the change in the pull request instead. The
|
||||
maintainer writes the entry after merging, when there is a commit for it to link.
|
||||
|
||||
Who that pull request credits follows the attribution rule above, and the two
|
||||
cases differ:
|
||||
@@ -171,8 +190,8 @@ The one repository under `.tools/` that is somebody else's is `sandstorm-io/sand
|
||||
cloned for reference only.
|
||||
|
||||
Everything below marked as maintainer-specific (committing directly, the exact commit
|
||||
author, and the entire "Making a release" / publishing flow) applies only in maintainer
|
||||
mode. The rest (translation policy, CHANGELOG structure, tests, validating from code) is
|
||||
author, updating changelogs, and the entire "Making a release" / publishing flow) applies
|
||||
only in maintainer mode. The rest (translation policy, tests, validating from code) is
|
||||
good practice for everyone.
|
||||
|
||||
## Process: "Fix open issues"
|
||||
@@ -206,6 +225,9 @@ When asked to fix an open issue (one issue at a time):
|
||||
|
||||
Fixes #1234,
|
||||
```
|
||||
|
||||
A contributor instead commits on a branch for one pull request that fixes this
|
||||
one issue, with no changelog changes (see "Contributor mode" above).
|
||||
6. **If the bug is already fixed**, still add a commit that CLOSES the issue (a commit
|
||||
whose message ends `Fixes #1234,`), noting where/when it was fixed.
|
||||
|
||||
@@ -357,7 +379,7 @@ directly after the merge.
|
||||
tool that reads it sees every file twice and reports the bundler's rewritten
|
||||
code as if it were source. That is not hypothetical — a source-scanning test
|
||||
did exactly that and failed on generated code nobody can edit.
|
||||
- `.tools/wekan.fi` — the WeKan website companion repository.
|
||||
- `.tools/w/wekan.fi` — the WeKan website companion repository.
|
||||
- `.tools/` — everything that is NOT part of this repository but is needed to
|
||||
build, test and release it, in ONE directory that `.gitignore` and
|
||||
`.meteorignore` already exclude (each used to need its own ignore entry at
|
||||
@@ -371,10 +393,10 @@ directly after the merge.
|
||||
|
||||
| Path | Repository | Branch | What it is |
|
||||
| --- | --- | --- | --- |
|
||||
| `.tools/FerretDB` | wekan/FerretDB | `main-v1` | the FerretDB v1 fork WeKan ships as its default database |
|
||||
| `.tools/node-patches` | wekan/node-patches | `main` | patches to upstream Node.js; builds the `node-<platform>` binaries the bundles, the Docker image and the snap embed |
|
||||
| `.tools/mongo-tools-patches` | wekan/mongo-tools-patches | `main` | patches to the MongoDB Database Tools; builds `mongo-tools-<arch>`, all eight tools as one program |
|
||||
| `.tools/TSC` | Secretchronicles/TSC | `devel` | the game xet7 also maintains — same author and maintainer mode, its own GNU ChangeLog and release process |
|
||||
| `.tools/w/FerretDB` | wekan/FerretDB | `main-v1` | the FerretDB v1 fork WeKan ships as its default database |
|
||||
| `.tools/w/node-patches` | wekan/node-patches | `main` | patches to upstream Node.js; builds the `node-<platform>` binaries the bundles, the Docker image and the snap embed |
|
||||
| `.tools/w/mongo-tools-patches` | wekan/mongo-tools-patches | `main` | patches to the MongoDB Database Tools; builds `mongo-tools-<arch>`, all eight tools as one program |
|
||||
| `.tools/Secretchronicles/TSC` | Secretchronicles/TSC | `devel` | the game xet7 also maintains — same author and maintainer mode, its own GNU ChangeLog and release process |
|
||||
| `.tools/sandstorm` | sandstorm-io/sandstorm | — | upstream Sandstorm, when present — somebody else's project, cloned for reference |
|
||||
|
||||
**Unpacked toolchains and caches.** Downloads, not repositories — put there by
|
||||
@@ -401,12 +423,16 @@ directly after the merge.
|
||||
of those repositories goes in that repository's own `README.md` or `docs/`,
|
||||
never in a new instruction file — and if you find one there, remove it rather
|
||||
than updating it.
|
||||
- `.tools/FerretDB` specifics — FerretDB `.go` files must contain **no**
|
||||
- `.tools/w/FerretDB` specifics — FerretDB `.go` files must contain **no**
|
||||
application-specific names (say "the client" / "a Meteor 3 driver" / a bare
|
||||
`#NNNN`); its `CHANGELOG.md` may use `wekan/wekan#NNNN`.
|
||||
|
||||
### CHANGELOG
|
||||
|
||||
- **[maintainer only] Changelogs are updated only in maintainer mode.** This whole
|
||||
section is for the maintainer and for an agent working as the maintainer, in WeKan
|
||||
and in the repositories under `.tools/`. A pull request contributor changes no
|
||||
changelog in any of them (see "Contributor mode" above).
|
||||
- **Every repository writes its changelog in the format that repository's own file
|
||||
already uses.** Open its changelog, read the entries above the place you are
|
||||
adding one, and match them — never import another project's shape into it. The
|
||||
@@ -808,7 +834,7 @@ have cost a released section its accuracy:
|
||||
during ordinary development, and the next run takes the next number.
|
||||
What decides it is the GitHub Release, not the tag: `prepare` pushes the tag early,
|
||||
so a tag can exist for a release that never published.
|
||||
- FerretDB: run `./build.sh release-ferretdb` from `.tools/FerretDB` (no
|
||||
- FerretDB: run `./build.sh release-ferretdb` from `.tools/w/FerretDB` (no
|
||||
version). It renames `## Upcoming FerretDB release` to the next version with the
|
||||
correct git-tag link, commits + tags + pushes, then triggers `release-all.yml` (which
|
||||
in turn triggers `docker.yml` for the multi-arch image). It refuses to re-release an
|
||||
@@ -817,7 +843,7 @@ have cost a released section its accuracy:
|
||||
### Security issues
|
||||
|
||||
- Fix the vulnerability, add a CRITICAL section to the WeKan CHANGELOG like previous
|
||||
entries, and update `.tools/wekan.fi/hall-of-fame/index.html` and the vuln-name
|
||||
entries (maintainer mode only, as with every changelog change), and update `.tools/w/wekan.fi/hall-of-fame/index.html` and the vuln-name
|
||||
subdirectory `index.html` like previous security issues.
|
||||
- **Every security fix gets a TEST and a NEGATIVE TEST, and they are written so the
|
||||
fault cannot exist ANYWHERE in the codebase — not just at the place it was
|
||||
@@ -916,7 +942,7 @@ have cost a released section its accuracy:
|
||||
(they are one icon) — the table itself stays percentage-sized with no fixed or
|
||||
minimum width, so it fills the page at every browser width.
|
||||
- **A report that arrives WITHOUT a GHSA is upgraded when its GHSA arrives.** The
|
||||
stars follow `.tools/wekan.fi/hall-of-fame/Audit-Stars.md`: a GHSA with a
|
||||
stars follow `.tools/w/wekan.fi/hall-of-fame/Audit-Stars.md`: a GHSA with a
|
||||
suggested fix is 5, a report sent to security@wekan.fi or any other way, with a
|
||||
suggested fix, is 3. When that researcher is later able to add a GitHub security
|
||||
advisory for the same report, the maintainer requests the CVE through the GHSA
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ FROM --platform=$BUILDPLATFORM debian:trixie AS bundle
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
ARG VERSION=12.90
|
||||
ARG VERSION=13.01
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
COPY --chmod=755 releases/check-telemetry.py /tmp/check-telemetry.py
|
||||
@@ -151,7 +151,7 @@ ENV \
|
||||
NODE_VERSION=v26.13.0 \
|
||||
METEOR_RELEASE=METEOR@3.6-rc.1 \
|
||||
USE_EDGE=false \
|
||||
NPM_VERSION=12.2.1 \
|
||||
NPM_VERSION=12.2.2 \
|
||||
SRC_PATH=./ \
|
||||
WITH_API=true \
|
||||
MONGO_OPLOG_URL="" \
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v12.90.0"
|
||||
appVersion: "v13.01.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
|
||||
@@ -2068,7 +2068,7 @@ exit /b 0
|
||||
REM ===========================================================================
|
||||
:test_all_databases
|
||||
REM The same thing build.sh's "All databases (sequential)" runs: build the newest
|
||||
REM FerretDB v1 from .tools\FerretDB (cloning wekan/FerretDB there if it is not
|
||||
REM FerretDB v1 from .tools\w\FerretDB (cloning wekan/FerretDB there if it is not
|
||||
REM there, and installing Go and the module dependencies if they are
|
||||
REM missing), then run the whole FerretDB v1 query catalogue against every
|
||||
REM database that has a Docker image for THIS CPU - one at a time, because they
|
||||
@@ -2105,7 +2105,7 @@ REM ===========================================================================
|
||||
:test_ferretdb
|
||||
REM All of FerretDB's own tests, one at a time: unit, vet, integration.
|
||||
REM
|
||||
REM FerretDB lives in .tools\FerretDB - companion repos are kept in one directory
|
||||
REM FerretDB lives in .tools\w\FerretDB - companion repos are kept in one directory
|
||||
REM that .gitignore and .meteorignore already exclude, instead of one ignored
|
||||
REM subdirectory each at the repo root. It is cloned here when it is not there,
|
||||
REM the same as build.sh's ensure_tool_repo does, so neither script depends on
|
||||
@@ -2117,27 +2117,27 @@ if errorlevel 1 (
|
||||
echo ERROR: bash was not found. It comes with Git for Windows ^(Git Bash^) and with WSL.
|
||||
goto end
|
||||
)
|
||||
if not exist "%REPO%\.tools\FerretDB\build.sh" (
|
||||
if not exist "%REPO%\.tools\w\FerretDB\build.sh" (
|
||||
where git >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo ERROR: git was not found, so .tools\FerretDB cannot be cloned.
|
||||
echo ERROR: git was not found, so .tools\w\FerretDB cannot be cloned.
|
||||
echo Install Git for Windows, or clone it by hand:
|
||||
echo git clone git@github.com:wekan/FerretDB .tools/FerretDB
|
||||
echo git clone git@github.com:wekan/FerretDB .tools/w/FerretDB
|
||||
goto end
|
||||
)
|
||||
if not exist "%REPO%\.tools" md "%REPO%\.tools"
|
||||
echo ==^> FerretDB is not in .tools\ yet; cloning wekan/FerretDB
|
||||
git clone git@github.com:wekan/FerretDB "%REPO%\.tools\FerretDB"
|
||||
git clone git@github.com:wekan/FerretDB "%REPO%\.tools\w\FerretDB"
|
||||
if errorlevel 1 (
|
||||
echo ==^> SSH clone failed ^(no key for github.com?^); trying HTTPS.
|
||||
git clone https://github.com/wekan/FerretDB "%REPO%\.tools\FerretDB"
|
||||
git clone https://github.com/wekan/FerretDB "%REPO%\.tools\w\FerretDB"
|
||||
)
|
||||
)
|
||||
if not exist "%REPO%\.tools\FerretDB\build.sh" (
|
||||
echo ERROR: .tools\FerretDB\build.sh is still missing after cloning.
|
||||
if not exist "%REPO%\.tools\w\FerretDB\build.sh" (
|
||||
echo ERROR: .tools\w\FerretDB\build.sh is still missing after cloning.
|
||||
goto end
|
||||
)
|
||||
bash -c "cd .tools/FerretDB && ./build.sh test-all"
|
||||
bash -c "cd .tools/w/FerretDB && ./build.sh test-all"
|
||||
goto end
|
||||
|
||||
REM ===========================================================================
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/bin/bash
|
||||
if [ -n "${ZSH_VERSION:-}" ]; then exec /bin/bash "$0" "$@"; fi
|
||||
|
||||
echo "Recommended for development: Newest Debian or Ubuntu amd64 based distro, directly to SSD disk or dual boot, not VM. Works fast."
|
||||
echo "Recommended for development: Newest Debian, Ubuntu, Fedora or Arch/Omarchy amd64 based distro, directly to SSD disk or dual boot, not VM. Works fast."
|
||||
echo "Note1: If you use other locale than en_US.UTF-8 , you need to additionally install en_US.UTF-8"
|
||||
echo " with 'sudo dpkg-reconfigure locales' , so that MongoDB works correctly."
|
||||
echo " so that MongoDB works correctly: Debian/Ubuntu 'sudo dpkg-reconfigure locales',"
|
||||
echo " Arch/Omarchy: uncomment en_US.UTF-8 in /etc/locale.gen and run 'sudo locale-gen'."
|
||||
echo " You can still use any other locale as your main locale."
|
||||
echo "Note2: Console output is also logged to the operation-specific path printed below."
|
||||
echo "Note3: All logs use .tools/log/<operation>/YYYY-MM-DD_HH-MM-SS/."
|
||||
@@ -139,25 +140,39 @@ if [ -n "$_wekan_node_version" ] && [ -n "$_wekan_node_arch" ] && [ -x "$_wekan_
|
||||
fi
|
||||
export PATH
|
||||
|
||||
# ensure_tool_repo <name> [git-url] - the path to .tools/<name>, cloning it if it
|
||||
# is not there yet. Prints the path on stdout; everything else goes to stderr, so
|
||||
# a caller can do `dir="$(ensure_tool_repo FerretDB)"`.
|
||||
# ensure_tool_repo <name> [git-url] - the path to .tools/<owner>/<name>, cloning
|
||||
# it if it is not there yet. Prints the path on stdout; everything else goes to
|
||||
# stderr, so a caller can do `dir="$(ensure_tool_repo FerretDB)"`.
|
||||
#
|
||||
# The companion repositories are grouped by their GitHub owner, the way they are
|
||||
# cloned: github.com/wekan/* in .tools/w/, github.com/xet7/* in .tools/xet7/ and
|
||||
# github.com/Secretchronicles/* in .tools/Secretchronicles/. tool_repo_dir says
|
||||
# which directory that is, so every script finds a repo in the same place.
|
||||
#
|
||||
# SSH first, HTTPS second: a maintainer has a key and wants to push, and anyone
|
||||
# else still gets a working clone. Returns non-zero when neither works, so the
|
||||
# caller can say what it cannot do rather than cd into nothing.
|
||||
function tool_repo_dir(){
|
||||
local owner="$1" name="$2"
|
||||
[ "$owner" = "wekan" ] && owner="w"
|
||||
printf '%s\n' "$WEKAN_TOOLS_DIR/$owner/$name"
|
||||
}
|
||||
|
||||
function ensure_tool_repo(){
|
||||
local name="$1"
|
||||
local url="${2:-git@github.com:wekan/$name}"
|
||||
local https_url="${url/git@github.com:/https://github.com/}"
|
||||
local dir="$WEKAN_TOOLS_DIR/$name"
|
||||
local owner="${url#*github.com[:/]}"
|
||||
owner="${owner%%/*}"
|
||||
local dir
|
||||
dir="$(tool_repo_dir "$owner" "$name")"
|
||||
|
||||
if [ -d "$dir/.git" ]; then
|
||||
printf '%s\n' "$dir"
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$WEKAN_TOOLS_DIR" || { echo "ERROR: cannot create $WEKAN_TOOLS_DIR" >&2; return 1; }
|
||||
echo "==> $name is not in .tools/ yet; cloning $url" >&2
|
||||
mkdir -p "$(dirname "$dir")" || { echo "ERROR: cannot create $(dirname "$dir")" >&2; return 1; }
|
||||
echo "==> $name is not in ${dir#"$WEKAN_DIR"/} yet; cloning $url" >&2
|
||||
if ! git clone "$url" "$dir" >&2; then
|
||||
echo "==> SSH clone failed (no key for github.com?); trying HTTPS." >&2
|
||||
if ! git clone "$https_url" "$dir" >&2; then
|
||||
@@ -605,6 +620,14 @@ function browser_needs_docker(){
|
||||
if [ "$PLATFORM_OS" = "linux" ] && [ "$PLATFORM_ARCH" = "arm64" ]; then
|
||||
return 0
|
||||
fi
|
||||
# Playwright builds WebKit for Debian/Ubuntu only. On Arch and Omarchy its
|
||||
# WebKit is linked against other ICU/libjpeg/libwebp versions than the
|
||||
# rolling release has and does not launch, while Chromium and Firefox do.
|
||||
# So WebKit alone goes to the Docker image there, when Docker is installed.
|
||||
if [ "$browser" = webkit ] && [ "$PLATFORM_OS" = "linux" ] && \
|
||||
[ "$(_et_linux_family)" = arch ] && docker_available; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -632,8 +655,14 @@ function docker_exec(){
|
||||
# Run a Playwright browser project (chromium / firefox / webkit) inside the
|
||||
# official Playwright container. First arg is the browser; any extra args are
|
||||
# passed through to `playwright test`. WeKan must already be running on the host
|
||||
# (default http://127.0.0.1:3000, Meteor's bundled Mongo on 3001); the container
|
||||
# (default http://localhost:3000, Meteor's bundled Mongo on 3001); the container
|
||||
# shares the host network so it can reach both.
|
||||
#
|
||||
# The browser must use the test server's ROOT_URL origin, http://localhost:3000.
|
||||
# Meteor-Files sends every upload to ROOT_URL + /cdn/storage/.../__upload, and
|
||||
# answers no CORS preflight for another origin, so a page opened on
|
||||
# http://127.0.0.1:3000 could not upload anything: the drawing specs' Save never
|
||||
# finished. Health checks with curl may still use 127.0.0.1; a browser may not.
|
||||
function run_playwright_docker(){
|
||||
local browser="$1"; shift
|
||||
local reporoot="$WEKAN_DIR"
|
||||
@@ -649,7 +678,7 @@ function run_playwright_docker(){
|
||||
pwver="$(node -e "console.log(require('$pwdir/node_modules/@playwright/test/package.json').version)" 2>/dev/null)"
|
||||
[ -z "$pwver" ] && pwver="1.60.0"
|
||||
local image="mcr.microsoft.com/playwright:v${pwver}-noble"
|
||||
local docker_base_url="${WEKAN_BASE_URL:-http://127.0.0.1:3000}"
|
||||
local docker_base_url="${WEKAN_BASE_URL:-http://localhost:3000}"
|
||||
local docker_mongo_url="${WEKAN_MONGO_URL:-mongodb://127.0.0.1:3001/meteor}"
|
||||
local docker_host_proxy_port=""
|
||||
local docker_network="host"
|
||||
@@ -798,7 +827,7 @@ function run_node_e2e_docker(){
|
||||
-e HOME=/repo/.tools/tmp \
|
||||
-e TMPDIR=/repo/.tools/tmp \
|
||||
-e NODE_OPTIONS="${NODE_OPTIONS:-}" \
|
||||
-e WEKAN_BASE_URL="${WEKAN_BASE_URL:-http://127.0.0.1:3000}" \
|
||||
-e WEKAN_BASE_URL="${WEKAN_BASE_URL:-http://localhost:3000}" \
|
||||
-e WEKAN_MONGO_URL="${WEKAN_MONGO_URL:-mongodb://127.0.0.1:3001/meteor}" \
|
||||
-v "$reporoot":/repo -w /repo \
|
||||
"$image" \
|
||||
@@ -825,8 +854,17 @@ function install_playwright_browsers(){
|
||||
browser_needs_docker "$b" || nativeList="$nativeList $b"
|
||||
done
|
||||
if [ -n "$nativeList" ]; then
|
||||
echo "Installing native Playwright browsers:$nativeList (with system deps; may use sudo)."
|
||||
( cd "$pwdir" && export HOME="$reporoot/.tools" && npx playwright install --with-deps $nativeList )
|
||||
if [ "$PLATFORM_OS" = "linux" ] && [ "$(_et_linux_family)" != debian ]; then
|
||||
# `--with-deps` runs apt-get, which only Debian/Ubuntu have. Elsewhere
|
||||
# install the libraries the browsers load with the native package
|
||||
# manager, then the browsers themselves without --with-deps.
|
||||
echo "Installing native Playwright browsers:$nativeList (system deps from the distribution's packages; may use sudo)."
|
||||
ensure_playwright_system_deps
|
||||
( cd "$pwdir" && export HOME="$reporoot/.tools" && npx playwright install $nativeList )
|
||||
else
|
||||
echo "Installing native Playwright browsers:$nativeList (with system deps; may use sudo)."
|
||||
( cd "$pwdir" && export HOME="$reporoot/.tools" && npx playwright install --with-deps $nativeList )
|
||||
fi
|
||||
else
|
||||
echo "All browsers are configured to run via Docker; skipping native browser install."
|
||||
fi
|
||||
@@ -1786,7 +1824,7 @@ function floating_promises_checks(){
|
||||
# not: "Install Playwright browsers" (setup, and the browser stages install what
|
||||
# they need), and "Count tests by category" (a report, not a check).
|
||||
#
|
||||
# FerretDB lives in .tools/FerretDB, cloned on demand by ensure_tool_repo (and by
|
||||
# FerretDB lives in .tools/w/FerretDB, cloned on demand by ensure_tool_repo (and by
|
||||
# releases/db-conformance.sh, which needs it a stage earlier). .tools/ is ignored
|
||||
# by git and by Meteor, so a companion repo cannot reach a commit or a rebuild.
|
||||
function run_everything(){
|
||||
@@ -1858,7 +1896,7 @@ function run_everything(){
|
||||
|
||||
echo
|
||||
echo "### 4/4 FerretDB tests ########################################################"
|
||||
# .tools/FerretDB, cloned here if this is the first run on this machine -
|
||||
# .tools/w/FerretDB, cloned here if this is the first run on this machine -
|
||||
# stage 3 usually got there first, but EVERYTHING must not depend on the
|
||||
# order of its own stages.
|
||||
ferret_dir="$(ensure_tool_repo FerretDB)" || ferret_dir=""
|
||||
@@ -1868,8 +1906,8 @@ function run_everything(){
|
||||
GOFLAGS="$FERRET_GOFLAGS" GOMEMLIMIT="$FERRET_GOMEMLIMIT" \
|
||||
./build.sh test-all ) || ferret_rc=$?
|
||||
else
|
||||
echo "ERROR: .tools/FerretDB/build.sh is missing and could not be cloned."
|
||||
echo " Clone it by hand: git clone git@github.com:wekan/FerretDB .tools/FerretDB"
|
||||
echo "ERROR: .tools/w/FerretDB/build.sh is missing and could not be cloned."
|
||||
echo " Clone it by hand: git clone git@github.com:wekan/FerretDB .tools/w/FerretDB"
|
||||
ferret_rc=1
|
||||
fi
|
||||
|
||||
@@ -2732,8 +2770,8 @@ RELEASE_SCRIPTS=( "Release|Release All: audit, commit, push and build with Actio
|
||||
"Git and repo|Migrate a MongoDB database to FerretDB (--help first)|releases/migrate-mongodb-to-ferretdb.mjs|Arguments, e.g. --help||"
|
||||
"Git and repo|Export local Git history to a new Fossil repository|releases/fossil.sh|||fossil-export"
|
||||
"Git and repo|Open the local Fossil repository UI|releases/fossil-ui.sh|||fossil-ui"
|
||||
"Server and VM|Enable and start the SSH server|!sudo systemctl enable ssh && sudo systemctl start ssh||linux|ssh-start"
|
||||
"Server and VM|Disable and stop the SSH server|!sudo systemctl disable ssh && sudo systemctl stop ssh||linux|ssh-stop"
|
||||
"Server and VM|Enable and start the SSH server|!sudo systemctl enable \"\$(_et_ssh_unit)\" && sudo systemctl start \"\$(_et_ssh_unit)\"||linux|ssh-start"
|
||||
"Server and VM|Disable and stop the SSH server|!sudo systemctl disable \"\$(_et_ssh_unit)\" && sudo systemctl stop \"\$(_et_ssh_unit)\"||linux|ssh-stop"
|
||||
"Server and VM|Enable the ufw firewall|!sudo ufw enable||linux|ufw-enable"
|
||||
"Server and VM|Disable the ufw firewall|!sudo ufw disable||linux|ufw-disable"
|
||||
"Server and VM|Remove the Multipass VM|!multipass stop --all && multipass delete --all && multipass purge && sudo snap remove multipass||linux|multipass-remove"
|
||||
@@ -2999,7 +3037,8 @@ for _once in 1; do
|
||||
sudo apk add --no-cache bash build-base git curl wget p7zip zip unzip file npm
|
||||
;;
|
||||
arch)
|
||||
sudo pacman -Sy --needed --noconfirm base-devel git curl wget p7zip zip unzip file npm
|
||||
# Arch and Omarchy: no `pacman -Sy` partial upgrade (see _et_pacman).
|
||||
_et_pacman base-devel git curl wget 7zip zip unzip file npm
|
||||
;;
|
||||
fedora)
|
||||
sudo dnf group install -y development-tools
|
||||
@@ -3400,7 +3439,7 @@ for _once in 1; do
|
||||
if [ -n "$ferret_dir" ] && [ -x "$ferret_dir/build.sh" ]; then
|
||||
( cd "$ferret_dir" && ./build.sh test-all )
|
||||
else
|
||||
echo "ERROR: .tools/FerretDB/build.sh is missing and could not be cloned."
|
||||
echo "ERROR: .tools/w/FerretDB/build.sh is missing and could not be cloned."
|
||||
fi
|
||||
;;
|
||||
"Run EVERYTHING sequentially: the floating-promises guard (seconds), then WeKan's own tests (mocha, the node suites, import regression, node E2E and all three browsers), then the database conformance run for every database with a Docker image for this CPU, then all of FerretDB's own tests (unit, vet, integration) - one stage at a time, all logs in log/<datetime>/")
|
||||
|
||||
@@ -112,9 +112,10 @@ template(name="allBoardsMultiSelectionSidebar")
|
||||
a.sidebar-btn.js-archive-selected-boards
|
||||
i.fa.fa-archive
|
||||
span {{_ 'archive-board'}}
|
||||
a.sidebar-btn.js-duplicate-selected-boards
|
||||
i.fa.fa-clipboard
|
||||
span {{_ 'duplicate-board'}}
|
||||
if boardCreationAllowed
|
||||
a.sidebar-btn.js-duplicate-selected-boards
|
||||
i.fa.fa-clipboard
|
||||
span {{_ 'duplicate-board'}}
|
||||
a.sidebar-btn.js-export-selected-boards
|
||||
i.fa.fa-download
|
||||
span {{_ 'export-selected-boards'}}
|
||||
|
||||
@@ -87,7 +87,7 @@ template(name="attachmentGallery")
|
||||
.attachment-gallery
|
||||
|
||||
//- #6796: a small "+" like every other section's, not a full-width box.
|
||||
if canModifyCard
|
||||
if canAddAttachment
|
||||
a.card-label.add-label.add-attachment.js-add-attachment(title="{{_ 'add-attachment'}}" aria-label="{{_ 'add-attachment'}}")
|
||||
i.fa.fa-plus
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { attachmentMigrationManager } from '/client/lib/attachmentMigrationManag
|
||||
import Attachments from '/models/attachments';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import { buildAttachmentUploadConfig } from '/client/lib/attachmentUploadConfig';
|
||||
import { attachmentUploadsBlocked } from '/client/lib/attachmentUploadPolicy';
|
||||
const { cleanFileName } = require('/imports/lib/fileNameDisplay');
|
||||
const { attachmentTileCompact } = require('/models/lib/attachmentTile.cjs');
|
||||
import { formatDateTime } from '/imports/lib/dateUtils';
|
||||
@@ -467,6 +468,10 @@ Template.attachmentGallery.onCreated(function () {
|
||||
});
|
||||
|
||||
Template.attachmentGallery.helpers({
|
||||
// #6870: the "+" button - a card the user may edit, unless uploads are blocked site-wide
|
||||
canAddAttachment() {
|
||||
return Utils.canModifyCard() && !attachmentUploadsBlocked();
|
||||
},
|
||||
// #6796: the small file button instead of the preview tile
|
||||
// (models/lib/attachmentTile.cjs).
|
||||
attachmentTileCompact(file) {
|
||||
@@ -672,6 +677,9 @@ export async function handleFileUpload(card, files) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// #6870: the administrator blocked uploads for the whole site; the server refuses them too
|
||||
if (attachmentUploadsBlocked()) return [];
|
||||
|
||||
// Check if board allows attachments
|
||||
const board = card.board();
|
||||
if (!board || !board.allowsAttachments) {
|
||||
|
||||
@@ -185,6 +185,23 @@ body.desktop-mode .card-details:not(.card-details-popup):not(.card-details-colla
|
||||
bottom: 8px;
|
||||
}
|
||||
|
||||
/* A phone in desktop mode: the header bar wraps to several rows (up to five with
|
||||
the board controls open), far taller than the 8px the floating window docks
|
||||
at, so the card's own top row (caret, number, menu, X) sat among the header's
|
||||
rows and was only seen by dragging the card down. Below 801px the card starts
|
||||
BELOW the header, at the height Utils publishes as --wekan-header-height.
|
||||
Only a card WITHOUT an inline top matches (`:not([style*="top"])`), so the
|
||||
place a finger dragged it to is an inline top and is kept when the finger is
|
||||
lifted - this rule never competes with it. */
|
||||
@media screen and (max-width: 800px) {
|
||||
body.desktop-mode .card-details:not(.card-details-popup):not([style*="top"]),
|
||||
/* a card opened as a popup (search result, table, Roadmap bar) docks the same way */
|
||||
body.desktop-mode .card-details.card-details-popup:not([style*="top"]),
|
||||
body.desktop-mode .card-details.card-details-maximized:not(.card-details-popup):not(.card-details-collapsed) {
|
||||
top: calc(var(--wekan-header-height, 0px) + 8px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* #4822: the MAXIMIZED card must be anchored to the VIEWPORT (position: fixed
|
||||
with viewport insets), never laid out inside — or measured from — the
|
||||
scrolled swimlane/board container. Historically the maximized pane was an
|
||||
@@ -250,6 +267,24 @@ body.desktop-mode .card-details.card-details-collapsed {
|
||||
color: #b3b3b3;
|
||||
display: inline-block;
|
||||
margin-inline-end: 6px;
|
||||
/* Above the title's edit zone (z-index 1, over the whole title): a press on the
|
||||
card number moves the window like the rest of the header row; only the title
|
||||
itself edits. */
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* The areas that move the window by a finger. Without touch-action: none the browser
|
||||
takes a finger drag for a scroll and cancels the pointer, so the window never moves
|
||||
(the page behind it scrolls instead). Only in desktop mode, where the card is a
|
||||
window; the full-screen card of mobile mode is not moved. */
|
||||
body.desktop-mode .card-details .card-details-header,
|
||||
body.desktop-mode .card-details .card-details-header .card-drag-handle {
|
||||
touch-action: none;
|
||||
}
|
||||
body.card-window-dragging {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
/* Collapse toggle triangle */
|
||||
@@ -536,6 +571,19 @@ body.desktop-mode .card-details.card-details-collapsed {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
/* A card opened as a POPUP (Session popupCardId: from a search result, a table
|
||||
or a Roadmap/Gantt bar) docks at the very top of the window like every other
|
||||
opened card, and covers the header bars, as the .pop-over[data-popup=
|
||||
"cardDetailsPopup"] rule near the end of this file says it must. The 97px
|
||||
above is the room the header takes for a card that is NOT docked by the
|
||||
desktop-mode rules (those skip popups), and it opened the popup BELOW the
|
||||
header: collapsed, it was a bar floating over the swimlane. Mobile mode keeps
|
||||
its own full-window rule (top: 0 !important). */
|
||||
.card-details.card-details-popup {
|
||||
top: 8px;
|
||||
z-index: 2001;
|
||||
}
|
||||
|
||||
/* Override for mobile mode even on larger screens */
|
||||
body.mobile-mode .card-details {
|
||||
width: 100vw !important;
|
||||
|
||||
@@ -1270,10 +1270,11 @@ template(name="cardDetailsActionsPopup")
|
||||
i.fa.fa-link
|
||||
| {{_ 'cardMorePopup-title'}}
|
||||
unless canModifyCard
|
||||
li
|
||||
a.js-create-board-from-card
|
||||
i.fa.fa-external-link
|
||||
| {{_ 'linkCardToNewBoard'}}
|
||||
if boardCreationAllowed
|
||||
li
|
||||
a.js-create-board-from-card
|
||||
i.fa.fa-external-link
|
||||
| {{_ 'linkCardToNewBoard'}}
|
||||
span.copied-tooltip {{_ 'copied'}}
|
||||
|
||||
//- #1173: the card export popup IS the shared export body - the same checkbox
|
||||
|
||||
@@ -81,6 +81,7 @@ const editingLocationId = new ReactiveVar(null);
|
||||
// Shared with Admin Panel, which links the places accounts log in from the same
|
||||
// way - models/lib/mapLink.js.
|
||||
const { mapLinkFor, GCJ } = require('/models/lib/mapLink');
|
||||
const { cardHeaderPress, dragThreshold, passedThreshold, clampWindow } = require('/models/lib/windowDrag');
|
||||
|
||||
|
||||
|
||||
@@ -1192,6 +1193,66 @@ Template.cardSectionHeader.events({
|
||||
},
|
||||
});
|
||||
|
||||
// Moves the card window under a finger or a mouse. `press` is cardHeaderPress's answer.
|
||||
// A press on a control waits until the pointer has really moved, so a tap still
|
||||
// collapses, closes or opens the menu; once it moved, the click that follows the drag
|
||||
// is swallowed.
|
||||
function beginCardWindowDrag(jqEvent, press) {
|
||||
// Blaze hands over a jQuery event, which does not copy `isPrimary`; the browser's
|
||||
// own event has it.
|
||||
const event = jqEvent.originalEvent || jqEvent;
|
||||
if (!press.start || !event.isPrimary) return;
|
||||
if (event.pointerType === 'mouse' && event.button !== 0) return;
|
||||
|
||||
const $card = $(event.target).closest('.card-details');
|
||||
if (!$card.length) return;
|
||||
|
||||
const startX = event.clientX;
|
||||
const startY = event.clientY;
|
||||
const offset = $card.offset();
|
||||
const startLeft = offset.left - window.scrollX;
|
||||
const startTop = offset.top - window.scrollY;
|
||||
const threshold = dragThreshold(event.pointerType, press.deferred);
|
||||
const pointerId = event.pointerId;
|
||||
let moving = false;
|
||||
|
||||
const onMove = (e) => {
|
||||
if (e.pointerId !== pointerId) return;
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
if (!moving) {
|
||||
if (!passedThreshold(dx, dy, threshold)) return;
|
||||
moving = true;
|
||||
// #6465: where the user put it wins over where the minicard is, so a later
|
||||
// resize re-places every other window but only clamps this one into view.
|
||||
markCardDetailsUserMoved($card);
|
||||
document.body.classList.add('card-window-dragging');
|
||||
}
|
||||
e.preventDefault();
|
||||
const placed = clampWindow(startLeft + dx, startTop + dy, $card.outerWidth(),
|
||||
{ width: window.innerWidth, height: window.innerHeight });
|
||||
$card.css({ left: `${placed.left}px`, top: `${placed.top}px` });
|
||||
};
|
||||
|
||||
const onEnd = (e) => {
|
||||
if (e.pointerId !== pointerId) return;
|
||||
document.removeEventListener('pointermove', onMove);
|
||||
document.removeEventListener('pointerup', onEnd);
|
||||
document.removeEventListener('pointercancel', onEnd);
|
||||
document.body.classList.remove('card-window-dragging');
|
||||
if (moving && press.deferred) {
|
||||
// The drag started on a link or a button: its click is not a tap.
|
||||
const swallow = (click) => { click.preventDefault(); click.stopPropagation(); };
|
||||
document.addEventListener('click', swallow, { capture: true, once: true });
|
||||
setTimeout(() => document.removeEventListener('click', swallow, { capture: true }), 400);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('pointermove', onMove, { passive: false });
|
||||
document.addEventListener('pointerup', onEnd);
|
||||
document.addEventListener('pointercancel', onEnd);
|
||||
}
|
||||
|
||||
Template.cardDetails.events({
|
||||
// #1591 folded each FIELD by its own title, storing a class per card in the
|
||||
// profile. The section carets replaced it: one handle per section, on the
|
||||
@@ -1221,86 +1282,31 @@ Template.cardDetails.events({
|
||||
Users.setPublicCardCollapsed(!currentState);
|
||||
}
|
||||
},
|
||||
'mousedown .js-card-drag-handle'(event) {
|
||||
event.preventDefault();
|
||||
const $card = $(event.target).closest('.card-details');
|
||||
// #6465: where the user put it wins over where the minicard is, so a later
|
||||
// resize re-places every other window but only clamps this one into view.
|
||||
markCardDetailsUserMoved($card);
|
||||
const startX = event.clientX;
|
||||
const startY = event.clientY;
|
||||
const startLeft = $card.offset().left;
|
||||
const startTop = $card.offset().top;
|
||||
|
||||
const onMouseMove = (e) => {
|
||||
const deltaX = e.clientX - startX;
|
||||
const deltaY = e.clientY - startY;
|
||||
$card.css({
|
||||
left: startLeft + deltaX + 'px',
|
||||
top: startTop + deltaY + 'px'
|
||||
});
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
$(document).off('mousemove', onMouseMove);
|
||||
$(document).off('mouseup', onMouseUp);
|
||||
};
|
||||
|
||||
$(document).on('mousemove', onMouseMove);
|
||||
$(document).on('mouseup', onMouseUp);
|
||||
// The opened card's window is moved by a finger as well as a mouse, so these are
|
||||
// Pointer Events (a finger sends no mouse events while it moves, which left the card
|
||||
// fixed on an iPad while the page behind it scrolled) and the areas that start a
|
||||
// drag are `touch-action: none` (cardDetails.css). With drag handles ON the handle
|
||||
// is the only thing that moves the window; with them off the header row moves it,
|
||||
// except the title (it edits). A link or button in the row - the collapse caret, the
|
||||
// menu, the card number's neighbours - still does its job when tapped, and moves the
|
||||
// window when dragged. models/lib/windowDrag.js
|
||||
'pointerdown .js-card-drag-handle'(event) {
|
||||
beginCardWindowDrag(event, { start: true, deferred: false });
|
||||
},
|
||||
'mousedown .js-card-title-drag-handle'(event) {
|
||||
// The title bar drags the window. Don't interfere with text selection.
|
||||
if (
|
||||
$(event.target).closest(
|
||||
'a, input, textarea, button, select, option, .js-card-details-title',
|
||||
).length > 0
|
||||
) {
|
||||
// #6641: while the editor replaces the header, a textarea press still
|
||||
// bubbles to this drag bar. Leave it to the browser so mouse dragging
|
||||
// selects text instead of moving the card window.
|
||||
return;
|
||||
}
|
||||
// #6839: ...and never from the title, which edits - all of it
|
||||
// (cardDetails.css). The window is moved from the empty space of the
|
||||
// header row above it, beside the collapse caret and the menu.
|
||||
if ($(event.target).closest('.card-details-title-edit-zone').length > 0) {
|
||||
return;
|
||||
}
|
||||
// The drag handle has its own handler; without this both would run and the
|
||||
// window would be moved twice by one press. And when there IS a handle -
|
||||
// drag handles on - it is the only thing that moves the window, which is
|
||||
// what turning handles on means.
|
||||
if ($(event.target).closest('.js-card-drag-handle').length > 0
|
||||
|| $(event.target).closest('.card-details').find('.js-card-drag-handle').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
const $card = $(event.target).closest('.card-details');
|
||||
// #6465: same as the drag handle above — a dragged window keeps its place.
|
||||
markCardDetailsUserMoved($card);
|
||||
const startX = event.clientX;
|
||||
const startY = event.clientY;
|
||||
const startLeft = $card.offset().left;
|
||||
const startTop = $card.offset().top;
|
||||
|
||||
const onMouseMove = (e) => {
|
||||
const deltaX = e.clientX - startX;
|
||||
const deltaY = e.clientY - startY;
|
||||
$card.css({
|
||||
left: startLeft + deltaX + 'px',
|
||||
top: startTop + deltaY + 'px'
|
||||
});
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
$(document).off('mousemove', onMouseMove);
|
||||
$(document).off('mouseup', onMouseUp);
|
||||
};
|
||||
|
||||
$(document).on('mousemove', onMouseMove);
|
||||
$(document).on('mouseup', onMouseUp);
|
||||
'pointerdown .js-card-title-drag-handle'(event) {
|
||||
const target = event.target;
|
||||
const closest = selector => (target.closest ? target.closest(selector) : null);
|
||||
const press = cardHeaderPress({
|
||||
onEditable: !!closest('input, textarea, select, option, .js-card-details-title'),
|
||||
onTitleEditZone: !!closest('.card-details-title-edit-zone'),
|
||||
// The handle has its own handler above; this one steps aside for it.
|
||||
onHandle: false,
|
||||
cardHasHandle: !!closest('.js-card-drag-handle')
|
||||
|| !!(closest('.card-details') && closest('.card-details').querySelector('.js-card-drag-handle')),
|
||||
onControl: !!closest('a, button'),
|
||||
});
|
||||
if (closest('.js-card-drag-handle')) return;
|
||||
beginCardWindowDrag(event, press);
|
||||
},
|
||||
'click .js-close-card-details'(event) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -419,18 +419,27 @@ template(name="minicard")
|
||||
.minicard-location
|
||||
i.fa.fa-map-marker
|
||||
| {{#if name}}{{name}}{{else}}{{address}}{{/if}}
|
||||
//- #6872: the + button on the opened card picks board members into
|
||||
requesters / assigners; the free-text name is optional. Show both,
|
||||
as the opened card does, or a picked member never reached here.
|
||||
if $eq field "requestedBy"
|
||||
unless $eq currentBoard.allowsRequestedByOnMinicard false
|
||||
if getRequestedBy
|
||||
if hasRequestedByOnMinicard
|
||||
.minicard-requested-by(title="{{_ 'requested-by'}}")
|
||||
i.fa.fa-shopping-cart
|
||||
| {{ getRequestedBy }}
|
||||
each userId in getRequesters
|
||||
+userAvatar(userId=userId noRemove=true)
|
||||
if getRequestedBy
|
||||
span {{ getRequestedBy }}
|
||||
if $eq field "assignedBy"
|
||||
unless $eq currentBoard.allowsAssignedByOnMinicard false
|
||||
if getAssignedBy
|
||||
if hasAssignedByOnMinicard
|
||||
.minicard-assigned-by(title="{{_ 'assigned-by'}}")
|
||||
i.fa.fa-user-plus
|
||||
| {{ getAssignedBy }}
|
||||
each userId in getAssigners
|
||||
+userAvatar(userId=userId noRemove=true)
|
||||
if getAssignedBy
|
||||
span {{ getAssignedBy }}
|
||||
if $eq field "flowtime"
|
||||
if currentBoard.allowsFlowtimeOnMinicard
|
||||
if isFlowActive
|
||||
|
||||
@@ -261,6 +261,13 @@ Template.minicard.helpers({
|
||||
isWatching() {
|
||||
return this.getRealCard().findWatcher(Meteor.userId());
|
||||
},
|
||||
// #6872: a picked member (requesters / assigners) or a free-text name.
|
||||
hasRequestedByOnMinicard() {
|
||||
return !!this.getRequestedBy() || (this.getRequesters() || []).length > 0;
|
||||
},
|
||||
hasAssignedByOnMinicard() {
|
||||
return !!this.getAssignedBy() || (this.getAssigners() || []).length > 0;
|
||||
},
|
||||
|
||||
showMembers() {
|
||||
const board = minicardBoardOf(this);
|
||||
|
||||
@@ -20,6 +20,18 @@
|
||||
background: #2573a7;
|
||||
z-index: 17;
|
||||
}
|
||||
/* An opened card covers the header bars, in every browser. iOS WebKit - iPad Safari and
|
||||
iPad Chrome, which is WebKit too - makes every SCROLLING element (#content, the board
|
||||
wrapper) a stacking context of its own. The card is inside them, so its z-index (2001)
|
||||
is only compared there, while the header bar (#header-quick-access, z-index 1000, a
|
||||
flex item of body and so a stacking context itself) is painted over all of it: the
|
||||
card's top row - the collapse caret, the card number, the menu, maximize and X - sat
|
||||
BEHIND the header bar. While a card is open the header drops to the level of the
|
||||
content, which comes after it in the page and so paints over it; a browser without that
|
||||
trap shows no difference. */
|
||||
body:has(.card-details) #header-quick-access {
|
||||
z-index: 0;
|
||||
}
|
||||
#header #header-main-bar {
|
||||
/* Flex row (was float-based) so the right button group — which ends with the
|
||||
sidebar hamburger — is always pinned to the right edge at every window width,
|
||||
|
||||
@@ -69,6 +69,19 @@ template(name="header")
|
||||
div.header-logo
|
||||
img(src="{{pathFor '/logo-header.png'}}" alt="{{headerLogoAlt}}" title="{{headerLogoAlt}}")
|
||||
|
||||
//- Full screen, to the right of the logo (iPad Safari, desktop and Android
|
||||
browsers). Only drawn where the browser has the Fullscreen API and the page
|
||||
may use it: not on iPhone, not in an iframe without allowfullscreen. The
|
||||
label is the existing "Maximize" one. It stays before the first
|
||||
.js-header-collapsible-icon, so "hide the icons" does not hide it.
|
||||
models/lib/fullscreen.js
|
||||
if fullscreenSupported
|
||||
a.board-header-btn.js-toggle-fullscreen(href="#" role="button" title="{{#if isFullscreen}}{{_ 'minimize-window'}}{{else}}{{_ 'maximize-window'}}{{/if}}" aria-label="{{#if isFullscreen}}{{_ 'minimize-window'}}{{else}}{{_ 'maximize-window'}}{{/if}}" aria-pressed="{{#if isFullscreen}}true{{else}}false{{/if}}")
|
||||
if isFullscreen
|
||||
i.fa.fa-compress
|
||||
else
|
||||
i.fa.fa-expand
|
||||
|
||||
// Mobile/desktop mode toggle (swapped with zoom)
|
||||
// #6680 follow-up: .js-header-collapsible-icon marks every icon the new
|
||||
// .js-toggle-header-icons-collapsed button (beside Home) hides as a
|
||||
|
||||
@@ -59,6 +59,7 @@ function customPageTitle(routeName) {
|
||||
// Drag-to-scroll on the two top header bars (they are not scroll containers
|
||||
// themselves, so the drag is forwarded to the board canvas / page scroller).
|
||||
import '/client/lib/headerDragscroll';
|
||||
const fullscreen = require('/models/lib/fullscreen');
|
||||
|
||||
Meteor.subscribe('user-admin');
|
||||
Meteor.subscribe('boards');
|
||||
@@ -70,6 +71,10 @@ Meteor.subscribe('setting');
|
||||
Meteor.subscribe('announcements');
|
||||
Template.header.onCreated(function () {
|
||||
const templateInstance = this;
|
||||
// Full screen: whether the browser offers it here, and whether the page is in it.
|
||||
// Both are set when the header is drawn (onRendered), where there is a document.
|
||||
templateInstance.fullscreenSupported = new ReactiveVar(false);
|
||||
templateInstance.isFullscreen = new ReactiveVar(false);
|
||||
templateInstance.currentSetting = new ReactiveVar();
|
||||
templateInstance.isLoading = new ReactiveVar(false);
|
||||
templateInstance.subscribe('setting');
|
||||
@@ -83,6 +88,14 @@ Template.header.onCreated(function () {
|
||||
|
||||
});
|
||||
Template.header.onRendered(function () {
|
||||
// The Fullscreen API, where it exists and the page may use it (models/lib/fullscreen.js).
|
||||
// The state follows the browser's own events, so leaving with Esc, or with the
|
||||
// browser's own control, changes the icon too.
|
||||
this.fullscreenSupported.set(fullscreen.isSupported(document));
|
||||
this.isFullscreen.set(fullscreen.isFullscreen(document));
|
||||
this.onFullscreenChange = () => this.isFullscreen.set(fullscreen.isFullscreen(document));
|
||||
fullscreen.CHANGE_EVENTS.forEach(name => document.addEventListener(name, this.onFullscreenChange));
|
||||
|
||||
this.autorun(() => {
|
||||
Meteor.userId();
|
||||
FlowRouter.watchPathChange();
|
||||
@@ -92,7 +105,19 @@ Template.header.onRendered(function () {
|
||||
});
|
||||
});
|
||||
|
||||
Template.header.onDestroyed(function () {
|
||||
if (this.onFullscreenChange) {
|
||||
fullscreen.CHANGE_EVENTS.forEach(name => document.removeEventListener(name, this.onFullscreenChange));
|
||||
}
|
||||
});
|
||||
|
||||
Template.header.helpers({
|
||||
fullscreenSupported() {
|
||||
return Template.instance().fullscreenSupported.get();
|
||||
},
|
||||
isFullscreen() {
|
||||
return Template.instance().isFullscreen.get();
|
||||
},
|
||||
logoSettingsReady() {
|
||||
return Template.instance().subscriptionsReady();
|
||||
},
|
||||
@@ -365,6 +390,13 @@ Template.header.helpers({
|
||||
});
|
||||
|
||||
Template.header.events({
|
||||
// Full screen on and off. The request is made inside this click, which is the
|
||||
// user gesture browsers require; a refusal is not an error (models/lib/fullscreen.js).
|
||||
'click .js-toggle-fullscreen'(event) {
|
||||
event.preventDefault();
|
||||
fullscreen.toggle(document);
|
||||
},
|
||||
|
||||
// The starred boards, by name. They were listed inline in this bar; the bar
|
||||
// is one row now and the names are in the popup. docs/Features/Header-Bar/Header.md
|
||||
// Titled "Starred Boards", from the key the app already has for that phrase,
|
||||
|
||||
@@ -725,6 +725,11 @@ a:not(.disabled).is-active i.fa {
|
||||
.viewer code {
|
||||
padding: 10px 12px 7px;
|
||||
background: #dedede;
|
||||
/* Its own text colour with its own light background: a dark theme's light
|
||||
text (#bbb in the dark theme's opened card) was inherited onto #dedede,
|
||||
1.42:1 (46-axe-theme-contrast). #222 on #dedede is 11.8:1, and
|
||||
codeHighlight.css's token colours are chosen for this background too. */
|
||||
color: #222;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.viewer a {
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
first row of a popup, and it must match the space beside it: `.content` has 18px of
|
||||
side padding, so 6 + 12 = 18 makes the top gap and the left gutter the same. */
|
||||
.pop-over .header {
|
||||
/* A popup is moved by its title bar (popup.js, Pointer Events). Without this a
|
||||
finger drag on it is taken for a scroll and the pointer is cancelled, so only a
|
||||
mouse could move it. */
|
||||
touch-action: none;
|
||||
height: calc(41px * var(--wekan-ui-font-scale, 1));
|
||||
position: relative;
|
||||
margin-bottom: 6px;
|
||||
@@ -1441,6 +1445,7 @@ body.mobile-mode .pop-over .content-wrapper {
|
||||
button (popup.js MOVABLE_POPUPS / MAXIMIZABLE_POPUPS), placed like the opened
|
||||
card's: the handle before the title, the button beside the close button. */
|
||||
.pop-over .header .popup-drag-handle {
|
||||
touch-action: none;
|
||||
float: inline-start;
|
||||
padding: 12px 0 12px 12px;
|
||||
cursor: move;
|
||||
|
||||
@@ -123,8 +123,16 @@ Popup.template.events({
|
||||
const drag = tpl._dateMove;
|
||||
if (!drag || drag.pointerId !== evt.pointerId) return;
|
||||
evt.preventDefault(); evt.stopPropagation();
|
||||
const boxLeft = Math.max(12, Math.min(window.innerWidth - drag.width - 12, drag.left + drag.dx + evt.clientX - drag.x));
|
||||
const boxTop = Math.max(12, Math.min(window.innerHeight - drag.height - 12, drag.top + drag.dy + evt.clientY - drag.y));
|
||||
// The popup's size NOW, not when the drag began: a long menu gets its
|
||||
// 960px column layout a frame after it is drawn (popupMenuLayout.js), so a
|
||||
// drag that starts in that frame would keep clamping for the narrow size
|
||||
// and push the grown popup most of the way out of the window - and undo
|
||||
// the ResizeObserver that had just put it back inside.
|
||||
const size = drag.element.getBoundingClientRect();
|
||||
const width = size.width || drag.width;
|
||||
const height = size.height || drag.height;
|
||||
const boxLeft = Math.max(12, Math.min(window.innerWidth - width - 12, drag.left + drag.dx + evt.clientX - drag.x));
|
||||
const boxTop = Math.max(12, Math.min(window.innerHeight - height - 12, drag.top + drag.dy + evt.clientY - drag.y));
|
||||
const left = boxLeft - drag.dx;
|
||||
const top = boxTop - drag.dy;
|
||||
// Dragging uses physical pointer coordinates in either text direction.
|
||||
@@ -230,13 +238,16 @@ Popup.template.onRendered(function () {
|
||||
this._focusTrap = event => trapTabKey(event, this._popupElement);
|
||||
this._popupElement?.addEventListener('keydown', this._focusTrap);
|
||||
if (this._popupElement) {
|
||||
const update = () => {
|
||||
this._menuLayoutFrame = null;
|
||||
updatePopupMenuColumns(this._popupElement);
|
||||
};
|
||||
this._menuLayoutObserver = new MutationObserver(() => {
|
||||
if (this._menuLayoutFrame == null) this._menuLayoutFrame = requestAnimationFrame(update);
|
||||
});
|
||||
const update = () => updatePopupMenuColumns(this._popupElement);
|
||||
// At once, not on the next animation frame: a MutationObserver callback
|
||||
// already runs before the browser paints, and a frame later the popup had
|
||||
// been drawn - and could be clicked or dragged - at its narrow size, then
|
||||
// jumped to the 960px column layout under the pointer. A click aimed at
|
||||
// Board View's "Lists" landed on "Calendar"'s place, and a drag clamped
|
||||
// for the narrow size (06-views-layout, card-title-edit-and-move-6839).
|
||||
// update() toggles a class only when it changes, so the attribute it
|
||||
// observes settles after one more call.
|
||||
this._menuLayoutObserver = new MutationObserver(update);
|
||||
this._menuLayoutObserver.observe(this._popupElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] });
|
||||
update();
|
||||
// A movable popup that grows - Drawing replacing the card's menu in this
|
||||
@@ -278,6 +289,5 @@ Popup.template.onRendered(function () {
|
||||
Popup.template.onDestroyed(function () {
|
||||
this._menuLayoutObserver?.disconnect();
|
||||
this._boundsObserver?.disconnect();
|
||||
if (this._menuLayoutFrame != null) cancelAnimationFrame(this._menuLayoutFrame);
|
||||
this._popupElement?.removeEventListener('keydown', this._focusTrap);
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
|
||||
// #6870: whether the administrator blocked attachment uploads for the whole site
|
||||
// (models/lib/attachmentUploadPolicy.js). Asked of the server once a minute at most; until it
|
||||
// answers - or if it cannot - uploads are shown as before, because the server refuses them either
|
||||
// way and a wrong guess here only costs a button.
|
||||
const blocked = new ReactiveVar(false);
|
||||
let askedAt = 0;
|
||||
|
||||
export function refreshUploadPolicy() {
|
||||
if (Date.now() - askedAt < 60 * 1000) return;
|
||||
askedAt = Date.now();
|
||||
Meteor.callAsync('attachmentUploadPolicy')
|
||||
.then(policy => blocked.set(!!(policy && policy.uploadsBlocked === true)))
|
||||
.catch(() => { askedAt = 0; });
|
||||
}
|
||||
|
||||
// reactive
|
||||
export function attachmentUploadsBlocked() {
|
||||
refreshUploadPolicy();
|
||||
return blocked.get();
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
|
||||
|
||||
// #4475: Admin Panel -> Settings -> Visibility -> "Only admins can create
|
||||
// boards" (tableVisibilityMode-boardCreationAdminOnly). Shared by every
|
||||
// client-side "Add board" entry point so the button hides consistently;
|
||||
// the real enforcement is server-side, in createBoardWithInitialSwimlanes
|
||||
// (server/models/boards.js) - this only avoids showing a control that
|
||||
// would be rejected.
|
||||
// client-side entry point that makes a new board - Add Board, and with it
|
||||
// Import and Template, Duplicate in the All Boards sidebar, and "Create
|
||||
// board from card" - so they hide consistently. The real enforcement is on
|
||||
// the server, on every path (CreateBleed, server/lib/boardCreationGuard.js);
|
||||
// this only avoids offering a control the server would refuse.
|
||||
export function boardCreationAllowed() {
|
||||
const restricted = TableVisibilityModeSettings.findOne(
|
||||
'tableVisibilityMode-boardCreationAdminOnly',
|
||||
@@ -16,3 +18,6 @@ export function boardCreationAllowed() {
|
||||
}
|
||||
return !!ReactiveCache.getCurrentUser()?.isAdmin;
|
||||
}
|
||||
|
||||
// For templates: {{#if boardCreationAllowed}}.
|
||||
Template.registerHelper('boardCreationAllowed', () => boardCreationAllowed());
|
||||
@@ -20,17 +20,29 @@ only what changes: documents, files and log lines. It writes them seconds after
|
||||
they happen, to a directory that can be another disk, an NFS share or an
|
||||
[rclone mount](Rclone/Rclone.md).
|
||||
|
||||
## One server only, for now
|
||||
## Several servers: one of them streams
|
||||
|
||||
Continuous backup is for installations with **one WeKan server**. When it is
|
||||
switched on in the Admin Panel, every WeKan server that shares the database
|
||||
applies the setting, so with several servers (for example the replicas of a
|
||||
Kubernetes Deployment) each of them tails the oplog and writes its own archives
|
||||
into the same target directory: the same backup stream, many times, into one
|
||||
place. It has no leader: one server should run the stream and another take it
|
||||
over when that one stops (a lease), and that is not built yet
|
||||
([#6861](https://github.com/wekan/wekan/issues/6861)). With several servers, use
|
||||
the database's own backup instead.
|
||||
When WeKan runs as several servers on one database (for example the replicas of a Kubernetes
|
||||
Deployment), **one of them runs the stream** and the others stand by
|
||||
([#6861](https://github.com/wekan/wekan/issues/6861)). They coordinate through a *lease*: one
|
||||
document, `continuousBackupLease`, holding the leader's id and an expiry.
|
||||
|
||||
- Every server with the feature switched on tries to take the lease. Taking it is one atomic
|
||||
database operation, so two servers can never both lead.
|
||||
- The leader renews it every 15 seconds. It lapses 45 seconds after the last renewal, so when the
|
||||
leader stops (a pod restart, a rollout, a crash) another server takes over within about a minute
|
||||
and continues from the position the stream saved in the target.
|
||||
- A server that finds it has lost the lease stops its stream, so two never write one target.
|
||||
- Saving the settings, switching the feature off and on, and a change saved on another server all
|
||||
converge through the same loop: the leader follows the change on its next tick.
|
||||
- A restore, and *fetch from the cloud*, must be the only writer: they take the lease from whoever
|
||||
holds it and wait one renewal, so the former leader has stopped before anything is written. The
|
||||
stream resumes through the loop afterwards.
|
||||
|
||||
An installation with one server is always its own leader, so nothing changes for it. The target
|
||||
directory is still one place: with several servers it must be on storage they all reach (a shared
|
||||
volume, NFS, an [rclone mount](Rclone/Rclone.md)), or the server that takes over starts a new
|
||||
generation instead of continuing.
|
||||
|
||||
## Licences: only copyfree code by default
|
||||
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
The full-scale tables below were measured on 2026-10-23 with FerretDB v1.99.0.
|
||||
Every change in this section came after it (FerretDB v1.100.0 to v1.105.0,
|
||||
2026-10-24 to 2026-10-31, WeKan up to 2026-11-02), so **those tables do not show
|
||||
them**; [Results.md](Results.md) is a new, small-scale run that does. The
|
||||
figures quoted in this section are the ones in FerretDB's own
|
||||
[CHANGELOG](https://github.com/wekan/FerretDB/blob/main-v1/CHANGELOG.md), each
|
||||
measured on a smaller database (61,507 cards, 92,000 boards or the 615,070 cards
|
||||
of this test, as stated), so they show the size of a change, not a place in the
|
||||
table.
|
||||
|
||||
# WeKan database speed test
|
||||
|
||||
How fast does WeKan's database answer the queries WeKan actually makes - on
|
||||
@@ -20,10 +30,13 @@ exist, which images they use or how they are started:
|
||||
|
||||
| Database | How it runs |
|
||||
| --- | --- |
|
||||
| FerretDB v1 + SQLite | FerretDB built from `.tools/FerretDB` (the newest source), data under `.tools/tmp` |
|
||||
| FerretDB v1 + SQLite | FerretDB built from `.tools/w/FerretDB` (the newest source), data under `.tools/tmp` |
|
||||
| FerretDB v1 + PostgreSQL | `postgres:18`, the image of [docker-compose-ferretdb-v1-postgresql.yml](../../../../../docker-compose-ferretdb-v1-postgresql.yml) |
|
||||
| FerretDB v1 + MySQL | `mysql:9`, from [docker-compose-ferretdb-v1-mysql.yml](../../../../../docker-compose-ferretdb-v1-mysql.yml) |
|
||||
| FerretDB v1 + MariaDB | `mariadb:12`, from [docker-compose-ferretdb-v1-mariadb.yml](../../../../../docker-compose-ferretdb-v1-mariadb.yml) |
|
||||
| FerretDB v1 + pgrust | `malisper/pgrust:v0.4-beta`: [PostgreSQL rewritten in Rust](https://github.com/malisper/pgrust), wire compatible, so FerretDB's PostgreSQL handler talks to it unchanged. In the conformance run only with `WEKAN_CONFORMANCE_PGRUST=1` |
|
||||
| FerretDB v1 + TiDB | `pingcap/tidb:latest` (Apache-2.0, MySQL protocol) as one process on its own storage, no PD or TiKV. In the conformance run only with `WEKAN_CONFORMANCE_TIDB=1` |
|
||||
| FerretDB v1 + Dolt | `dolthub/dolt-sql-server:latest`: [git for data](https://github.com/dolthub/dolt), MySQL protocol, run as it comes (no commit per write). In the conformance run only with `WEKAN_CONFORMANCE_DOLT=1`. What its version history gives WeKan's undo is in [VersionHistory](../VersionHistory/) |
|
||||
| MongoDB 6, 7, 8, 9 | `mongo:6.0`, `mongo:7` ([docker-compose-mongodb-v7.yml](../../../../../docker-compose-mongodb-v7.yml)), `mongo:8.0`, `mongo:9.0` - queried directly, no FerretDB |
|
||||
| FerretDB v1 + SAP HANA | only with `WEKAN_CONFORMANCE_HANA=1` (amd64, ~16 GB RAM, SAP's licence) |
|
||||
| FerretDB 2 + PostgreSQL with DocumentDB | `ghcr.io/ferretdb/ferretdb:2` and `ghcr.io/ferretdb/postgres-documentdb:17`, the images of [docker-compose-ferretdb-v2-postgresql.yml](../../../../../docker-compose-ferretdb-v2-postgresql.yml), on a network of their own; in the conformance run only with `WEKAN_CONFORMANCE_FERRETDB2=1` |
|
||||
@@ -59,7 +72,11 @@ the busy users.
|
||||
It is **deterministic**: a fixed seed and a seeded generator, never
|
||||
`Math.random()`, so every database is loaded with byte-for-byte identical
|
||||
documents - which is what makes it possible to ask whether they answer the same.
|
||||
`WEKAN_SPEEDTEST_SCALE` changes the size (1 = production, 0.05 for a quick try).
|
||||
`WEKAN_SPEEDTEST_SCALE` changes the size (1 = production, 2 = twice that, the
|
||||
default). **`WEKAN_SPEEDTEST_SCALE=0.05` is 1/20 of a production WeKan - 15,377
|
||||
cards - and the whole run, every database, takes under half an hour instead of
|
||||
hours** (with the images already pulled) (the SQLite indexes alone take 19 minutes at scale 2 and 7 seconds at
|
||||
0.05). Results.md says which scale it was measured at.
|
||||
|
||||
After loading, the test creates **the indexes WeKan creates**. They are not
|
||||
listed here: [indexes.cjs](indexes.cjs) reads every `ensureIndex(...)` call in
|
||||
@@ -135,6 +152,12 @@ Things to know when reading the numbers:
|
||||
That costs FerretDB some load and write time MongoDB does not pay here.
|
||||
- Each database runs with its image's default settings - what an administrator
|
||||
gets - not tuned.
|
||||
- Results.md was measured at scale 0.05 (see "The data"), with FerretDB v1.105.0
|
||||
plus the changes of "Improvements since the last run". A row's FerretDB commit
|
||||
is in its name; the rows differ because a database is re-run alone after a
|
||||
change to FerretDB, which does not touch the others. The full-scale tables
|
||||
further down are from 2026-10-23 (v1.98 and v1.99) and are kept as the record
|
||||
of what each change was worth.
|
||||
- A row's FerretDB commit is in its name: the databases were measured as their
|
||||
FerretDB fixes went in (the PostgreSQL row after its index fix, MySQL and
|
||||
MariaDB after their `_id` fix).
|
||||
@@ -208,28 +231,207 @@ and its board scope is a plain `boardId` list when no board is assigned-only. It
|
||||
docker-compose for FerretDB 2 could not start with the current DocumentDB image
|
||||
(`POSTGRES_DB` must be `postgres`).
|
||||
|
||||
## Improvements since the last run
|
||||
|
||||
[Results.md](Results.md) was measured on 2026-10-23 with FerretDB v1.99.0. Every
|
||||
change below came after it (FerretDB v1.100.0 to v1.105.0, 2026-10-24 to
|
||||
2026-10-31, and WeKan up to 2026-11-02), so **the tables in Results.md do not
|
||||
show them yet** - run the speed test again for full-scale numbers. The figures
|
||||
quoted here are the ones in FerretDB's own
|
||||
[CHANGELOG](https://github.com/wekan/FerretDB/blob/main-v1/CHANGELOG.md), each
|
||||
measured on a smaller database (61,507 cards, 92,000 boards or the 615,070 cards
|
||||
of this test, as stated), so they show the size of a change, not a place in the
|
||||
table.
|
||||
|
||||
**FerretDB on SQLite**
|
||||
|
||||
- **SQLite sorts in the database** (v1.100.0). A sorted query used to read,
|
||||
decode and keep every matching document in Go to return a few - People's ten
|
||||
newest users, the first page of a list. SQLite now orders the row ids by a key
|
||||
that reproduces MongoDB's order for simple values, and documents are fetched a
|
||||
batch at a time in that order: the ten newest of 30,000 users allocate 2.6 MB
|
||||
instead of 264 MB. A document whose sort field holds an array, a document, NaN
|
||||
or an infinity comes first, and then everything is sorted in Go as before, so
|
||||
the answer is the same. The key is read once per row, in one JSON path.
|
||||
- **Documents are stored as SQLite JSONB** (v1.101.0). Every filter, sort key,
|
||||
index and multikey trigger used to parse the JSON text of a document again.
|
||||
New collections are binary, and an existing database is converted once when
|
||||
it is opened, table by table in a transaction (204,212 documents took 81 s; a
|
||||
table that fails stays text and keeps working). On 61,507 cards, due cards went
|
||||
from 1,003 to 519 ms, All Boards from 95 to 42 ms and creating the 44 indexes
|
||||
from 107 to 70 s. After the conversion an older FerretDB cannot read the
|
||||
database.
|
||||
- **`distinct` works when documents lack the field** (v1.105.0), which a database
|
||||
migrated from MongoDB 3 needs on its first start.
|
||||
|
||||
- **A membership test reads the array from the column, not from the text of `->`**
|
||||
(found by profiling Due Cards, fixed after v1.105.0). `{members: {$elemMatch:
|
||||
{userId: X}}}` was `json_each(col->"members")`: `->` renders the whole array as
|
||||
JSON text and `json_each` parses that text again, for every row. It is now
|
||||
`json_each(col, '$."members"')`, which SQLite walks in place. The visible-boards
|
||||
query went from 26.2 to 15.7 ms on 2,308 boards, and in the speed test All Boards
|
||||
count from 6.9 to 4.9 ms, All Boards page from 5.9 to 3.9 ms and Due Cards from
|
||||
54.5 to 43.9 ms (the SQLite total 82.7 → 68.3 ms); the 169 answers of 12 queries
|
||||
are identical to before.
|
||||
|
||||
**FerretDB on PostgreSQL**
|
||||
|
||||
- **Membership tests use a GIN index** (v1.100.0): an index whose leading key is
|
||||
`ARRAY.FIELD` (`members.userId`) also gets a `jsonb_path_ops` GIN index of the
|
||||
array, and `{members: {$elemMatch: {userId: X}}}` uses it.
|
||||
- **`$or` and `$elemMatch` are conditions in the database** (v1.101.0), so Due
|
||||
Cards' query went from 2,031 to 815 ms on 61,507 cards.
|
||||
- **A top-level `$and` is pushed down** (v1.100.0). WeKan's Due Cards wraps its
|
||||
conditions in one, and every document was read.
|
||||
|
||||
- **A table is analyzed once its indexes are created** (found by the small run,
|
||||
fixed after v1.105.0). PostgreSQL has statistics for an expression index
|
||||
(`_jsonb->'field'`) only after `ANALYZE`, which autovacuum runs a minute or
|
||||
more after a bulk load. Before that the planner guessed **one row** for a Due
|
||||
Cards query that returns 2,000, and chose a plan with no parallelism: 407 ms
|
||||
on 15,377 cards, 84 ms after. The whole PostgreSQL row went from 454 ms to
|
||||
145 ms and from last place to eighth. The same `ANALYZE TABLE` was tried on
|
||||
MySQL and MariaDB and made no difference (100 and 279 ms before, 106 and 283
|
||||
after), so it was not kept.
|
||||
|
||||
**FerretDB on MySQL and MariaDB**
|
||||
|
||||
- **Reads use their indexes** (v1.100.0): an equality or `$in` narrows through
|
||||
the index's generated column. A card by `_id` among 615,070 went from 3.4 s
|
||||
(MySQL) and 18.7 s (MariaDB) to 1.8 ms. Generated columns are `VIRTUAL`, so
|
||||
adding the 44 indexes of a 600,000-document database takes 157 s, not 20
|
||||
minutes; an index of four or more fields indexes prefixes, so it fits the
|
||||
3,072-byte key limit that three of WeKan's compound indexes used to exceed;
|
||||
and a path with a dash or a digit (`services.accounts-lockout.unlockTime`, `profile.emailBuffer.0`) no longer
|
||||
fails with "Invalid JSON path expression".
|
||||
- **Multikey side tables** (v1.100.0), as on SQLite, so a membership test reads
|
||||
only the matching documents. Without the right to create triggers (MySQL with
|
||||
binary logging and no SUPER) nothing is created and queries read as before.
|
||||
- **An `$or` of `_id` sets is one `UNION ALL`** (v1.100.0): All Boards for a user
|
||||
with a starred board went from 900 ms (MySQL) and 5.7 s (MariaDB) among 92,000
|
||||
boards to 6-7 ms.
|
||||
- **A long `$in` is one `IN`** (v1.101.0), not 2,596 `JSON_CONTAINS` in an OR
|
||||
(52 million comparisons over 20,000 cards).
|
||||
- **Updates and deletes by `_id` use the `_id` index** (v1.99.0, listed above).
|
||||
|
||||
**WeKan** - less work for the database, which matters more the bigger it is:
|
||||
|
||||
- **Collection scans are gone from the hourly and startup paths.** The recurring
|
||||
cards job has a partial index on `recurrenceInterval` (it read 1,108,017 cards
|
||||
in 1,155 ms every hour); search sessions have the indexes their upsert and
|
||||
lookup use, and the stale sessions of every user are removed (one instance had
|
||||
4,751 taking 10 GB); the board key backfill is one `$in` on the key index; the
|
||||
list sync scan and the due-date lookup have partial and sparse indexes.
|
||||
- **One server does it, not all of them.** The startup schema re-check is claimed
|
||||
in `_wekan_migration`, the daily due-date reminder scan and the database
|
||||
health probe are claimed per period, so 24 replicas no longer scan, send or
|
||||
report 24 times.
|
||||
- **Admin Panel / Problems / Database** reports what makes an instance slow: a
|
||||
query that read a whole collection of at least 10,000 documents (which
|
||||
collection, the command, the operators per field and the `queryHash`), and a
|
||||
high read rate. The speed test's Due Cards and All Boards numbers say what a
|
||||
query costs on one database; this says which query an instance is actually
|
||||
asking.
|
||||
|
||||
## What could still be improved
|
||||
|
||||
- **Sorting and `limit` in the database.** FerretDB v1 sorts in Go: People's ten
|
||||
newest users, All Boards' first page and a card window read and sort every
|
||||
matching document. Pushing an ORDER BY down needs MongoDB's ordering across
|
||||
types, missing fields and arrays to be exact in SQL.
|
||||
- **Due Cards on SQLite, 44 ms of the 68 ms total.** A CPU profile of FerretDB
|
||||
while it ran Due Cards (taken before the change above): half the CPU was inside
|
||||
SQLite, and about a fifth of all of it converting JSON between binary and text
|
||||
(`_jsonTranslateBlobToText` 10%, `_jsonTranslateTextToBlob` 10%, besides
|
||||
`_jsonLookupStep`); another quarter was Go runtime (thread signalling,
|
||||
`madvise`, the network poller). Of the cards query's 24 ms, 18 ms is the index
|
||||
lookups (the 700-board `IN` list) and 6 ms the five per-user conditions
|
||||
(`members`, `assignees`, `requesters`, `assigners`, `userId`), each of which
|
||||
evaluates `->` three times per row. Reading each once, as
|
||||
`json_type(col,'$."f"') = 'array' OR col->>'$."f"' = ?`, took that step from
|
||||
24.4 to 20.5 ms with the same rows - **not done**, because it may only be applied
|
||||
to a field no index covers (an index is matched by the expression `col->"f"`),
|
||||
and FerretDB's WHERE builder does not know which fields are indexed.
|
||||
- **Tried on SQLite and not worth it, or not conclusive**: `ANALYZE` made no
|
||||
difference (FerretDB chooses its indexes itself); a Unix socket instead of TCP
|
||||
saves about 0.05 ms (20%) on a point read (0.24 → 0.19 ms), which is free when
|
||||
WeKan and FerretDB share a machine (`--listen-unix`); `GOGC=400` gave 10-15% on
|
||||
some queries in one test (board cards 5.3 → 4.4 ms) and nothing in another that
|
||||
also set a memory limit, so it is a lead, not a result.
|
||||
|
||||
- **The busiest users.** What remains of All Boards for a member of thousands of
|
||||
boards, and of Due Cards, is decoding those boards and sorting them in Go.
|
||||
- **SQLite stores JSON as text.** Every `->` parses the document again - which is
|
||||
also why creating an index takes 19 minutes for SQLite and 115 s for PostgreSQL,
|
||||
whose `jsonb` is binary. Storing SQLite's JSONB would remove that, but changes
|
||||
the storage format and needs a migration.
|
||||
- **Multikey indexes for PostgreSQL, MySQL and MariaDB.** Only SQLite has the side
|
||||
tables; All Boards takes 8-9 s on the others.
|
||||
- **MySQL and MariaDB reads do not use their indexes**: equality is
|
||||
`JSON_CONTAINS(JSON_EXTRACT(doc, ?))`, so a card by `_id` takes 3.4 s and 18.7 s
|
||||
(and an edit as long - it reads the card first). Their indexes are generated
|
||||
`VARCHAR(255)` columns added with a table rebuild each (20 and 40 minutes for
|
||||
44 indexes; VIRTUAL columns would avoid the rebuild), and three of WeKan's
|
||||
compound indexes exceed the 3,072-byte key limit and are not created.
|
||||
boards, and of Due Cards, is decoding those boards and sorting them in Go on
|
||||
the databases that do not sort in SQL.
|
||||
- **Sorting in the database on PostgreSQL, MySQL and MariaDB.** Only SQLite does
|
||||
it. PostgreSQL was tried (2026-11-03): an `ORDER BY` of type class, number and
|
||||
text (`COLLATE "C"`, MongoDB's byte order) with the documents that cannot be
|
||||
ordered first, as on SQLite, and the tests matched the Go sort over mixed
|
||||
types. It was **not faster**: the ten newest of 30,116 users took 52 ms, against
|
||||
38-49 ms for fetching, decoding and sorting them in Go, because the Go path is
|
||||
already fast after the decoding changes and the sort expressions cost about as
|
||||
much. It would only save memory and transfer, so it was not kept. A sort that
|
||||
wins needs an expression index on the sort field, which has to know the field
|
||||
in advance; MySQL and MariaDB have the same cost structure and no measurement
|
||||
that says otherwise.
|
||||
- **MariaDB stores JSON as text** that every JSON function parses again, so it
|
||||
keeps only what an index answers (`$or` and `$elemMatch` are not pushed down).
|
||||
- **Existing MySQL/MariaDB tables** keep their case-insensitive index columns
|
||||
until rebuilt.
|
||||
- **Measuring it again at full scale.** Results.md is scale 0.05. A full-scale
|
||||
run (`WEKAN_SPEEDTEST_SCALE=2`, hours) would show the busiest users, where the
|
||||
differences are largest.
|
||||
|
||||
## Other databases tried
|
||||
|
||||
Asked for: any other fast database that FerretDB can use and that is open source.
|
||||
FerretDB v1 has handlers for PostgreSQL, MySQL, SQLite and SAP HANA, so a
|
||||
candidate has to speak one of those protocols **and** run the SQL the handler
|
||||
writes: `jsonb` expression indexes, `@>` and GIN on PostgreSQL; `JSON`,
|
||||
`JSON_EXTRACT`, generated columns and triggers on MySQL.
|
||||
|
||||
| Database | Result |
|
||||
| --- | --- |
|
||||
| [pgrust](https://github.com/malisper/pgrust) v0.4-beta (PostgreSQL in Rust, AGPL-3.0) | **Works**, same answers as MongoDB, and is in Results.md. It is not faster than PostgreSQL for WeKan's queries: 174 ms against 145 ms in total, and creating the 47 indexes takes 38 s against 1 s. |
|
||||
| [TiDB](https://github.com/pingcap/tidb) 7.5 (Apache-2.0, MySQL protocol) | **Works**, same answers, and is in Results.md (240 ms, between pgrust and MariaDB). It needed one change in FerretDB: its metadata table's generated columns are `VIRTUAL`, because TiDB refuses `ALTER TABLE ... ADD COLUMN ... STORED`. This is one process with its own storage; a real cluster (PD and TiKV) is a different test. |
|
||||
| [Dolt](https://github.com/dolthub/dolt) 2.6.2 (git for data, Apache-2.0, MySQL protocol) | **Works with the existing MySQL handler, no new handler**, same answers as MongoDB, and is in Results.md - and is **by far the slowest: 20,470 ms in total, 1,230x MongoDB**. Every query over cards takes about 2.8 s on 15,377 cards (a whole-table scan, about 187 µs a document: the generated-column indexes the handler relies on do not narrow a read there), All Boards 850 ms, and creating the indexes 106 s. Its point is version history, not speed; see [VersionHistory](../VersionHistory/). |
|
||||
| [Noria](https://github.com/mit-pdos/noria) (MIT research prototype, Apache-2.0) | **Cannot run FerretDB.** It is a streaming dataflow system that precomputes the results of a fixed set of prepared queries. Its SQL has no `JSON` type (the parser's type list is `INT`, `VARCHAR`, `TEXT`, `BLOB`, `DATE`, ...), no triggers and no generated columns, and the MySQL handler needs all three: it uses `JSON` 79 times, `JSON_EXTRACT` 74 times and 7 generated columns. It also needs unpinned nightly Rust (2021) and ZooKeeper, and has not changed since October 2021. Not built or run. |
|
||||
| [YugabyteDB](https://github.com/yugabyte/yugabyte-db) 2026.1.2 (core Apache-2.0, PostgreSQL 15 compatible) | **Cannot run FerretDB.** Started, and FerretDB's first `CREATE INDEX` fails: `INDEX on column of type 'JSONB' not yet supported`. Every FerretDB v1 index on PostgreSQL is an expression of type `jsonb`, and so is every query that uses one; YugabyteDB can index a `jsonb` column only with GIN. Supporting it means writing the indexes and conditions as `->>` text, which is a different handler. |
|
||||
| [CockroachDB](https://github.com/cockroachdb/cockroach) | **Not tried: not open source.** Its LICENSE is the proprietary CockroachDB Software License, not an open-source one. |
|
||||
|
||||
Tried with the MySQL handler, to see how far it gets (both speak the MySQL protocol):
|
||||
|
||||
| Database | Result |
|
||||
| --- | --- |
|
||||
| [ClickHouse](https://github.com/ClickHouse/ClickHouse) 26.10 (Apache-2.0) | **Needs a handler of its own.** The first statement FerretDB sends through its MySQL interface fails: `sql: expected 0 arguments, got 2`. Behind that it has no `JSON_EXTRACT`, `JSON_CONTAINS`, triggers or generated columns as the handler uses them (it has `JSONExtract*`), and a row update is an asynchronous mutation, which is not what WeKan's edits and moves are. |
|
||||
| [StarRocks](https://github.com/StarRocks/starrocks) 4.1 (Apache-2.0) | **Needs a handler of its own.** The first `CREATE TABLE` fails: `Data type of first column cannot be JSON`; its tables need a key column and a distribution. An analytical engine again, with no triggers. |
|
||||
|
||||
A handler for either is a new backend of the size of the SAP HANA one (about
|
||||
2,000 lines of Go: query, insert, update, delete, indexes, statistics) plus its own
|
||||
SQL for every filter FerretDB can push down; without the pushdown a query reads
|
||||
the whole collection, which the speed test would measure as slow. That has not
|
||||
been written. Not tried at all: DuckDB (no server; the Go driver needs cgo, which
|
||||
FerretDB's builds do not use) and SingleStore (not open source, and its image
|
||||
needs a licence key). A MongoDB-protocol database needs no FerretDB and joins the
|
||||
MongoDB rows; FerretDB 2 with DocumentDB is already there.
|
||||
|
||||
## Opening a board in WeKan
|
||||
|
||||
The same databases, one level up: [app/](app/) opens a WeKan board of 200, 2,000 and
|
||||
5,000 cards on FerretDB (SQLite) and on MongoDB 8 and profiles both the Meteor server
|
||||
and FerretDB. The Meteor server's CPU is the same on both (0.1-0.5 s); on a 5,000-card
|
||||
board FerretDB (SQLite) takes **400-600 ms longer** (to the first card +42-48%), a
|
||||
small board only 70-100 ms, and it uses about 4 s of CPU to open the 5,000-card board,
|
||||
a third of it filtering in Go what it read; the browser receives the same ~210 KB at
|
||||
every size.
|
||||
|
||||
## Go databases linked into FerretDB
|
||||
|
||||
Could a database implemented in Go be linked into the FerretDB binary beside its
|
||||
SQLite, and be faster? [embedded/](embedded/) asks that of the
|
||||
[awesome-go list](https://github.com/avelino/awesome-go#databases-implemented-in-go):
|
||||
Pebble, Badger, bbolt, BuntDB, Nutsdb and Clover against SQLite set up as FerretDB
|
||||
sets it, on the speed test's cards, in minutes. **None is faster where it counts**:
|
||||
a board's cards take the same time on every engine that is not slower, because
|
||||
decoding the JSON dominates; the key-value stores win single reads and writes by
|
||||
microseconds that the 0.2 ms floor of a FerretDB call hides, and would make the
|
||||
handler do in Go what SQL does in SQLite. bbolt with its default fsync is 100-160x
|
||||
slower on writes, Pebble 12x slower on fetching a board, Clover 90-190x slower.
|
||||
|
||||
## FerretDB 2
|
||||
|
||||
|
||||
@@ -1,12 +1,34 @@
|
||||
# WeKan database speed test results
|
||||
|
||||
Run 2026-10-23 on Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, 12 threads, 30 GiB RAM, Linux 7.2.6-070206-generic, by
|
||||
Run 2026-11-03 on Apple M2 Max, 12 threads, 32 GiB RAM, Darwin 27.2.0, by
|
||||
[speedtest.sh](speedtest.sh). How it works, and what was improved because of it:
|
||||
[the README](.).
|
||||
|
||||
The data is twice a production WeKan: 30,116 users, 92,306 boards, 221,038 lists, 222,546 swimlanes, 615,070 cards (production: 307,535 cards). Every database got identical
|
||||
The data is 1/20 of a production WeKan: 753 users, 2,308 boards, 5,526 lists, 5,564 swimlanes, 15,377 cards (production: 307,535 cards). Every database got identical
|
||||
documents and WeKan's own indexes, and was asked identical questions.
|
||||
|
||||
## At a glance
|
||||
|
||||
One number per database: the time of one call of each of the common WeKan queries below,
|
||||
added together. **Times slower** is that sum divided by the fastest database's. Ordered
|
||||
from fastest to slowest. The second pair of columns is the same for the slowest parameter -
|
||||
the biggest board or the busiest user, who waits longest.
|
||||
|
||||
| # | Database | Total (ms) | Times slower | Slowest user (ms) | Times slower |
|
||||
| ---: | --- | ---: | ---: | ---: | ---: |
|
||||
| 1 | MongoDB 9.0.2 | 16.6 | 1.0x | 45.1 | 1.0x |
|
||||
| 2 | MongoDB 8.0.32 | 17.4 | 1.0x | 48.8 | 1.1x |
|
||||
| 3 | MongoDB 6.0.28 | 18.0 | 1.1x | 50.2 | 1.1x |
|
||||
| 4 | MongoDB 7.0.43 | 18.1 | 1.1x | 50.1 | 1.1x |
|
||||
| 5 | FerretDB v2.7.0 + PostgreSQL 17.6 with DocumentDB | 57.9 | 3.5x | 154 | 3.4x |
|
||||
| 6 | FerretDB v1.105.0 (9030e55c) + SQLite (modernc.org/sqlite v1.61.0) | 68.3 | 4.1x | 219 | 4.9x |
|
||||
| 7 | FerretDB v1.105.0 (0db7b801) + MySQL 9.7.2 | 103 | 6.2x | 415 | 9.2x |
|
||||
| 8 | FerretDB v1.105.0 (0c784f2c) + PostgreSQL 18.6 | 145 | 8.7x | 483 | 11x |
|
||||
| 9 | FerretDB v1.105.0 (0c784f2c) + pgrust 0.4-beta (PostgreSQL 18.6 compatible) | 174 | 10x | 594 | 13x |
|
||||
| 10 | FerretDB v1.105.0 (0db7b801) + TiDB v7.5.1 | 240 | 14x | 877 | 19x |
|
||||
| 11 | FerretDB v1.105.0 (0db7b801) + MariaDB 12.3.3 | 290 | 17x | 1,063 | 24x |
|
||||
| 12 | FerretDB v1.105.0 (0db7b801) + Dolt 2.6.2 | 20,470 | 1,230x | 20,904 | 464x |
|
||||
|
||||
## Speed
|
||||
|
||||
Milliseconds for one call - smaller is faster. Each query is asked with 15 parameters (the
|
||||
@@ -16,66 +38,80 @@ fastest database at the top.
|
||||
|
||||
| Database | Total | Login | User by id | All Boards count | All Boards page | Board by id | Board lists | Board swimlanes | Board cards | Card window | Card count | Cards per list | Card by id | Card search | Due Cards | People page | Edit card | Move card | Add card |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| MongoDB 9.0.4 | **495** | 0.75 | 0.66 | 5.76 | 16.4 | 0.68 | 1.06 | 0.98 | 47.7 | 0.77 | 1.61 | 2.62 | 0.66 | 5.34 | 394 | 13.6 | 0.77 | 1.16 | 1.00 |
|
||||
| MongoDB 6.0.28 | **562** | 0.78 | 0.73 | 5.49 | 21.0 | 0.83 | 1.01 | 0.93 | 48.2 | 0.89 | 2.01 | 4.38 | 0.74 | 4.76 | 447 | 20.9 | 0.83 | 0.90 | 0.77 |
|
||||
| MongoDB 7.0.45 | **565** | 0.86 | 0.74 | 6.29 | 23.7 | 0.79 | 1.07 | 1.00 | 48.9 | 0.87 | 2.00 | 4.35 | 0.72 | 5.52 | 445 | 20.7 | 0.79 | 0.77 | 0.76 |
|
||||
| MongoDB 8.0.34 | **577** | 0.81 | 0.69 | 6.83 | 16.8 | 0.77 | 1.19 | 1.01 | 48.5 | 0.88 | 1.90 | 2.87 | 0.69 | 6.28 | 470 | 15.6 | 0.78 | 0.89 | 0.78 |
|
||||
| FerretDB v1.99.0 (b6c494e9) + SQLite (modernc.org/sqlite v1.61.0) | **18,848** | 0.99 | 0.93 | 1,405 | 1,655 | 1.08 | 4.15 | 2.94 | 223 | 67.2 | 86.0 | 119 | 0.89 | 81.7 | 12,604 | 2,582 | 4.69 | 6.36 | 2.32 |
|
||||
| FerretDB v2.7.0 + PostgreSQL 17.6 with DocumentDB | **88,884** | 1.35 | 1.03 | 183 | 241 | 1.15 | 1.72 | 1.68 | 113 | 53.5 | 43.5 | 46.0 | 0.93 | 46.1 | 88,013 | 118 | 7.89 | 6.09 | 6.20 |
|
||||
| FerretDB v1.99.0 (b6c494e9) + MariaDB 12.3.3 | **97,536** | 1.76 | 2.05 | 11,389 | 11,396 | 1.82 | 3.41 | 2.43 | 189 | 2.43 | 171 | 173 | 1.79 | 171 | 73,217 | 791 | 9.24 | 8.49 | 6.68 |
|
||||
| FerretDB v1.99.0 (b6c494e9) + PostgreSQL 18.6 | **98,435** | 1.24 | 1.21 | 159 | 149 | 1.36 | 2.14 | 1.62 | 217 | 1.46 | 121 | 132 | 1.14 | 137 | 96,035 | 1,444 | 9.51 | 11.9 | 9.48 |
|
||||
| FerretDB v1.99.0 (b6c494e9) + MySQL 9.7.4 | **106,082** | 1.86 | 3.09 | 1,073 | 1,082 | 1.86 | 2.90 | 2.37 | 249 | 4.88 | 133 | 140 | 1.74 | 153 | 101,505 | 1,687 | 14.4 | 15.7 | 11.6 |
|
||||
| MongoDB 9.0.2 | **16.6** | 0.48 | 0.41 | 0.89 | 0.87 | 0.43 | 0.42 | 0.43 | 2.08 | 0.42 | 0.45 | 0.53 | 0.35 | 0.60 | 6.23 | 0.68 | 0.39 | 0.52 | 0.48 |
|
||||
| MongoDB 8.0.32 | **17.4** | 0.46 | 0.41 | 0.86 | 0.85 | 0.39 | 0.48 | 0.45 | 2.14 | 0.42 | 0.49 | 0.52 | 0.35 | 0.63 | 7.09 | 0.78 | 0.38 | 0.40 | 0.34 |
|
||||
| MongoDB 6.0.28 | **18.0** | 0.44 | 0.46 | 0.71 | 0.96 | 0.43 | 0.44 | 0.41 | 2.13 | 0.39 | 0.43 | 0.50 | 0.36 | 0.60 | 7.42 | 0.82 | 0.49 | 0.49 | 0.53 |
|
||||
| MongoDB 7.0.43 | **18.1** | 0.45 | 0.41 | 0.71 | 0.91 | 0.41 | 0.46 | 0.41 | 2.05 | 0.40 | 0.42 | 0.51 | 0.36 | 0.55 | 7.81 | 0.76 | 0.46 | 0.52 | 0.48 |
|
||||
| FerretDB v2.7.0 + PostgreSQL 17.6 with DocumentDB | **57.9** | 0.67 | 0.55 | 2.10 | 2.82 | 0.69 | 0.71 | 0.56 | 3.21 | 1.12 | 1.06 | 1.29 | 0.52 | 1.42 | 33.8 | 1.91 | 1.87 | 1.77 | 1.85 |
|
||||
| FerretDB v1.105.0 (9030e55c) + SQLite (modernc.org/sqlite v1.61.0) | **68.3** | 0.29 | 0.23 | 4.35 | 3.40 | 0.28 | 0.42 | 0.37 | 4.91 | 0.72 | 1.69 | 2.36 | 0.22 | 1.01 | 40.7 | 3.90 | 1.36 | 1.39 | 0.67 |
|
||||
| FerretDB v1.105.0 (0db7b801) + MySQL 9.7.2 | **103** | 0.92 | 0.87 | 6.13 | 6.40 | 0.91 | 0.97 | 0.88 | 7.79 | 1.04 | 5.05 | 5.13 | 0.85 | 5.75 | 35.9 | 16.9 | 2.65 | 2.87 | 2.08 |
|
||||
| FerretDB v1.105.0 (0c784f2c) + PostgreSQL 18.6 | **145** | 0.76 | 0.67 | 5.84 | 6.14 | 0.59 | 0.57 | 0.55 | 7.70 | 0.70 | 4.54 | 4.57 | 0.50 | 5.20 | 83.8 | 16.1 | 2.34 | 2.27 | 1.66 |
|
||||
| FerretDB v1.105.0 (0c784f2c) + pgrust 0.4-beta (PostgreSQL 18.6 compatible) | **174** | 0.75 | 0.62 | 6.00 | 6.39 | 0.70 | 0.68 | 0.61 | 7.49 | 0.73 | 4.75 | 4.70 | 0.57 | 5.29 | 112 | 15.3 | 2.59 | 2.58 | 1.95 |
|
||||
| FerretDB v1.105.0 (0db7b801) + TiDB v7.5.1 | **240** | 2.00 | 1.48 | 32.7 | 33.0 | 1.44 | 1.95 | 1.68 | 10.4 | 2.96 | 7.61 | 7.59 | 1.50 | 8.35 | 97.2 | 18.4 | 4.32 | 4.81 | 2.71 |
|
||||
| FerretDB v1.105.0 (0db7b801) + MariaDB 12.3.3 | **290** | 1.06 | 0.90 | 11.6 | 11.6 | 1.20 | 1.13 | 0.96 | 7.68 | 1.28 | 6.63 | 6.77 | 0.91 | 6.90 | 216 | 9.12 | 2.52 | 2.48 | 1.43 |
|
||||
| FerretDB v1.105.0 (0db7b801) + Dolt 2.6.2 | **20,470** | 42.6 | 1.87 | 852 | 847 | 2.09 | 188 | 84.7 | 2,876 | 2,846 | 2,857 | 2,836 | 2.06 | 2,834 | 4,126 | 56.6 | 6.94 | 6.92 | 4.75 |
|
||||
|
||||
### The slowest parameter
|
||||
|
||||
The same, for the slowest of the 15 parameters - nearly always the biggest board or the
|
||||
busiest user. This is how long the person who waits longest waits. Same row order.
|
||||
busiest user. This is how long the person who waits longest waits. Ordered by its own
|
||||
total, so the order can differ from the table above.
|
||||
|
||||
| Database | Total | Login | User by id | All Boards count | All Boards page | Board by id | Board lists | Board swimlanes | Board cards | Card window | Card count | Cards per list | Card by id | Card search | Due Cards | People page | Edit card | Move card | Add card |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| MongoDB 9.0.4 | **1,110** | 0.78 | 0.69 | 28.1 | 187 | 0.72 | 1.89 | 1.63 | 297 | 0.83 | 4.48 | 9.99 | 0.68 | 28.1 | 525 | 19.9 | 0.85 | 1.48 | 1.02 |
|
||||
| MongoDB 6.0.28 | **1,257** | 0.80 | 0.78 | 29.6 | 256 | 0.95 | 1.82 | 1.43 | 300 | 0.98 | 7.77 | 22.4 | 0.78 | 24.9 | 585 | 21.1 | 0.87 | 1.11 | 0.79 |
|
||||
| MongoDB 7.0.45 | **1,303** | 0.88 | 0.77 | 34.1 | 291 | 0.83 | 1.83 | 1.65 | 304 | 0.89 | 7.95 | 22.1 | 0.77 | 29.6 | 583 | 20.8 | 0.84 | 1.04 | 0.79 |
|
||||
| MongoDB 8.0.34 | **1,212** | 0.87 | 0.71 | 33.7 | 184 | 0.82 | 2.09 | 1.66 | 301 | 0.93 | 5.14 | 11.0 | 0.73 | 33.8 | 611 | 22.1 | 0.84 | 1.12 | 0.80 |
|
||||
| FerretDB v1.99.0 (b6c494e9) + SQLite (modernc.org/sqlite v1.61.0) | **29,845** | 1.04 | 0.97 | 4,780 | 4,812 | 1.27 | 18.1 | 8.09 | 1,419 | 389 | 538 | 755 | 0.96 | 504 | 13,798 | 2,804 | 5.69 | 7.52 | 2.83 |
|
||||
| FerretDB v2.7.0 + PostgreSQL 17.6 with DocumentDB | **136,362** | 1.39 | 1.07 | 190 | 252 | 1.22 | 2.90 | 2.50 | 451 | 113 | 90.2 | 90.2 | 0.98 | 90.0 | 134,900 | 147 | 13.8 | 7.81 | 6.78 |
|
||||
| FerretDB v1.99.0 (b6c494e9) + MariaDB 12.3.3 | **130,784** | 1.87 | 2.46 | 15,784 | 15,689 | 2.05 | 9.24 | 5.17 | 1,192 | 2.71 | 1,101 | 1,079 | 2.63 | 1,079 | 93,914 | 891 | 11.3 | 10.5 | 7.36 |
|
||||
| FerretDB v1.99.0 (b6c494e9) + PostgreSQL 18.6 | **127,323** | 1.27 | 1.31 | 887 | 812 | 1.56 | 6.39 | 3.80 | 1,365 | 1.63 | 762 | 874 | 1.20 | 848 | 120,122 | 1,601 | 12.2 | 13.1 | 10.4 |
|
||||
| FerretDB v1.99.0 (b6c494e9) + MySQL 9.7.4 | **131,734** | 1.98 | 3.56 | 1,821 | 1,815 | 2.41 | 7.84 | 4.79 | 1,561 | 6.74 | 791 | 873 | 1.84 | 936 | 122,154 | 1,709 | 15.7 | 16.6 | 12.5 |
|
||||
| MongoDB 9.0.2 | **45.1** | 0.59 | 0.47 | 2.54 | 4.03 | 0.48 | 0.50 | 0.50 | 13.9 | 0.45 | 0.64 | 0.90 | 0.37 | 1.87 | 15.6 | 0.71 | 0.42 | 0.59 | 0.54 |
|
||||
| MongoDB 8.0.32 | **48.8** | 0.52 | 0.44 | 2.63 | 4.09 | 0.42 | 0.59 | 0.55 | 14.6 | 0.44 | 0.66 | 0.91 | 0.40 | 2.08 | 18.4 | 0.83 | 0.40 | 0.45 | 0.37 |
|
||||
| MongoDB 7.0.43 | **50.1** | 0.51 | 0.46 | 2.41 | 5.25 | 0.44 | 0.54 | 0.48 | 14.3 | 0.42 | 0.64 | 1.26 | 0.40 | 1.83 | 18.7 | 0.80 | 0.52 | 0.60 | 0.54 |
|
||||
| MongoDB 6.0.28 | **50.2** | 0.48 | 0.52 | 2.49 | 5.59 | 0.49 | 0.52 | 0.50 | 14.6 | 0.42 | 0.66 | 1.33 | 0.39 | 2.05 | 17.6 | 0.87 | 0.54 | 0.55 | 0.59 |
|
||||
| FerretDB v2.7.0 + PostgreSQL 17.6 with DocumentDB | **154** | 0.74 | 0.59 | 2.40 | 3.22 | 0.88 | 0.91 | 0.65 | 20.4 | 1.65 | 1.43 | 2.17 | 0.57 | 2.74 | 107 | 2.01 | 2.28 | 2.17 | 2.06 |
|
||||
| FerretDB v1.105.0 (9030e55c) + SQLite (modernc.org/sqlite v1.61.0) | **219** | 0.34 | 0.30 | 21.6 | 11.2 | 0.35 | 0.82 | 0.67 | 44.0 | 3.71 | 13.9 | 20.6 | 0.24 | 5.43 | 85.6 | 6.88 | 1.53 | 1.49 | 0.77 |
|
||||
| FerretDB v1.105.0 (0db7b801) + MySQL 9.7.2 | **415** | 1.00 | 0.93 | 36.7 | 38.9 | 1.05 | 1.43 | 1.11 | 61.6 | 1.26 | 36.6 | 37.1 | 0.93 | 41.5 | 128 | 18.3 | 3.26 | 3.21 | 2.63 |
|
||||
| FerretDB v1.105.0 (0c784f2c) + PostgreSQL 18.6 | **483** | 0.87 | 0.73 | 38.3 | 39.1 | 0.68 | 0.97 | 0.79 | 66.7 | 1.22 | 36.7 | 37.0 | 0.57 | 41.4 | 193 | 16.9 | 2.65 | 2.86 | 1.91 |
|
||||
| FerretDB v1.105.0 (0c784f2c) + pgrust 0.4-beta (PostgreSQL 18.6 compatible) | **594** | 0.88 | 0.67 | 36.5 | 38.8 | 0.80 | 1.13 | 0.92 | 62.5 | 1.19 | 36.4 | 36.1 | 0.60 | 40.3 | 314 | 15.8 | 2.78 | 2.77 | 2.12 |
|
||||
| FerretDB v1.105.0 (0db7b801) + TiDB v7.5.1 | **877** | 2.12 | 1.52 | 77.1 | 77.7 | 1.58 | 2.64 | 2.11 | 76.4 | 4.03 | 51.1 | 49.9 | 1.62 | 55.6 | 442 | 18.9 | 4.53 | 5.05 | 2.86 |
|
||||
| FerretDB v1.105.0 (0db7b801) + MariaDB 12.3.3 | **1,063** | 1.19 | 0.98 | 104 | 104 | 1.39 | 1.66 | 1.34 | 54.4 | 1.39 | 49.9 | 50.7 | 0.98 | 51.3 | 622 | 9.26 | 2.84 | 2.63 | 1.61 |
|
||||
| FerretDB v1.105.0 (0db7b801) + Dolt 2.6.2 | **20,904** | 43.8 | 1.99 | 875 | 887 | 2.36 | 192 | 88.5 | 2,912 | 2,890 | 2,895 | 2,908 | 2.57 | 2,860 | 4,262 | 61.4 | 8.81 | 8.48 | 4.91 |
|
||||
|
||||
## Loading the data
|
||||
|
||||
Inserting the documents in batches of 1,000, then creating every index WeKan creates.
|
||||
Ordered by the whole time, fastest first.
|
||||
|
||||
| Database | users | boards | lists | swimlanes | cards | Indexes | Index errors |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |
|
||||
| MongoDB 9.0.4 | 0.8 s | 3.4 s | 4.5 s | 4.1 s | 17.0 s | 43.6 s | none |
|
||||
| MongoDB 6.0.28 | 0.8 s | 3.3 s | 4.6 s | 4.2 s | 17.7 s | 41.6 s | none |
|
||||
| MongoDB 7.0.45 | 0.8 s | 3.3 s | 4.7 s | 4.2 s | 17.3 s | 42.8 s | none |
|
||||
| MongoDB 8.0.34 | 0.8 s | 3.2 s | 4.9 s | 4.3 s | 17.9 s | 45.3 s | none |
|
||||
| FerretDB v1.99.0 (b6c494e9) + SQLite (modernc.org/sqlite v1.61.0) | 10.5 s | 65.5 s | 59.6 s | 41.4 s | 315.3 s | 1157.0 s | none |
|
||||
| FerretDB v2.7.0 + PostgreSQL 17.6 with DocumentDB | 1.5 s | 6.0 s | 7.7 s | 6.9 s | 32.3 s | 219.2 s | none |
|
||||
| FerretDB v1.99.0 (b6c494e9) + MariaDB 12.3.3 | 8.5 s | 35.9 s | 33.7 s | 28.3 s | 148.9 s | 900.9 s | none |
|
||||
| FerretDB v1.99.0 (b6c494e9) + PostgreSQL 18.6 | 15.2 s | 83.7 s | 74.6 s | 63.3 s | 376.9 s | 116.8 s | none |
|
||||
| FerretDB v1.99.0 (b6c494e9) + MySQL 9.7.4 | 11.2 s | 58.6 s | 58.0 s | 53.1 s | 254.2 s | 172.0 s | none |
|
||||
| MongoDB 9.0.2 | 0.0 s | 0.0 s | 0.1 s | 0.0 s | 0.2 s | 0.6 s | none |
|
||||
| MongoDB 7.0.43 | 0.0 s | 0.0 s | 0.1 s | 0.0 s | 0.2 s | 0.6 s | none |
|
||||
| MongoDB 6.0.28 | 0.0 s | 0.0 s | 0.1 s | 0.0 s | 0.2 s | 0.6 s | none |
|
||||
| MongoDB 8.0.32 | 0.0 s | 0.0 s | 0.1 s | 0.0 s | 0.2 s | 0.7 s | none |
|
||||
| FerretDB v2.7.0 + PostgreSQL 17.6 with DocumentDB | 0.0 s | 0.1 s | 0.1 s | 0.1 s | 0.3 s | 1.3 s | none |
|
||||
| FerretDB v1.105.0 (0db7b801) + MySQL 9.7.2 | 0.2 s | 0.5 s | 0.4 s | 0.4 s | 2.0 s | 1.7 s | none |
|
||||
| FerretDB v1.105.0 (0db7b801) + MariaDB 12.3.3 | 0.1 s | 0.3 s | 0.3 s | 0.2 s | 1.3 s | 7.2 s | none |
|
||||
| FerretDB v1.105.0 (9030e55c) + SQLite (modernc.org/sqlite v1.61.0) | 0.1 s | 0.5 s | 0.3 s | 0.2 s | 1.9 s | 7.4 s | none |
|
||||
| FerretDB v1.105.0 (0db7b801) + TiDB v7.5.1 | 0.3 s | 0.6 s | 0.5 s | 0.5 s | 2.2 s | 7.9 s | none |
|
||||
| FerretDB v1.105.0 (0c784f2c) + PostgreSQL 18.6 | 0.1 s | 0.7 s | 0.5 s | 0.4 s | 3.0 s | 16.0 s | none |
|
||||
| FerretDB v1.105.0 (0c784f2c) + pgrust 0.4-beta (PostgreSQL 18.6 compatible) | 0.1 s | 0.7 s | 0.5 s | 0.4 s | 3.2 s | 38.0 s | none |
|
||||
| FerretDB v1.105.0 (0db7b801) + Dolt 2.6.2 | 0.2 s | 0.8 s | 0.8 s | 0.6 s | 4.2 s | 105.7 s | none |
|
||||
|
||||
## Same data
|
||||
|
||||
Every answer of every query, for every parameter, and the cards the writes left behind,
|
||||
compared with **MongoDB 8.0.34**. Answers are compared as typed values with
|
||||
compared with **MongoDB 8.0.32**. Answers are compared as typed values with
|
||||
sorted keys; a query whose order WeKan relies on must also return the same order.
|
||||
|
||||
| Database | Same answers | Different |
|
||||
| --- | --- | --- |
|
||||
| MongoDB 9.0.4 | 19 of 19 | none |
|
||||
| MongoDB 9.0.2 | 19 of 19 | none |
|
||||
| MongoDB 8.0.32 | 19 of 19 | none |
|
||||
| MongoDB 6.0.28 | 19 of 19 | none |
|
||||
| MongoDB 7.0.45 | 19 of 19 | none |
|
||||
| MongoDB 8.0.34 | 19 of 19 | none |
|
||||
| FerretDB v1.99.0 (b6c494e9) + SQLite (modernc.org/sqlite v1.61.0) | 19 of 19 | none |
|
||||
| MongoDB 7.0.43 | 19 of 19 | none |
|
||||
| FerretDB v2.7.0 + PostgreSQL 17.6 with DocumentDB | 19 of 19 | none |
|
||||
| FerretDB v1.99.0 (b6c494e9) + MariaDB 12.3.3 | 19 of 19 | none |
|
||||
| FerretDB v1.99.0 (b6c494e9) + PostgreSQL 18.6 | 19 of 19 | none |
|
||||
| FerretDB v1.99.0 (b6c494e9) + MySQL 9.7.4 | 19 of 19 | none |
|
||||
| FerretDB v1.105.0 (9030e55c) + SQLite (modernc.org/sqlite v1.61.0) | 19 of 19 | none |
|
||||
| FerretDB v1.105.0 (0db7b801) + MySQL 9.7.2 | 19 of 19 | none |
|
||||
| FerretDB v1.105.0 (0c784f2c) + PostgreSQL 18.6 | 19 of 19 | none |
|
||||
| FerretDB v1.105.0 (0c784f2c) + pgrust 0.4-beta (PostgreSQL 18.6 compatible) | 19 of 19 | none |
|
||||
| FerretDB v1.105.0 (0db7b801) + TiDB v7.5.1 | 19 of 19 | none |
|
||||
| FerretDB v1.105.0 (0db7b801) + MariaDB 12.3.3 | 19 of 19 | none |
|
||||
| FerretDB v1.105.0 (0db7b801) + Dolt 2.6.2 | 19 of 19 | none |
|
||||
|
||||
## The queries
|
||||
|
||||
@@ -103,7 +139,7 @@ Each is the query WeKan itself sends; see [queries.cjs](queries.cjs).
|
||||
| Add card | cards.insertOne({ ...a new card }) | the 3 biggest boards + 12 others |
|
||||
|
||||
Each query is first run once per parameter to warm up (those answers are the ones compared),
|
||||
then repeated for up to 5 passes or 10 seconds; 15-90 calls per query were timed.
|
||||
then repeated for up to 5 passes or the time budget (WEKAN_SPEEDTEST_BUDGET_MS); 15-90 calls per query were timed.
|
||||
A query too slow to be repeated in that time keeps its warm-up time.
|
||||
Raw results: `.tools/log/test-db-speedtest/2026-10-23_19-33-27/speedtest-<database>.json`.
|
||||
Raw results: `.tools/log/test-db-speedtest/2026-11-03_22-10-05/speedtest-<database>.json`.
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# Opening a WeKan board: where the time goes
|
||||
|
||||
The [speed test](../) asks the database alone. This asks what a person waits for: the
|
||||
whole of opening a board in WeKan, with WeKan on FerretDB (SQLite) and on MongoDB 8,
|
||||
and how much of it is the database. [board-open.cjs](board-open.cjs) seeds a board of
|
||||
N cards, logs in, opens it in Chromium and records
|
||||
|
||||
- the wall clock to the first minicard and until the DDP WebSocket has been quiet for
|
||||
1.5 s (**settled**), and what crossed the WebSocket;
|
||||
- the Meteor server's CPU, by who spends it (a Node inspector profile of the server);
|
||||
- FerretDB's CPU (its pprof), against what it spends with nobody in the browser.
|
||||
|
||||
```sh
|
||||
# WeKan on FerretDB: MONGO_URL=mongodb://127.0.0.1:27040/wekan?directConnection=true,
|
||||
# DEFAULT_METEOR_REACTIVITY_ORDER=polling (as WeKan is deployed), meteor run --inspect=9229,
|
||||
# and FerretDB with --debug-addr=127.0.0.1:8098
|
||||
WEKAN_BASE_URL=http://localhost:3310 WEKAN_MONGO_URL=mongodb://127.0.0.1:27040/wekan \
|
||||
node docs/Databases/FerretDB/1/SpeedTest/app/board-open.cjs --sizes 200,2000,5000 \
|
||||
--inspect 9229 --ferret-pprof http://127.0.0.1:8098 --label ferretdb-sqlite
|
||||
```
|
||||
|
||||
Run on 2026-11-03/04, Apple M2 Max, **the development build of Meteor** (so the browser's
|
||||
own part is larger than in production), one user, `polling` reactivity, FerretDB v1.105.0
|
||||
with the SQLite membership-test change, MongoDB 8.0 in Docker. Each board is opened
|
||||
twice (the second is warm), and the whole measurement was run **twice** (run 1 / run 2
|
||||
below), because one run is not enough: the same board's "settled" time moved by up to
|
||||
300 ms between runs.
|
||||
|
||||
## Result: the database is not what you wait for on a small board, and is on a big one
|
||||
|
||||
Milliseconds, first open of a board with this many cards (run 1 / run 2). WeKan loads
|
||||
cards lazily, so the browser gets **the same ~210 KB and draws 70-120 minicards at every
|
||||
size**.
|
||||
|
||||
| Cards | FerretDB (SQLite): first card | settled | MongoDB 8: first card | settled |
|
||||
| ---: | ---: | ---: | ---: | ---: |
|
||||
| 200 | 750 / 764 | 966 / 880 | 752 / 698 | 865 / 813 |
|
||||
| 2,000 | 931 / 1,031 | 1,763 / 1,555 | 829 / 856 | 1,457 / 1,582 |
|
||||
| 5,000 | 1,331 / 1,334 | 2,166 / 2,065 | 938 / 899 | 1,562 / 1,622 |
|
||||
|
||||
What is the same in both runs:
|
||||
|
||||
- **5,000 cards: FerretDB is 400-600 ms slower**, to the first card by 42-48% and until
|
||||
settled by 27-39%.
|
||||
- **2,000 cards: within the noise** for "settled" (-30 to +300 ms), and 100-175 ms
|
||||
slower to the first card.
|
||||
- **200 cards: 70-100 ms**, which is 9-12%.
|
||||
|
||||
The warm second open of run 2: 5,000 cards 1,321 against 865 ms to the first card
|
||||
and 1,946 against 1,594 ms settled; 2,000 cards 904 against 824 and 1,628 against
|
||||
1,446; 200 cards 721 against 689 and 934 against 805.
|
||||
|
||||
The Meteor server's CPU is the same on both databases within the noise (first open, run
|
||||
1 / run 2: 102 / 119, 276 / 421 and 470 / 517 ms on FerretDB; 178 / 93, 245 / 342 and
|
||||
521 / 483 ms on MongoDB), so the difference is **time spent waiting for the database**,
|
||||
and it grows with the size of the board while the data the browser receives does not.
|
||||
|
||||
For a board of a few hundred cards, 0.7-0.75 s to the first card is nearly the same on
|
||||
both: that is the application (the development bundle, Blaze, the DDP round trips), not
|
||||
the database.
|
||||
|
||||
## Where the Meteor server spends its CPU (5,000 cards, first open, run 2)
|
||||
|
||||
| | FerretDB (517 ms) | MongoDB 8 (483 ms) |
|
||||
| --- | ---: | ---: |
|
||||
| Garbage collector | 31% | 24% |
|
||||
| MongoDB driver (wire, decode, encode) | 19% | 22% |
|
||||
| Meteor mongo (observers, polling, diff) | 12% | 13% |
|
||||
| Node internals | 11% | 12% |
|
||||
| WeKan code | 11% | 10% |
|
||||
| Other Meteor packages and dependencies | 8% | 9% |
|
||||
| Native, EJSON, DDP server (publish, mergebox) | 8% | 10% |
|
||||
|
||||
The DDP server itself, publish and mergebox, is 1%. The largest single functions are
|
||||
the garbage collector, `CollectionDocument` (WeKan's model class), `replaceTypes`
|
||||
(Meteor's BSON-to-EJSON step) and the BSON decoder.
|
||||
|
||||
## Where FerretDB spends its CPU
|
||||
|
||||
With nobody in the browser FerretDB uses 110-510 ms of CPU in the 14 s window the
|
||||
profile covers. Opening a board adds, net: about nothing at 200 cards, **about 1.6 s at
|
||||
2,000 and about 3.9-4.1 s at 5,000** - eight times the Meteor server's CPU for the same
|
||||
open. For the 5,000-card board, of 4.41 s (run 2):
|
||||
|
||||
- the SQLite engine, 1.72 s (39%), a good part of it moving through the pages of the
|
||||
cards table (`btreeInitPage` 18% by itself);
|
||||
- 34% is `filterIterator.Next`: documents read from SQLite and filtered again in Go;
|
||||
- by MongoDB command: `find` 0.79 s, `getMore` 0.74 s, `aggregate` 0.68 s (a `$group`
|
||||
done in Go over the documents it read);
|
||||
- the Go garbage collector, 0.57 s (13%).
|
||||
|
||||
Which WeKan query sends the `aggregate` during the open was **not identified**; the
|
||||
debug log of a 2,000-card open contains none, so the 5,000-card profile may have
|
||||
included the All Boards page's card counters (`getAllBoardsTileData`, a `$group` over
|
||||
the user's boards) that the login lands on first. Treat that line as a lead.
|
||||
|
||||
## What this does and does not say
|
||||
|
||||
- It says the database layer is the part of opening a board that **scales with the board
|
||||
size**, and that on FerretDB (SQLite) a 5,000-card board takes 400-600 ms longer than on
|
||||
MongoDB, with the same data sent to the browser.
|
||||
- It says the **Meteor server is not the bottleneck**: 0.1-0.5 s of CPU, the same on both
|
||||
databases, and the DDP server is about 1% of it. A backend rewritten in another language
|
||||
could save a part of that, and would not change the database or the browser.
|
||||
- It does not separate the browser's own time from the wait for the server: the first
|
||||
card at 0.7 s on a small board is mostly the development bundle and rendering.
|
||||
- Single machine, the development build, one user, two runs, two opens per board, `settled`
|
||||
meaning 1.5 s without a DDP frame. A production build and a loaded server would differ.
|
||||
@@ -0,0 +1,188 @@
|
||||
'use strict';
|
||||
|
||||
// Where the time goes when a WeKan board is opened, with WeKan running on a database
|
||||
// (FerretDB on SQLite, or MongoDB): the browser's wall clock, what crosses the DDP
|
||||
// WebSocket, the Meteor server's CPU by who spends it (Node inspector profile), and
|
||||
// FerretDB's CPU (its pprof). The speed test asks the database alone; this asks whether
|
||||
// the database is what a person waits for.
|
||||
//
|
||||
// # WeKan on FerretDB: meteor run --port 3310 --inspect=9229 with MONGO_URL set,
|
||||
// # FerretDB with --debug-addr=127.0.0.1:8098
|
||||
// WEKAN_BASE_URL=http://localhost:3310 WEKAN_MONGO_URL=mongodb://127.0.0.1:27040/wekan \
|
||||
// node docs/Databases/FerretDB/1/SpeedTest/app/board-open.cjs --sizes 200,2000 \
|
||||
// --inspect 9229 --ferret-pprof http://127.0.0.1:8098 --label ferretdb-sqlite
|
||||
//
|
||||
// Dev servers: the Meteor build is the development one, so the client's own time is
|
||||
// larger than in production; the comparison between databases is what it is for.
|
||||
|
||||
const path = require('path');
|
||||
const { execFileSync } = require('child_process');
|
||||
const ROOT = path.join(__dirname, '../../../../../..');
|
||||
const pw = require(path.join(ROOT, 'tests/playwright/node_modules/playwright-core'));
|
||||
const { MongoClient } = require(path.join(ROOT, 'tests/playwright/node_modules/mongodb'));
|
||||
const WebSocket = require(path.join(ROOT, 'node_modules/ws'));
|
||||
|
||||
function arg(name, fallback) {
|
||||
const i = process.argv.indexOf(`--${name}`);
|
||||
return i !== -1 && process.argv[i + 1] ? process.argv[i + 1] : fallback;
|
||||
}
|
||||
const BASE = process.env.WEKAN_BASE_URL || 'http://localhost:3310';
|
||||
process.env.WEKAN_BASE_URL = BASE;
|
||||
const MONGO = process.env.WEKAN_MONGO_URL || 'mongodb://127.0.0.1:27040/wekan';
|
||||
process.env.WEKAN_MONGO_URL = MONGO;
|
||||
const db = require(path.join(ROOT, 'tests/playwright/helpers/db.js'));
|
||||
const { loginWithToken } = require(path.join(ROOT, 'tests/playwright/helpers/auth.js'));
|
||||
|
||||
const SIZES = arg('sizes', '200,2000').split(',').map(Number);
|
||||
const INSPECT = arg('inspect', '');
|
||||
const PPROF = arg('ferret-pprof', '');
|
||||
const LABEL = arg('label', 'run');
|
||||
const GO = arg('go', path.join(ROOT, '.tools/go/bin/go'));
|
||||
const FERRET_BIN = arg('ferret-bin', path.join(ROOT, '.tools/w/FerretDB/bin/ferretdb'));
|
||||
const QUIET_MS = 1500;
|
||||
|
||||
// ── seeding ─────────────────────────────────────────────────────────────────
|
||||
async function seedBoard(user, size) {
|
||||
const board = db.seedBoard({ ownerId: user.id, title: `Open ${size}`, listCount: 6, cardTitlesPerList: [['Template']] });
|
||||
const template = db.findOne('cards', { boardId: board.boardId, title: 'Template' });
|
||||
const client = await new MongoClient(MONGO).connect();
|
||||
try {
|
||||
const cards = client.db().collection('cards');
|
||||
const docs = [];
|
||||
for (let i = 0; i < size; i += 1) {
|
||||
docs.push({
|
||||
...template,
|
||||
_id: `card${board.boardId}x${i}`,
|
||||
title: `Card ${i} ${['plan', 'build', 'review', 'ship'][i % 4]} the thing`,
|
||||
listId: board.listIds[i % board.listIds.length],
|
||||
sort: Math.floor(i / board.listIds.length) * 100,
|
||||
cardNumber: i + 2,
|
||||
createdAt: new Date(), modifiedAt: new Date(), dateLastActivity: new Date(),
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < docs.length; i += 1000) await cards.insertMany(docs.slice(i, i + 1000));
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
return board;
|
||||
}
|
||||
|
||||
// ── the Meteor server's CPU, from the Node inspector ────────────────────────
|
||||
async function inspector() {
|
||||
const list = await (await fetch(`http://127.0.0.1:${INSPECT}/json`)).json();
|
||||
const ws = new WebSocket(list[0].webSocketDebuggerUrl);
|
||||
await new Promise((resolve, reject) => { ws.on('open', resolve); ws.on('error', reject); });
|
||||
let id = 0;
|
||||
const pending = new Map();
|
||||
ws.on('message', (m) => {
|
||||
const msg = JSON.parse(m);
|
||||
if (msg.id && pending.has(msg.id)) { pending.get(msg.id)(msg); pending.delete(msg.id); }
|
||||
});
|
||||
const send = (method, params = {}) => new Promise((resolve) => { id += 1; pending.set(id, resolve); ws.send(JSON.stringify({ id, method, params })); });
|
||||
await send('Profiler.enable');
|
||||
await send('Profiler.setSamplingInterval', { interval: 250 });
|
||||
return {
|
||||
start: () => send('Profiler.start'),
|
||||
stop: async () => (await send('Profiler.stop')).result.profile,
|
||||
close: () => ws.close(),
|
||||
};
|
||||
}
|
||||
|
||||
const { analyse } = require('./profileAnalysis.cjs');
|
||||
|
||||
// ── FerretDB's CPU, from its pprof ──────────────────────────────────────────
|
||||
async function ferretProfile(seconds) {
|
||||
if (!PPROF) return null;
|
||||
const res = await fetch(`${PPROF}/debug/pprof/profile?seconds=${seconds}`);
|
||||
const file = path.join(process.env.TMPDIR || '/tmp', `ferret-open-${Date.now()}.prof`);
|
||||
require('fs').writeFileSync(file, Buffer.from(await res.arrayBuffer()));
|
||||
const env = { ...process.env, HOME: path.join(ROOT, '.tools') };
|
||||
const top = execFileSync(GO, ['tool', 'pprof', '-top', '-nodecount=80', FERRET_BIN, file], { encoding: 'utf8', env });
|
||||
const total = /Total samples = ([\d.]+)(m?s)/.exec(top);
|
||||
const cpuMs = total ? Number(total[1]) * (total[2] === 's' ? 1000 : 1) : null;
|
||||
// A line reads: flat flat% sum% cum cum% name - SQLite's share is the cumulative time of its VM.
|
||||
const line = top.split('\n').find((l) => /_sqlite3VdbeExec/.test(l));
|
||||
const sqlite = line ? /([\d.]+)(m?s)\s+[\d.]+%\s+[\d.]+%\s+([\d.]+)(m?s)\s+[\d.]+%/.exec(line) : null;
|
||||
// The cumulative top, for the first open: who in FerretDB spends it.
|
||||
const cum = execFileSync(GO, ['tool', 'pprof', '-top', '-cum', '-nodecount=34', FERRET_BIN, file], { encoding: 'utf8', env })
|
||||
.split('\n').filter((l) => /^\s+[\d.]+m?s/.test(l)).map((l) => l.trim().replace(/\s+/g, ' ')).slice(0, 34);
|
||||
return { cum, cpuMs, sqliteMs: sqlite ? Number(sqlite[3]) * (sqlite[4] === 's' ? 1000 : 1) : null };
|
||||
}
|
||||
|
||||
// ── one board open ──────────────────────────────────────────────────────────
|
||||
async function openBoard(user, board, size) {
|
||||
const browser = await pw.chromium.launch();
|
||||
const context = await browser.newContext({ viewport: { width: 1500, height: 1000 } });
|
||||
const page = await context.newPage();
|
||||
await page.addInitScript(() => { try { localStorage.setItem('wekan-mobile-mode', 'false'); } catch (e) { /* none */ } });
|
||||
let frames = 0; let bytes = 0; let last = Date.now();
|
||||
page.on('websocket', (ws) => ws.on('framereceived', (e) => { frames += 1; bytes += e.payload.length; last = Date.now(); }));
|
||||
await loginWithToken(page, user.id, user.token);
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
const insp = INSPECT ? await inspector() : null;
|
||||
frames = 0; bytes = 0;
|
||||
const ferret = PPROF ? ferretProfile(14) : Promise.resolve(null);
|
||||
if (insp) await insp.start();
|
||||
const t0 = Date.now();
|
||||
await page.goto(`${BASE}/b/${board.boardId}/${board.slug}`, { waitUntil: 'commit' });
|
||||
let tFirst = null;
|
||||
await page.waitForSelector('.minicard', { timeout: 60000 }).then(() => { tFirst = Date.now() - t0; });
|
||||
// quiet: no DDP frame for QUIET_MS
|
||||
for (;;) {
|
||||
if (Date.now() - last > QUIET_MS && Date.now() - t0 > (tFirst || 0)) break;
|
||||
if (Date.now() - t0 > 60000) break;
|
||||
await page.waitForTimeout(100);
|
||||
}
|
||||
const tQuiet = Date.now() - t0 - QUIET_MS;
|
||||
const profile = insp ? await insp.stop() : null;
|
||||
const minicards = await page.locator('.minicard').count();
|
||||
const ferretResult = await ferret;
|
||||
if (insp) insp.close();
|
||||
await browser.close();
|
||||
return { size, tFirst, tQuiet, frames, kb: Math.round(bytes / 1024), minicards, server: profile ? analyse(profile) : null, ferret: ferretResult };
|
||||
}
|
||||
|
||||
const f = (v) => (v === null || v === undefined ? '-' : Math.round(v).toLocaleString('en-US'));
|
||||
|
||||
async function main() {
|
||||
const user = db.seedUser();
|
||||
const results = [];
|
||||
// What FerretDB spends in the same 14 s with nobody in the browser: the baseline.
|
||||
const idle = PPROF ? await ferretProfile(14) : null;
|
||||
if (idle) console.error(`FerretDB idle for 14 s: ${f(idle.cpuMs)} ms of CPU`);
|
||||
for (const size of SIZES) {
|
||||
console.error(`seeding a board of ${size} cards...`);
|
||||
const board = await seedBoard(user, size);
|
||||
// The first open of a board is the one a person waits for; a second shows a warm server.
|
||||
for (const pass of ['first open', 'second open']) {
|
||||
console.error(`opening ${size} (${pass})...`);
|
||||
const r = await openBoard(user, board, size);
|
||||
r.pass = pass;
|
||||
results.push(r);
|
||||
}
|
||||
}
|
||||
console.log(`\n## ${LABEL}: opening a board\n`);
|
||||
if (idle) console.log(`FerretDB with nobody in the browser: ${f(idle.cpuMs)} ms of CPU in the same 14 s window (subtract it).\n`);
|
||||
console.log('| Cards | Open | First card (ms) | Settled (ms) | DDP frames | DDP KB | Minicards drawn | Meteor server busy (ms) | FerretDB CPU (ms) | of it SQLite (ms) |');
|
||||
console.log('| ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |');
|
||||
for (const r of results) {
|
||||
console.log(`| ${r.size} | ${r.pass} | ${f(r.tFirst)} | ${f(r.tQuiet)} | ${f(r.frames)} | ${f(r.kb)} | ${f(r.minicards)} | ${f(r.server && r.server.busyMs)} | ${f(r.ferret && r.ferret.cpuMs)} | ${f(r.ferret && r.ferret.sqliteMs)} |`);
|
||||
}
|
||||
for (const r of results.filter((x) => x.pass === 'first open' && x.server)) {
|
||||
console.log(`\n### The Meteor server, ${r.size} cards, first open: ${f(r.server.busyMs)} ms of CPU\n`);
|
||||
for (const [c, ms] of r.server.categories) console.log(`- ${c}: ${f(ms)} ms (${Math.round((100 * ms) / r.server.busyMs)}%)`);
|
||||
if (r.ferret && r.ferret.cum) {
|
||||
console.log(`\n### FerretDB, ${r.size} cards, first open: cumulative CPU by function (flat flat% sum% cum cum% name)\n`);
|
||||
console.log('```');
|
||||
r.ferret.cum.forEach((l) => console.log(l));
|
||||
console.log('```');
|
||||
}
|
||||
console.log('\nTop functions:');
|
||||
for (const [fn, ms] of r.server.topFunctions) console.log(`- ${f(ms)} ms ${fn}`);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (require.main === module) main().catch((e) => { console.error(e); process.exit(1); });
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
// A Node inspector CPU profile of the Meteor server, by who spends the time. Apart from
|
||||
// the browser and the database so a test can drive it with a made-up profile.
|
||||
|
||||
function categorise(node) {
|
||||
const { functionName, url } = node.callFrame;
|
||||
if (functionName === '(idle)') return 'idle';
|
||||
if (functionName === '(garbage collector)') return 'garbage collector';
|
||||
if (functionName === '(program)') return 'native (program)';
|
||||
if (/\/mongodb\/|npm-mongo|\/bson\//.test(url)) return 'MongoDB driver (wire, decode, encode)';
|
||||
if (/packages\/ddp-server|packages\/ddp-common|packages\/ddp\b/.test(url)) return 'Meteor DDP (publish, mergebox, sessions)';
|
||||
if (/packages\/mongo|minimongo|diff-sequence/.test(url)) return 'Meteor mongo (observe, polling, diff)';
|
||||
if (/packages\/ejson/.test(url)) return 'EJSON';
|
||||
// Other Meteor packages and dependencies are not WeKan's: their paths contain /server/ too.
|
||||
if (/programs\/server\/packages|node_modules/.test(url)) return 'other packages';
|
||||
if (/programs\/server\/app|\/_build\/|\/models\/|\/server\/|\/imports\/|wekan/.test(url)) return 'WeKan code';
|
||||
if (/^node:/.test(url) || url === '') return 'node internals';
|
||||
return 'other packages';
|
||||
}
|
||||
|
||||
function analyse(profile) {
|
||||
const byId = new Map(profile.nodes.map((n) => [n.id, n]));
|
||||
const cats = new Map();
|
||||
const fns = new Map();
|
||||
let total = 0;
|
||||
profile.samples.forEach((sid, i) => {
|
||||
const dt = (profile.timeDeltas[i + 1] ?? profile.timeDeltas[i] ?? 0) / 1000; // ms
|
||||
const node = byId.get(sid);
|
||||
const cat = categorise(node);
|
||||
cats.set(cat, (cats.get(cat) || 0) + dt);
|
||||
total += dt;
|
||||
if (cat !== 'idle') {
|
||||
const key = `${node.callFrame.functionName || '(anonymous)'} ${node.callFrame.url.split('/').slice(-2).join('/')}`;
|
||||
fns.set(key, (fns.get(key) || 0) + dt);
|
||||
}
|
||||
});
|
||||
const idle = cats.get('idle') || 0;
|
||||
return {
|
||||
totalMs: total, busyMs: total - idle,
|
||||
categories: [...cats.entries()].filter(([c]) => c !== 'idle').sort((a, b) => b[1] - a[1]),
|
||||
topFunctions: [...fns.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8),
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { categorise, analyse };
|
||||
@@ -0,0 +1,148 @@
|
||||
# Go databases that could be linked into FerretDB, against its SQLite
|
||||
|
||||
FerretDB is one Go binary, and its SQLite handler is the pure-Go driver
|
||||
[modernc.org/sqlite](https://gitlab.com/cznic/sqlite) inside it. Is any database
|
||||
implemented in Go (the [awesome-go list](https://github.com/avelino/awesome-go#databases-implemented-in-go))
|
||||
one that could be linked in beside it, has enough to take SQLite's place, and is
|
||||
faster for what WeKan asks?
|
||||
|
||||
**Short answer: no. Nothing is faster in a way that matters, and the ones that are
|
||||
faster at a single read or write are key-value stores that would make the handler
|
||||
do SQLite's job.**
|
||||
|
||||
This is the engine level of the question, so it can be answered in minutes and
|
||||
without writing a handler for each: [bench.go](main.go) loads the speed test's cards
|
||||
and asks each engine the questions WeKan asks of cards, the way a FerretDB handler
|
||||
written for it would ask them.
|
||||
|
||||
```sh
|
||||
node docs/Databases/FerretDB/1/SpeedTest/embedded/export.cjs 0.05 <dir> # the speed test's cards as JSON lines
|
||||
cd docs/Databases/FerretDB/1/SpeedTest/embedded && go run . -data <dir> # go 1.24+, pure Go, no cgo
|
||||
```
|
||||
|
||||
Run on 2026-11-03 on an Apple M2 Max, Go 1.27, one engine at a time, each in its own
|
||||
directory. `-only "Pebble (CockroachDB's LSM),Nutsdb"` runs some.
|
||||
|
||||
## What is asked
|
||||
|
||||
| Column | The question |
|
||||
| --- | --- |
|
||||
| By id | one card by `_id` |
|
||||
| Board cards | the unarchived cards of a board (an index on boardId and archived), **decoded** from JSON as FerretDB must |
|
||||
| (raw, not decoded) | the same fetch without the decoding: the part that differs between engines |
|
||||
| Count | how many unarchived cards a board has |
|
||||
| Edit, Move | read the card, change it in Go, write it back (FerretDB's `updateOne`), 200 different cards |
|
||||
| Add | insert a card |
|
||||
|
||||
Each read is asked of 15 boards - the 3 biggest and 12 ordinary ones, as in the
|
||||
speed test - and a cell is the average of the 15 medians. Times are milliseconds
|
||||
for one call, **Total** is the sum of the six and the table is ordered by it. Every
|
||||
engine first has to give the right number of cards and the right counts for every
|
||||
board, and the writes must be there afterwards: a fast wrong answer is no answer.
|
||||
|
||||
**SQLite is set up as FerretDB sets it**: WAL, `synchronous=NORMAL`, a 64 MiB cache
|
||||
and 256 MiB of mmap, the document as JSON and the indexes on `json_extract`
|
||||
expressions. A row with plain columns shows what SQLite could do and FerretDB does not.
|
||||
The key-value stores keep a document under `d/<id>` and an index of
|
||||
(boardId, archived) as keys, which is what a handler on them would have to build.
|
||||
No engine fsyncs per commit except where the row says so, to match SQLite's
|
||||
`NORMAL`.
|
||||
|
||||
## 15,377 cards (scale 0.05)
|
||||
|
||||
| # | Engine | Total | By id | Board cards | (raw, not decoded) | Count | Edit | Move | Add | Load (s) | Size (MB) |
|
||||
| ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| 1 | Nutsdb | **1.394** | 0.0004 | 1.332 | 0.057 | 0.0066 | 0.019 | 0.032 | 0.0041 | 0.0 | 13 |
|
||||
| 2 | bbolt, no fsync | **1.481** | 0.0005 | 1.349 | 0.055 | 0.0047 | 0.037 | 0.054 | 0.035 | 0.2 | 27 |
|
||||
| 3 | SQLite, plain columns (not what FerretDB does) | **1.598** | 0.0067 | 1.385 | 0.116 | 0.025 | 0.057 | 0.067 | 0.058 | 0.1 | 15 |
|
||||
| 4 | Badger | **1.623** | 0.0015 | 1.536 | 0.152 | 0.012 | 0.027 | 0.040 | 0.0065 | 0.0 | n/a |
|
||||
| 5 | SQLite as FerretDB uses it (JSON, expression indexes) | **1.677** | 0.0078 | 1.421 | 0.113 | 0.037 | 0.073 | 0.082 | 0.057 | 0.2 | 14 |
|
||||
| 6 | SQLite as FerretDB uses it, synchronous=FULL | **1.747** | 0.0079 | 1.421 | 0.118 | 0.037 | 0.098 | 0.099 | 0.084 | 0.2 | 14 |
|
||||
| 7 | BuntDB (in memory with an append-only file) | **1.795** | 0.0003 | 1.470 | 0.186 | 0.166 | 0.066 | 0.072 | 0.021 | 0.3 | 12 |
|
||||
| 8 | Pebble (CockroachDB's LSM) | **1.841** | 0.0028 | 1.770 | 0.298 | 0.011 | 0.021 | 0.034 | 0.0010 | 0.1 | 16 |
|
||||
| 9 | bbolt, fsync on every commit (its default) | **25.5** | 0.0005 | 1.370 | 0.057 | 0.0048 | 8.062 | 8.025 | 8.015 | 0.2 | 27 |
|
||||
| 10 | Clover (document database over Badger) | **145** | 0.016 | 59.1 | 58.5 | 57.2 | 9.412 | 9.635 | 9.751 | 0.7 | 41 |
|
||||
|
||||
## 153,768 cards (scale 0.5)
|
||||
|
||||
| # | Engine | Total | By id | Board cards | (raw, not decoded) | Count | Edit | Move | Add | Load (s) | Size (MB) |
|
||||
| ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| 1 | SQLite, plain columns (not what FerretDB does) | **5.950** | 0.0071 | 5.693 | 0.418 | 0.062 | 0.052 | 0.072 | 0.064 | 1.1 | 147 |
|
||||
| 2 | Nutsdb | **6.007** | 0.0005 | 5.923 | 0.326 | 0.024 | 0.022 | 0.033 | 0.0044 | 0.3 | 132 |
|
||||
| 3 | bbolt, no fsync | **6.065** | 0.0005 | 5.909 | 0.289 | 0.019 | 0.042 | 0.056 | 0.039 | 14.5 | 274 |
|
||||
| 4 | SQLite as FerretDB uses it, synchronous=FULL | **6.118** | 0.0089 | 5.726 | 0.413 | 0.103 | 0.098 | 0.097 | 0.085 | 1.8 | 141 |
|
||||
| 5 | SQLite as FerretDB uses it (JSON, expression indexes) | **6.153** | 0.0082 | 5.830 | 0.430 | 0.103 | 0.072 | 0.079 | 0.062 | 1.8 | 141 |
|
||||
| 6 | BuntDB (in memory with an append-only file) | **7.001** | 0.0003 | 6.130 | 0.753 | 0.682 | 0.080 | 0.083 | 0.026 | 4.3 | 124 |
|
||||
| 7 | Badger | **7.419** | 0.0025 | 7.273 | 1.483 | 0.060 | 0.032 | 0.042 | 0.0096 | 0.4 | n/a |
|
||||
| 8 | Pebble (CockroachDB's LSM) | **10.5** | 0.023 | 10.3 | 4.992 | 0.056 | 0.033 | 0.032 | 0.0012 | 2.0 | 67 |
|
||||
| 9 | bbolt, fsync on every commit (its default) | **42.0** | 0.0006 | 5.923 | 0.296 | 0.019 | 11.8 | 12.2 | 12.1 | 14.4 | 274 |
|
||||
| 10 | Clover (document database over Badger) | **1176** | 0.017 | 577 | 571 | 563 | 11.9 | 11.7 | 11.8 | 25.7 | 301 |
|
||||
|
||||
## What it says
|
||||
|
||||
- **Nothing is faster where it counts.** A board's cards cost 1.4 ms at 15,000 cards
|
||||
and 5.7-6.2 ms at 154,000 for every engine that is not slower, and nearly all of
|
||||
it is decoding the JSON, which is the same work whatever stores it. Totals are
|
||||
within 5% of SQLite's for Nutsdb and bbolt.
|
||||
- **The key-value stores win single reads and writes, by microseconds.** A card by
|
||||
id takes 0.0003-0.0006 ms on bbolt, Nutsdb and BuntDB against SQLite's 0.008; an insert
|
||||
0.001 ms on Pebble against 0.062; an edit 0.022 ms on Nutsdb against 0.072. But the
|
||||
cheapest call through FerretDB costs about 0.2 ms in the speed test (the protocol,
|
||||
the handler, the network) and most cost more, so an engine that took nothing at all would make one call
|
||||
at most a few percent faster.
|
||||
- **Some are slower, and by a lot.** Pebble's LSM is 12x SQLite on fetching a big
|
||||
board's cards (5.0 ms against 0.43), Badger 3.5x, Clover about 1,300x (it decodes every
|
||||
document to answer anything), 90-190x in total. **bbolt with its default - an fsync on every commit
|
||||
- takes 8-12 ms per write, 100-160x SQLite's**; without it, it is as fast as
|
||||
SQLite and not durable the same way.
|
||||
- **The cost is in what is not there.** A key-value store has keys. FerretDB's SQLite
|
||||
handler is about 8,500 lines because filters, sorting, `$or`, `$in`, regular
|
||||
expressions, aggregation, indexes and statistics are SQL there. On a key-value
|
||||
store the handler would do them in Go, and this benchmark asked only the six
|
||||
easiest questions. Of the stores here only BuntDB and Clover have indexes on JSON
|
||||
fields, and neither has a query language that could take SQLite's place.
|
||||
- **Loading** at 154,000 cards: Badger 0.4 s, Nutsdb 0.3 s, Pebble 2.0 s, SQLite 1.8
|
||||
s, BuntDB 4.3 s, bbolt 14.5 s (unsorted inserts), Clover 26 s.
|
||||
- Disk: SQLite 141 MB, Pebble 67, Nutsdb 132, BuntDB 124, bbolt 274, Clover 301.
|
||||
|
||||
## The list, entry by entry
|
||||
|
||||
Tested (Go library, open source, maintained, pure Go):
|
||||
|
||||
| Entry | Licence, last push | Result |
|
||||
| --- | --- | --- |
|
||||
| [go-sqlite](https://gitlab.com/cznic/sqlite) | BSD-3, FerretDB's own | the baseline, three ways |
|
||||
| [Pebble](https://github.com/cockroachdb/pebble) | BSD-3, 2026-11 | fastest insert and edit, 12x slower board fetch |
|
||||
| [Badger](https://github.com/dgraph-io/badger) | Apache-2.0, 2026-10 | fast writes, 3.5x slower board fetch |
|
||||
| [bbolt](https://github.com/etcd-io/bbolt) | MIT, 2026-10 | as fast as SQLite without fsync, 100x slower writes with it |
|
||||
| [BuntDB](https://github.com/tidwall/buntdb) | MIT, 2026-05 | fastest by id, slowest count (index scan), in memory |
|
||||
| [Nutsdb](https://github.com/nutsdb/nutsdb) | Apache-2.0, 2026-10 | on par with SQLite, faster point operations |
|
||||
| [Clover](https://github.com/ostafen/clover) | MIT, 2025-09 | has indexes and queries, 90-190x slower in total |
|
||||
|
||||
Not tested, and why:
|
||||
|
||||
| Entry | Why not |
|
||||
| --- | --- |
|
||||
| goleveldb (2024-05), tiedot (2021), pudge (2021), scribble (2023), ledisdb (2023), hare, diskv, libradb | not maintained since 2021-2024, or toys; the engines that matter of their kind (LSM, B+tree) are Pebble, Badger and bbolt, tested |
|
||||
| Bitcask, pogreb, moss, LotusDB, RoseDB, Coffer, tempdb | more key-value stores of the same kind; they would also need the handler to do what SQL does |
|
||||
| gedb, [minisql](https://github.com/RichardKnop/minisql) (54 stars) | not looked at beyond their descriptions: a MongoDB-like store and a young single-file SQL database |
|
||||
| [rqlite](https://github.com/rqlite/rqlite), [redka](https://github.com/nalgeon/redka) | they are SQLite underneath, so not faster by construction |
|
||||
| [Dolt](https://github.com/dolthub/dolt), [TiDB](https://github.com/pingcap/tidb) | tested as servers in the [speed test](../) (Dolt the slowest of all, TiDB 2.3x MySQL's total); embedding their engines (go-mysql-server) would bring the same engines and far more code |
|
||||
| CockroachDB | not open source (proprietary licence) |
|
||||
| immudb | GitHub does not recognise its licence as an open-source one (`NOASSERTION`); an immutable ledger |
|
||||
| DiceDB, godis, rotom, Vasto, ledisdb | Redis-style servers, not libraries |
|
||||
| Dgraph, EliasDB, NornicDB, MemHop | graph databases |
|
||||
| InfluxDB, Prometheus, VictoriaMetrics, LinDB, lynxdb, nanotdb, unitdb | time series and logs |
|
||||
| Milvus | vector database |
|
||||
| column | in-memory columnar store |
|
||||
| objectbox-go | needs cgo |
|
||||
| CovenantSQL, Databunker, NoKV, piladb | blockchain, a PII vault, a metadata service, a stack |
|
||||
|
||||
## What would be needed to know more
|
||||
|
||||
A real answer for one engine means a handler for it: query pushdown, updates,
|
||||
indexes, statistics, 2,000-8,000 lines each. This benchmark says where it could pay:
|
||||
nowhere on reads (decoding dominates), and only on small writes, which the protocol
|
||||
hides. It does not measure what SQLite does in the engine and a key-value store
|
||||
would do in Go - sorting, `$or`, regular expressions, aggregation - where SQLite can
|
||||
only be ahead.
|
||||
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
)
|
||||
|
||||
type badgerEng struct {
|
||||
db *badger.DB
|
||||
dir string
|
||||
}
|
||||
|
||||
func openBadger() maker {
|
||||
return maker{name: "Badger", open: func(dir string) (Engine, error) {
|
||||
db, err := badger.Open(badger.DefaultOptions(dir).WithLogger(nil).WithSyncWrites(false))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &badgerEng{db: db, dir: dir}, nil
|
||||
}}
|
||||
}
|
||||
|
||||
func (e *badgerEng) Name() string { return "Badger" }
|
||||
|
||||
func (e *badgerEng) Load(cards []Card) error {
|
||||
wb := e.db.NewWriteBatch()
|
||||
defer wb.Cancel()
|
||||
for _, c := range cards {
|
||||
if err := wb.Set(docKey(c.ID), c.Raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := wb.Set(indexKey(c), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return wb.Flush()
|
||||
}
|
||||
|
||||
func (e *badgerEng) ByID(id string) ([]byte, error) {
|
||||
var out []byte
|
||||
err := e.db.View(func(tx *badger.Txn) error {
|
||||
it, err := tx.Get(docKey(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err = it.ValueCopy(nil)
|
||||
return err
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (e *badgerEng) BoardCards(board string) ([][]byte, error) {
|
||||
var out [][]byte
|
||||
err := e.db.View(func(tx *badger.Txn) error {
|
||||
p := boardPrefix(board)
|
||||
it := tx.NewIterator(badger.IteratorOptions{Prefix: p})
|
||||
defer it.Close()
|
||||
var ids []string
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
ids = append(ids, idFromIndexKey(it.Item().Key()))
|
||||
}
|
||||
for _, id := range ids {
|
||||
item, err := tx.Get(docKey(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out = append(out, v)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (e *badgerEng) CountBoard(board string) (int, error) {
|
||||
n := 0
|
||||
err := e.db.View(func(tx *badger.Txn) error {
|
||||
it := tx.NewIterator(badger.IteratorOptions{Prefix: boardPrefix(board)})
|
||||
defer it.Close()
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
n++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (e *badgerEng) Update(id string, change func([]byte) ([]byte, error)) error {
|
||||
return e.db.Update(func(tx *badger.Txn) error {
|
||||
item, err := tx.Get(docKey(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
next, err := change(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Set(docKey(id), next)
|
||||
})
|
||||
}
|
||||
|
||||
func (e *badgerEng) Insert(c Card) error {
|
||||
return e.db.Update(func(tx *badger.Txn) error {
|
||||
if err := tx.Set(docKey(c.ID), c.Raw); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Set(indexKey(c), nil)
|
||||
})
|
||||
}
|
||||
|
||||
// SizeBytes is -1: Badger keeps a recent write in its memtable and preallocates its
|
||||
// log files, so the directory says nothing about what the data takes.
|
||||
func (e *badgerEng) SizeBytes() int64 { return -1 }
|
||||
func (e *badgerEng) Close() error { return e.db.Close() }
|
||||
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
type boltEng struct {
|
||||
db *bolt.DB
|
||||
dir string
|
||||
}
|
||||
|
||||
// bbolt fsyncs on every commit, which the others here do not; "NoSync" is the same
|
||||
// engine without it, for a like-for-like with SQLite's synchronous=NORMAL.
|
||||
func openBolt(name string, noSync bool) maker {
|
||||
return maker{name: name, open: func(dir string) (Engine, error) {
|
||||
db, err := bolt.Open(filepath.Join(dir, "cards.bolt"), 0o600, &bolt.Options{NoSync: noSync})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.NoSync = noSync
|
||||
return &boltEng{db: db, dir: dir}, nil
|
||||
}}
|
||||
}
|
||||
|
||||
func (e *boltEng) Name() string { return "bbolt" }
|
||||
|
||||
func (e *boltEng) Load(cards []Card) error {
|
||||
return e.db.Update(func(tx *bolt.Tx) error {
|
||||
d, err := tx.CreateBucketIfNotExists([]byte("d"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := tx.CreateBucketIfNotExists([]byte("b"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range cards {
|
||||
if err := d.Put([]byte(c.ID), c.Raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.Put(indexKey(c)[2:], nil); err != nil { // without the b/
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (e *boltEng) ByID(id string) ([]byte, error) {
|
||||
var out []byte
|
||||
err := e.db.View(func(tx *bolt.Tx) error {
|
||||
v := tx.Bucket([]byte("d")).Get([]byte(id))
|
||||
if v == nil {
|
||||
return fmt.Errorf("not found")
|
||||
}
|
||||
out = append([]byte(nil), v...)
|
||||
return nil
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (e *boltEng) scan(tx *bolt.Tx, board string, f func(id string) error) error {
|
||||
p := boardPrefix(board)[2:]
|
||||
c := tx.Bucket([]byte("b")).Cursor()
|
||||
for k, _ := c.Seek(p); k != nil && bytes.HasPrefix(k, p); k, _ = c.Next() {
|
||||
if err := f(idFromIndexKey(k)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *boltEng) BoardCards(board string) ([][]byte, error) {
|
||||
var out [][]byte
|
||||
err := e.db.View(func(tx *bolt.Tx) error {
|
||||
d := tx.Bucket([]byte("d"))
|
||||
return e.scan(tx, board, func(id string) error {
|
||||
out = append(out, append([]byte(nil), d.Get([]byte(id))...))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (e *boltEng) CountBoard(board string) (int, error) {
|
||||
n := 0
|
||||
err := e.db.View(func(tx *bolt.Tx) error { return e.scan(tx, board, func(string) error { n++; return nil }) })
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (e *boltEng) Update(id string, change func([]byte) ([]byte, error)) error {
|
||||
return e.db.Update(func(tx *bolt.Tx) error {
|
||||
d := tx.Bucket([]byte("d"))
|
||||
cur := d.Get([]byte(id))
|
||||
if cur == nil {
|
||||
return fmt.Errorf("not found")
|
||||
}
|
||||
next, err := change(append([]byte(nil), cur...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.Put([]byte(id), next)
|
||||
})
|
||||
}
|
||||
|
||||
func (e *boltEng) Insert(c Card) error {
|
||||
return e.db.Update(func(tx *bolt.Tx) error {
|
||||
if err := tx.Bucket([]byte("d")).Put([]byte(c.ID), c.Raw); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Bucket([]byte("b")).Put(indexKey(c)[2:], nil)
|
||||
})
|
||||
}
|
||||
|
||||
func (e *boltEng) SizeBytes() int64 { return dirSize(e.dir) }
|
||||
func (e *boltEng) Close() error { return e.db.Close() }
|
||||
@@ -0,0 +1,101 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tidwall/buntdb"
|
||||
)
|
||||
|
||||
// BuntDB keeps everything in memory and writes an append-only file. It has indexes
|
||||
// on JSON fields of its own, so no index keys are built by hand.
|
||||
type buntEng struct {
|
||||
db *buntdb.DB
|
||||
dir string
|
||||
}
|
||||
|
||||
func openBunt() maker {
|
||||
return maker{name: "BuntDB (in memory with an append-only file)", open: func(dir string) (Engine, error) {
|
||||
db, err := buntdb.Open(filepath.Join(dir, "cards.db"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &buntEng{db: db, dir: dir}, nil
|
||||
}}
|
||||
}
|
||||
|
||||
func (e *buntEng) Name() string { return "BuntDB" }
|
||||
|
||||
func (e *buntEng) Load(cards []Card) error {
|
||||
if err := e.db.Update(func(tx *buntdb.Tx) error {
|
||||
for _, c := range cards {
|
||||
if _, _, err := tx.Set("card:"+c.ID, string(c.Raw), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.db.CreateIndex("board", "card:*", buntdb.IndexJSON("boardId"), buntdb.IndexJSON("archived"))
|
||||
}
|
||||
|
||||
func (e *buntEng) ByID(id string) ([]byte, error) {
|
||||
var out []byte
|
||||
err := e.db.View(func(tx *buntdb.Tx) error {
|
||||
v, err := tx.Get("card:" + id)
|
||||
out = []byte(v)
|
||||
return err
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (e *buntEng) pivot(board string) string {
|
||||
b, _ := json.Marshal(board)
|
||||
return fmt.Sprintf(`{"boardId":%s,"archived":false}`, b)
|
||||
}
|
||||
|
||||
func (e *buntEng) BoardCards(board string) ([][]byte, error) {
|
||||
var out [][]byte
|
||||
err := e.db.View(func(tx *buntdb.Tx) error {
|
||||
return tx.AscendEqual("board", e.pivot(board), func(_, v string) bool {
|
||||
out = append(out, []byte(v))
|
||||
return true
|
||||
})
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (e *buntEng) CountBoard(board string) (int, error) {
|
||||
n := 0
|
||||
err := e.db.View(func(tx *buntdb.Tx) error {
|
||||
return tx.AscendEqual("board", e.pivot(board), func(_, _ string) bool { n++; return true })
|
||||
})
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (e *buntEng) Update(id string, change func([]byte) ([]byte, error)) error {
|
||||
return e.db.Update(func(tx *buntdb.Tx) error {
|
||||
v, err := tx.Get("card:" + id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
next, err := change([]byte(v))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, err = tx.Set("card:"+id, string(next), nil)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (e *buntEng) Insert(c Card) error {
|
||||
return e.db.Update(func(tx *buntdb.Tx) error {
|
||||
_, _, err := tx.Set("card:"+c.ID, string(c.Raw), nil)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (e *buntEng) SizeBytes() int64 { return dirSize(e.dir) }
|
||||
func (e *buntEng) Close() error { return e.db.Close() }
|
||||
@@ -0,0 +1,143 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
c "github.com/ostafen/clover/v2"
|
||||
d "github.com/ostafen/clover/v2/document"
|
||||
q "github.com/ostafen/clover/v2/query"
|
||||
)
|
||||
|
||||
// Clover is a document database with indexes and queries of its own, over Badger.
|
||||
type cloverEng struct {
|
||||
db *c.DB
|
||||
dir string
|
||||
}
|
||||
|
||||
func openClover() maker {
|
||||
return maker{name: "Clover (document database over Badger)", open: func(dir string) (Engine, error) {
|
||||
db, err := c.Open(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := db.CreateCollection("cards"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cloverEng{db: db, dir: dir}, nil
|
||||
}}
|
||||
}
|
||||
|
||||
func (e *cloverEng) Name() string { return "Clover" }
|
||||
|
||||
func toDoc(raw []byte) (*d.Document, error) {
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// _id is Clover's own (a UUID); the card's id is kept in cid.
|
||||
m["cid"] = m["_id"]
|
||||
delete(m, "_id")
|
||||
return d.NewDocumentOf(m), nil
|
||||
}
|
||||
|
||||
// fromDoc is the card as JSON again, with its own _id back.
|
||||
func fromDoc(doc *d.Document) ([]byte, error) {
|
||||
m := map[string]any{}
|
||||
for k, v := range doc.AsMap() {
|
||||
m[k] = v
|
||||
}
|
||||
m["_id"] = m["cid"]
|
||||
delete(m, "cid")
|
||||
delete(m, "_id_clover")
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
func (e *cloverEng) Load(cards []Card) error {
|
||||
docs := make([]*d.Document, 0, len(cards))
|
||||
for _, card := range cards {
|
||||
doc, err := toDoc(card.Raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docs = append(docs, doc)
|
||||
}
|
||||
if err := e.db.Insert("cards", docs...); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range []string{"cid", "boardId"} {
|
||||
if err := e.db.CreateIndex("cards", f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *cloverEng) one(id string) (*d.Document, error) {
|
||||
return e.db.FindFirst(q.NewQuery("cards").Where(q.Field("cid").Eq(id)))
|
||||
}
|
||||
|
||||
func (e *cloverEng) ByID(id string) ([]byte, error) {
|
||||
doc, err := e.one(id)
|
||||
if err != nil || doc == nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromDoc(doc)
|
||||
}
|
||||
|
||||
func (e *cloverEng) query(board string) *q.Query {
|
||||
return q.NewQuery("cards").Where(q.Field("boardId").Eq(board).And(q.Field("archived").Eq(false)))
|
||||
}
|
||||
|
||||
func (e *cloverEng) BoardCards(board string) ([][]byte, error) {
|
||||
docs, err := e.db.FindAll(e.query(board))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Clover returns decoded documents. They are encoded again so the runner decodes
|
||||
// them like every other engine's - which charges Clover for a step it does not need.
|
||||
out := make([][]byte, 0, len(docs))
|
||||
for _, doc := range docs {
|
||||
b, err := fromDoc(doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, b)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (e *cloverEng) CountBoard(board string) (int, error) { return e.db.Count(e.query(board)) }
|
||||
|
||||
func (e *cloverEng) Update(id string, change func([]byte) ([]byte, error)) error {
|
||||
doc, err := e.one(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
raw, err := fromDoc(doc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
next, err := change(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(next, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
m["cid"] = m["_id"]
|
||||
delete(m, "_id")
|
||||
return e.db.Update(q.NewQuery("cards").Where(q.Field("cid").Eq(id)), m)
|
||||
}
|
||||
|
||||
func (e *cloverEng) Insert(card Card) error {
|
||||
doc, err := toDoc(card.Raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = e.db.InsertOne("cards", doc)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *cloverEng) SizeBytes() int64 { return dirSize(e.dir) }
|
||||
func (e *cloverEng) Close() error { return e.db.Close() }
|
||||
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
// engines lists what is compared, the baseline first.
|
||||
func engines() []maker {
|
||||
return []maker{
|
||||
openSQLite("SQLite as FerretDB uses it (JSON, expression indexes)", "normal", false),
|
||||
openSQLite("SQLite, plain columns (not what FerretDB does)", "normal", true),
|
||||
openSQLite("SQLite as FerretDB uses it, synchronous=FULL", "full", false),
|
||||
openPebble(),
|
||||
openBadger(),
|
||||
openBolt("bbolt, no fsync", true),
|
||||
openBolt("bbolt, fsync on every commit (its default)", false),
|
||||
openBunt(),
|
||||
openNuts(),
|
||||
openClover(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
// Writes the speed test's deterministic data as JSON lines for the Go benchmark of
|
||||
// embedded engines (bench.go): the cards, with the fields the queries read, and the
|
||||
// parameters (the 3 biggest boards and 12 ordinary ones) the same way run.cjs picks
|
||||
// them. node embedded/export.cjs <scale> <out-directory>
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const data = require('../data.cjs');
|
||||
|
||||
const scale = Number(process.argv[2] || '0.05');
|
||||
const out = path.resolve(process.argv[3] || '.');
|
||||
fs.mkdirSync(out, { recursive: true });
|
||||
|
||||
const plan = data.plan({ scale });
|
||||
const lines = [];
|
||||
const perBoard = new Map();
|
||||
let n = 0;
|
||||
for (const batch of data.documents(plan, 'cards', 5000)) {
|
||||
for (const doc of batch) {
|
||||
lines.push(JSON.stringify(doc, (k, v) => (v instanceof Date ? { $date: v.getTime() } : v)));
|
||||
perBoard.set(doc.boardId, (perBoard.get(doc.boardId) || 0) + 1);
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(path.join(out, 'cards.jsonl'), `${lines.join('\n')}\n`);
|
||||
|
||||
// Parameters: the 3 biggest boards, then 12 ordinary ones spread over the rest.
|
||||
const boards = [...perBoard.entries()].sort((a, b) => b[1] - a[1]);
|
||||
const biggest = boards.slice(0, 3).map(b => b[0]);
|
||||
const rest = boards.slice(3);
|
||||
const step = Math.max(1, Math.floor(rest.length / 12));
|
||||
const ordinary = Array.from({ length: 12 }, (_, i) => rest[Math.min(rest.length - 1, i * step)][0]);
|
||||
fs.writeFileSync(path.join(out, 'params.json'), JSON.stringify({ scale, cards: n, boards: [...biggest, ...ordinary], sizes: boards.slice(0, 3).map(b => b[1]) }));
|
||||
console.log(`${n} cards, ${perBoard.size} boards; biggest boards have ${boards.slice(0, 3).map(b => b[1]).join(', ')} cards`);
|
||||
@@ -0,0 +1,86 @@
|
||||
module embeddedbench
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
github.com/cockroachdb/pebble v1.1.5
|
||||
github.com/dgraph-io/badger/v4 v4.9.6
|
||||
github.com/nutsdb/nutsdb v1.1.0
|
||||
github.com/ostafen/clover/v2 v2.0.0-alpha.3
|
||||
github.com/tidwall/buntdb v1.3.2
|
||||
go.etcd.io/bbolt v1.5.0
|
||||
modernc.org/sqlite v1.61.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/DataDog/zstd v1.4.5 // indirect
|
||||
github.com/antlabs/stl v0.0.2 // indirect
|
||||
github.com/antlabs/timer v0.1.4 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cockroachdb/errors v1.11.3 // indirect
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
|
||||
github.com/cockroachdb/redact v1.1.5 // indirect
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgraph-io/badger/v3 v3.2103.2 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.1.0 // indirect
|
||||
github.com/edsrzf/mmap-go v1.2.0 // indirect
|
||||
github.com/getsentry/sentry-go v0.27.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/glog v1.2.4 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/flatbuffers v25.2.10+incompatible // indirect
|
||||
github.com/google/orderedcode v0.0.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/klauspost/compress v1.18.7 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/ncruces/go-strftime v1.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.15.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/tidwall/btree v1.6.0 // indirect
|
||||
github.com/tidwall/gjson v1.14.3 // indirect
|
||||
github.com/tidwall/grect v0.1.4 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/tidwall/rtred v0.1.2 // indirect
|
||||
github.com/tidwall/tinyqueue v0.1.1 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/xujiajun/utils v0.0.0-20220904132955-5f7c5b914235 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.42.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||
golang.org/x/net v0.62.0 // indirect
|
||||
golang.org/x/sys v0.49.0 // indirect
|
||||
golang.org/x/text v0.43.0 // indirect
|
||||
google.golang.org/protobuf v1.36.7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.79.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.12.1 // indirect
|
||||
)
|
||||
@@ -0,0 +1,379 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/antlabs/stl v0.0.2 h1:sna1AXR5yIkNE9lWhCcKbheFJSVfCa3vugnGyakI79s=
|
||||
github.com/antlabs/stl v0.0.2/go.mod h1:kKrO4xrn9cfS1mJVo+/BqePZjAYMXqD0amGF2Ouq7ac=
|
||||
github.com/antlabs/timer v0.1.4 h1:MHdE00MDnNfhJCmqSOdLXs35uGNwfkMwfbynxrGmQ1c=
|
||||
github.com/antlabs/timer v0.1.4/go.mod h1:mpw4zlD5KVjstEyUDp43DGLWsY076Mdo4bS78NTseRE=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/brianvoe/gofakeit/v6 v6.17.0 h1:obbQTJeHfktJtiZzq0Q1bEpsNUs+yHrYlPVWt7BtmJ4=
|
||||
github.com/brianvoe/gofakeit/v6 v6.17.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
|
||||
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
|
||||
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
|
||||
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
|
||||
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
|
||||
github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw=
|
||||
github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo=
|
||||
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
|
||||
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger/v3 v3.2103.2 h1:dpyM5eCJAtQCBcMCZcT4UBZchuTJgCywerHHgmxfxM8=
|
||||
github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M=
|
||||
github.com/dgraph-io/badger/v4 v4.9.6 h1:IQqMPVGLNCQr1b4Mu8lHkYm/xyqFRsyKaFEtyLi9CCQ=
|
||||
github.com/dgraph-io/badger/v4 v4.9.6/go.mod h1:Xa9dAupjbwAacupWFCpa6YEn9E1PjBXkfZYr2I/8aWg=
|
||||
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
|
||||
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2xWg=
|
||||
github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI=
|
||||
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
|
||||
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc=
|
||||
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v2.0.6+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us=
|
||||
github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20=
|
||||
github.com/google/pprof v0.0.0-20261016150608-1facf8f91b50 h1:PlNWJRxu5APSV0pWqfIsZ0qt9OOGHSWfZQ4zvx+WgWs=
|
||||
github.com/google/pprof v0.0.0-20261016150608-1facf8f91b50/go.mod h1:yUo1AMMpsPIDFGuwDK8nLcf8+vvAd5l2O6JDZ6hpwrM=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
|
||||
github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/ncruces/go-strftime v1.1.0 h1:vtPmPWMkb539RGV9WpWT9EwJGvrOCKvMNI4obWL47Hk=
|
||||
github.com/ncruces/go-strftime v1.1.0/go.mod h1:yVaq7iwCN09vHRrKBrrflXT0M9tTMG2KmZ9Fj7xeG/g=
|
||||
github.com/nutsdb/nutsdb v1.1.0 h1:fNGFzBHGqF2mB5BF8Qk8W94c3/ZzwdCdKAH7azwx70Y=
|
||||
github.com/nutsdb/nutsdb v1.1.0/go.mod h1:aKCtgSprZf2Mp1dIQD00Iya3DttoTErSSOnRx5ZtpAs=
|
||||
github.com/ostafen/clover/v2 v2.0.0-alpha.3 h1:fXC7tVHQkUPFlxlj/kD98h0ngrTpIeJymaxVIqDzw3Q=
|
||||
github.com/ostafen/clover/v2 v2.0.0-alpha.3/go.mod h1:5YCDt+wJDUNN1uSXE5csxSQBuJrNjidkOkJTXWuNhDY=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM=
|
||||
github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
|
||||
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
|
||||
github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg=
|
||||
github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
|
||||
github.com/tidwall/buntdb v1.3.2 h1:qd+IpdEGs0pZci37G4jF51+fSKlkuUTMXuHhXL1AkKg=
|
||||
github.com/tidwall/buntdb v1.3.2/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
|
||||
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
|
||||
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=
|
||||
github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=
|
||||
github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
|
||||
github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
|
||||
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
|
||||
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
|
||||
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/xujiajun/utils v0.0.0-20220904132955-5f7c5b914235 h1:w0si+uee0iAaCJO9q86T6yrhdadgcsoNuh47LrUykzg=
|
||||
github.com/xujiajun/utils v0.0.0-20220904132955-5f7c5b914235/go.mod h1:MR4+0R6A9NS5IABnIM3384FfOq8QFVnm7WDrBOhIaMU=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU=
|
||||
go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
|
||||
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=
|
||||
golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
|
||||
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
|
||||
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
|
||||
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.52.0 h1:ryxg4U63oaUYk7uOLTF37n43f4qgtGvqV4Ioit1BaEs=
|
||||
golang.org/x/tools v0.52.0/go.mod h1:ItcP5UNaVGG+Yc4eIYmaFLNJ4soEopkx5Y2d4lcEurs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
modernc.org/cc/v4 v4.30.3 h1:aDq8gIX1X7+VFgO/JYYji6pLWL2H4tv3g8WatT3KCys=
|
||||
modernc.org/cc/v4 v4.30.3/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.37.0 h1:BAuJCUtIHIxFIYaFhqXIw5YaoMBlNCUlFovejHfMip4=
|
||||
modernc.org/ccgo/v4 v4.37.0/go.mod h1:/8627VhwWtAbqUXmPORxFQU5SgLrAx2jl9seLw9GBhY=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.2.0 h1:/Y3LGDMQaoYf55PebP1qsZ5FDGgAZyG1RYKEHeQzh1o=
|
||||
modernc.org/gc/v3 v3.2.0/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.79.0 h1:9Dvvv++bAMSkSuBzuOFAatbTV5OIoq6Y6FohmD3e7q8=
|
||||
modernc.org/libc v1.79.0/go.mod h1:uT3kR61NsbeKJSLqq83GJwSnZMVLlSjyAz4DfTaBy1M=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
|
||||
modernc.org/memory v1.12.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.61.0 h1:sQi63oLQ9uquAUA4CzOisH35Q3pEszkPBuHM0tFL8CY=
|
||||
modernc.org/sqlite v1.61.0/go.mod h1:f4gr5tbpx2umklaT/QVtbi/50hNdM+4ng+2zMo8KXJ0=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
// The key layout a handler on a key-value store would have to build: the document
|
||||
// under d/<id>, and the index of (boardId, archived) as keys b/<board>/<0|1>/<id>
|
||||
// with no value. A scan of the prefix b/<board>/0/ is "the unarchived cards of a
|
||||
// board" and counting its keys is the count.
|
||||
func docKey(id string) []byte { return []byte("d/" + id) }
|
||||
|
||||
func boardPrefix(board string) []byte { return []byte("b/" + board + "/0/") }
|
||||
|
||||
func indexKey(c Card) []byte {
|
||||
a := "0"
|
||||
if c.Archived {
|
||||
a = "1"
|
||||
}
|
||||
return []byte("b/" + c.Board + "/" + a + "/" + c.ID)
|
||||
}
|
||||
|
||||
func idFromIndexKey(k []byte) string {
|
||||
s := string(k)
|
||||
return s[strings.LastIndexByte(s, '/')+1:]
|
||||
}
|
||||
|
||||
// prefixEnd is the first key after every key with the prefix.
|
||||
func prefixEnd(p []byte) []byte {
|
||||
end := append([]byte(nil), p...)
|
||||
end[len(end)-1]++
|
||||
return end
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Embedded engines for FerretDB, compared with the SQLite it uses today.
|
||||
//
|
||||
// FerretDB is one Go binary, and its SQLite handler is the modernc.org/sqlite driver
|
||||
// inside it. Which of the databases implemented in Go (awesome-go) could be linked in
|
||||
// beside it, and is any of them faster for what WeKan asks? This is the engine
|
||||
// level of that question: the same cards, the same questions, asked of each engine
|
||||
// the way a handler written for it would ask. See README.md.
|
||||
//
|
||||
// go run . -data <dir with cards.jsonl and params.json> [-only sqlite,pebble] [-out results.json]
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Card is one WeKan card: what the questions read, and the whole document.
|
||||
type Card struct {
|
||||
ID string `json:"_id"`
|
||||
Board string `json:"boardId"`
|
||||
List string `json:"listId"`
|
||||
Archived bool `json:"archived"`
|
||||
Raw []byte `json:"-"`
|
||||
}
|
||||
|
||||
// Engine is what a FerretDB handler needs from a storage engine for WeKan.
|
||||
type Engine interface {
|
||||
Name() string
|
||||
// Load stores the cards and builds the indexes: by _id, and by (boardId, archived).
|
||||
Load(cards []Card) error
|
||||
ByID(id string) ([]byte, error)
|
||||
// BoardCards returns the unarchived cards of a board.
|
||||
BoardCards(board string) ([][]byte, error)
|
||||
CountBoard(board string) (int, error)
|
||||
// Update reads the card, changes it in Go and writes it back, which is what
|
||||
// FerretDB's updateOne does.
|
||||
Update(id string, change func([]byte) ([]byte, error)) error
|
||||
Insert(c Card) error
|
||||
SizeBytes() int64
|
||||
Close() error
|
||||
}
|
||||
|
||||
type params struct {
|
||||
Scale float64 `json:"scale"`
|
||||
Cards int `json:"cards"`
|
||||
Boards []string `json:"boards"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
dataDir := flag.String("data", "", "directory with cards.jsonl and params.json")
|
||||
only := flag.String("only", "", "comma separated engine names to run")
|
||||
outFile := flag.String("out", "", "write the results as JSON")
|
||||
workDir := flag.String("work", "", "where the engines keep their files (default: a temporary directory)")
|
||||
flag.Parse()
|
||||
|
||||
cards, p := readData(*dataDir)
|
||||
byBoard := map[string][]Card{}
|
||||
for _, c := range cards {
|
||||
byBoard[c.Board] = append(byBoard[c.Board], c)
|
||||
}
|
||||
|
||||
root := *workDir
|
||||
if root == "" {
|
||||
var err error
|
||||
root, err = os.MkdirTemp("", "embeddedbench")
|
||||
must(err)
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
var results []Result
|
||||
for _, mk := range engines() {
|
||||
name := mk.name
|
||||
if *only != "" && !contains(strings.Split(*only, ","), name) {
|
||||
continue
|
||||
}
|
||||
dir := filepath.Join(root, strings.NewReplacer(" ", "_", "/", "_", "(", "", ")", "").Replace(name))
|
||||
must(os.MkdirAll(dir, 0o755))
|
||||
fmt.Fprintf(os.Stderr, "== %s\n", name)
|
||||
res, err := run(mk.open, name, dir, cards, byBoard, p)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, " FAILED: %v\n", err)
|
||||
results = append(results, Result{Name: name, Error: err.Error()})
|
||||
continue
|
||||
}
|
||||
results = append(results, *res)
|
||||
}
|
||||
|
||||
report(results, p)
|
||||
if *outFile != "" {
|
||||
b, _ := json.MarshalIndent(results, "", " ")
|
||||
must(os.WriteFile(*outFile, b, 0o644))
|
||||
}
|
||||
}
|
||||
|
||||
func readData(dir string) ([]Card, params) {
|
||||
var p params
|
||||
b, err := os.ReadFile(filepath.Join(dir, "params.json"))
|
||||
must(err)
|
||||
must(json.Unmarshal(b, &p))
|
||||
|
||||
f, err := os.Open(filepath.Join(dir, "cards.jsonl"))
|
||||
must(err)
|
||||
defer f.Close()
|
||||
|
||||
var cards []Card
|
||||
sc := bufio.NewScanner(f)
|
||||
sc.Buffer(make([]byte, 1<<20), 1<<24)
|
||||
for sc.Scan() {
|
||||
line := bytes.TrimSpace(sc.Bytes())
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
var c Card
|
||||
must(json.Unmarshal(line, &c))
|
||||
c.Raw = append([]byte(nil), line...)
|
||||
cards = append(cards, c)
|
||||
}
|
||||
must(sc.Err())
|
||||
return cards, p
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func contains(list []string, s string) bool {
|
||||
for _, x := range list {
|
||||
if x == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func median(d []time.Duration) time.Duration {
|
||||
sort.Slice(d, func(i, j int) bool { return d[i] < d[j] })
|
||||
return d[len(d)/2]
|
||||
}
|
||||
|
||||
// modify is the change an updateOne makes: decode, set, encode.
|
||||
func modify(raw []byte, key string, value any) ([]byte, error) {
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[key] = value
|
||||
return json.Marshal(m)
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nutsdb/nutsdb"
|
||||
)
|
||||
|
||||
type nutsEng struct {
|
||||
db *nutsdb.DB
|
||||
dir string
|
||||
}
|
||||
|
||||
func openNuts() maker {
|
||||
return maker{name: "Nutsdb", open: func(dir string) (Engine, error) {
|
||||
db, err := nutsdb.Open(nutsdb.DefaultOptions, nutsdb.WithDir(dir), nutsdb.WithSyncEnable(false))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, b := range []string{"d", "b"} {
|
||||
if err := db.Update(func(tx *nutsdb.Tx) error { return tx.NewBucket(nutsdb.DataStructureBTree, b) }); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &nutsEng{db: db, dir: dir}, nil
|
||||
}}
|
||||
}
|
||||
|
||||
func (e *nutsEng) Name() string { return "Nutsdb" }
|
||||
|
||||
func (e *nutsEng) Load(cards []Card) error {
|
||||
for start := 0; start < len(cards); start += 2000 {
|
||||
end := min(start+2000, len(cards))
|
||||
if err := e.db.Update(func(tx *nutsdb.Tx) error {
|
||||
for _, c := range cards[start:end] {
|
||||
if err := tx.Put("d", []byte(c.ID), c.Raw, nutsdb.Persistent); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Put("b", indexKey(c)[2:], []byte{1}, nutsdb.Persistent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *nutsEng) ByID(id string) ([]byte, error) {
|
||||
var out []byte
|
||||
err := e.db.View(func(tx *nutsdb.Tx) error {
|
||||
v, err := tx.Get("d", []byte(id))
|
||||
out = append([]byte(nil), v...)
|
||||
return err
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (e *nutsEng) ids(tx *nutsdb.Tx, board string) ([]string, error) {
|
||||
p := boardPrefix(board)[2:]
|
||||
keys, _, err := tx.PrefixScanEntries("b", p, "", 0, 1<<30, true, false)
|
||||
if err != nil {
|
||||
if errors.Is(err, nutsdb.ErrPrefixScan) || errors.Is(err, nutsdb.ErrNotFoundKey) {
|
||||
return nil, nil // a prefix with nothing under it is an error here, not an empty list
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
var ids []string
|
||||
for _, k := range keys {
|
||||
if !bytes.HasPrefix(k, p) {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, idFromIndexKey(k))
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (e *nutsEng) BoardCards(board string) ([][]byte, error) {
|
||||
var out [][]byte
|
||||
err := e.db.View(func(tx *nutsdb.Tx) error {
|
||||
ids, err := e.ids(tx, board)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range ids {
|
||||
v, err := tx.Get("d", []byte(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out = append(out, append([]byte(nil), v...))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (e *nutsEng) CountBoard(board string) (int, error) {
|
||||
n := 0
|
||||
err := e.db.View(func(tx *nutsdb.Tx) error {
|
||||
ids, err := e.ids(tx, board)
|
||||
n = len(ids)
|
||||
return err
|
||||
})
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (e *nutsEng) Update(id string, change func([]byte) ([]byte, error)) error {
|
||||
return e.db.Update(func(tx *nutsdb.Tx) error {
|
||||
v, err := tx.Get("d", []byte(id))
|
||||
if err != nil {
|
||||
return fmt.Errorf("get %s: %w", id, err)
|
||||
}
|
||||
next, err := change(append([]byte(nil), v...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Put("d", []byte(id), next, nutsdb.Persistent)
|
||||
})
|
||||
}
|
||||
|
||||
func (e *nutsEng) Insert(c Card) error {
|
||||
return e.db.Update(func(tx *nutsdb.Tx) error {
|
||||
if err := tx.Put("d", []byte(c.ID), c.Raw, nutsdb.Persistent); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Put("b", indexKey(c)[2:], []byte{1}, nutsdb.Persistent)
|
||||
})
|
||||
}
|
||||
|
||||
func (e *nutsEng) SizeBytes() int64 { return dirSize(e.dir) }
|
||||
func (e *nutsEng) Close() error { return e.db.Close() }
|
||||
@@ -0,0 +1,110 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/pebble"
|
||||
)
|
||||
|
||||
type pebbleEng struct {
|
||||
db *pebble.DB
|
||||
dir string
|
||||
}
|
||||
|
||||
func openPebble() maker {
|
||||
return maker{name: "Pebble (CockroachDB's LSM)", open: func(dir string) (Engine, error) {
|
||||
db, err := pebble.Open(dir, &pebble.Options{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pebbleEng{db: db, dir: dir}, nil
|
||||
}}
|
||||
}
|
||||
|
||||
func (e *pebbleEng) Name() string { return "Pebble" }
|
||||
|
||||
func (e *pebbleEng) Load(cards []Card) error {
|
||||
b := e.db.NewBatch()
|
||||
for i, c := range cards {
|
||||
if err := b.Set(docKey(c.ID), c.Raw, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.Set(indexKey(c), nil, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if i%5000 == 4999 {
|
||||
if err := b.Commit(pebble.NoSync); err != nil {
|
||||
return err
|
||||
}
|
||||
b = e.db.NewBatch()
|
||||
}
|
||||
}
|
||||
if err := b.Commit(pebble.NoSync); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.db.Flush()
|
||||
}
|
||||
|
||||
func (e *pebbleEng) ByID(id string) ([]byte, error) {
|
||||
v, closer, err := e.db.Get(docKey(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closer.Close()
|
||||
return append([]byte(nil), v...), nil
|
||||
}
|
||||
|
||||
func (e *pebbleEng) ids(board string) ([]string, error) {
|
||||
p := boardPrefix(board)
|
||||
it, err := e.db.NewIter(&pebble.IterOptions{LowerBound: p, UpperBound: prefixEnd(p)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer it.Close()
|
||||
var ids []string
|
||||
for ok := it.First(); ok; ok = it.Next() {
|
||||
ids = append(ids, idFromIndexKey(it.Key()))
|
||||
}
|
||||
return ids, it.Error()
|
||||
}
|
||||
|
||||
func (e *pebbleEng) BoardCards(board string) ([][]byte, error) {
|
||||
ids, err := e.ids(board)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([][]byte, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
d, err := e.ByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, d)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (e *pebbleEng) CountBoard(board string) (int, error) {
|
||||
ids, err := e.ids(board)
|
||||
return len(ids), err
|
||||
}
|
||||
|
||||
func (e *pebbleEng) Update(id string, change func([]byte) ([]byte, error)) error {
|
||||
d, err := e.ByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
next, err := change(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.db.Set(docKey(id), next, pebble.NoSync)
|
||||
}
|
||||
|
||||
func (e *pebbleEng) Insert(c Card) error {
|
||||
b := e.db.NewBatch()
|
||||
_ = b.Set(docKey(c.ID), c.Raw, nil)
|
||||
_ = b.Set(indexKey(c), nil, nil)
|
||||
return b.Commit(pebble.NoSync)
|
||||
}
|
||||
|
||||
func (e *pebbleEng) SizeBytes() int64 { return dirSize(e.dir) }
|
||||
func (e *pebbleEng) Close() error { return e.db.Close() }
|
||||
@@ -0,0 +1,238 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type maker struct {
|
||||
name string
|
||||
open func(dir string) (Engine, error)
|
||||
}
|
||||
|
||||
// Result is one engine's numbers. Times are milliseconds for one call.
|
||||
type Result struct {
|
||||
Name string `json:"name"`
|
||||
Error string `json:"error,omitempty"`
|
||||
LoadMs float64 `json:"loadMs"`
|
||||
SizeMB float64 `json:"sizeMB"`
|
||||
Queries map[string]float64 `json:"queries"`
|
||||
Slowest map[string]float64 `json:"slowest"`
|
||||
TotalMs float64 `json:"totalMs"`
|
||||
TotalSlow float64 `json:"totalSlowestMs"`
|
||||
}
|
||||
|
||||
var queryOrder = []string{"byId", "boardCards", "countBoard", "update", "move", "insert"}
|
||||
|
||||
// sample times f: it is repeated until about 15 ms have passed (at most 400 times)
|
||||
// and the mean of one call is returned, so a microsecond call is not lost in the
|
||||
// clock. It is taken three times and the median returned.
|
||||
func sample(f func(i int) error) (time.Duration, error) {
|
||||
var out []time.Duration
|
||||
for pass := 0; pass < 3; pass++ {
|
||||
start := time.Now()
|
||||
n := 0
|
||||
for n < 400 && (n == 0 || time.Since(start) < 15*time.Millisecond) {
|
||||
if err := f(n); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
out = append(out, time.Since(start)/time.Duration(n))
|
||||
}
|
||||
return median(out), nil
|
||||
}
|
||||
|
||||
func run(open func(string) (Engine, error), name, dir string, cards []Card, byBoard map[string][]Card, p params) (*Result, error) {
|
||||
e, err := open(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer e.Close()
|
||||
|
||||
res := &Result{Name: name, Queries: map[string]float64{}, Slowest: map[string]float64{}}
|
||||
|
||||
t0 := time.Now()
|
||||
if err = e.Load(cards); err != nil {
|
||||
return nil, fmt.Errorf("load: %w", err)
|
||||
}
|
||||
res.LoadMs = float64(time.Since(t0).Microseconds()) / 1000
|
||||
res.SizeMB = float64(e.SizeBytes()) / (1 << 20)
|
||||
|
||||
// Correctness first: a fast wrong answer is no answer.
|
||||
for _, b := range p.Boards {
|
||||
want := 0
|
||||
for _, c := range byBoard[b] {
|
||||
if !c.Archived {
|
||||
want++
|
||||
}
|
||||
}
|
||||
docs, err := e.BoardCards(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n, err := e.CountBoard(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(docs) != want || n != want {
|
||||
return nil, fmt.Errorf("board %s: %d cards and a count of %d, want %d", b, len(docs), n, want)
|
||||
}
|
||||
first := byBoard[b][0]
|
||||
raw, err := e.ByID(first.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var back Card
|
||||
if err := json.Unmarshal(raw, &back); err != nil || back.ID != first.ID || back.Board != first.Board {
|
||||
return nil, fmt.Errorf("byId %s returned %q", first.ID, string(raw))
|
||||
}
|
||||
}
|
||||
|
||||
// Reads: each is asked with every parameter board, and the cell is the average of
|
||||
// the per-parameter medians, like the speed test's. "Slowest" is the worst board.
|
||||
reads := map[string]func(b string, i int) error{
|
||||
"byId": func(b string, i int) error {
|
||||
cs := byBoard[b]
|
||||
_, err := e.ByID(cs[i%len(cs)].ID)
|
||||
return err
|
||||
},
|
||||
"boardCards": func(b string, i int) error {
|
||||
docs, err := e.BoardCards(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, d := range docs { // decoded, as FerretDB must
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(d, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
"countBoard": func(b string, i int) error { _, err := e.CountBoard(b); return err },
|
||||
// The same fetch without decoding: the part that differs between engines.
|
||||
"boardRaw": func(b string, i int) error { _, err := e.BoardCards(b); return err },
|
||||
}
|
||||
for _, q := range []string{"byId", "boardCards", "boardRaw", "countBoard"} {
|
||||
var sum, worst float64
|
||||
for _, b := range p.Boards {
|
||||
f := reads[q]
|
||||
d, err := sample(func(i int) error { return f(b, i) })
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", q, err)
|
||||
}
|
||||
ms := float64(d.Nanoseconds()) / 1e6
|
||||
sum += ms
|
||||
if ms > worst {
|
||||
worst = ms
|
||||
}
|
||||
}
|
||||
res.Queries[q] = sum / float64(len(p.Boards))
|
||||
res.Slowest[q] = worst
|
||||
}
|
||||
|
||||
// Writes: 200 different cards each, so nothing is a no-op; the mean of a call.
|
||||
ids := make([]string, 0, 200)
|
||||
for i := 0; i < len(cards) && len(ids) < 200; i += len(cards) / 200 {
|
||||
ids = append(ids, cards[i].ID)
|
||||
}
|
||||
writes := []struct {
|
||||
name string
|
||||
f func(i int) error
|
||||
}{
|
||||
{"update", func(i int) error {
|
||||
return e.Update(ids[i%len(ids)], func(raw []byte) ([]byte, error) { return modify(raw, "title", fmt.Sprintf("Edited %d", i)) })
|
||||
}},
|
||||
{"move", func(i int) error {
|
||||
return e.Update(ids[(i+1)%len(ids)], func(raw []byte) ([]byte, error) {
|
||||
m, err := modify(raw, "sort", 100000+i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return modify(m, "listId", "moved")
|
||||
})
|
||||
}},
|
||||
{"insert", func(i int) error {
|
||||
doc := fmt.Sprintf(`{"_id":"new%d","boardId":"%s","listId":"l","archived":false,"title":"New","sort":%d}`, i, p.Boards[0], i)
|
||||
return e.Insert(Card{ID: fmt.Sprintf("new%d", i), Board: p.Boards[0], Raw: []byte(doc)})
|
||||
}},
|
||||
}
|
||||
for _, w := range writes {
|
||||
start := time.Now()
|
||||
for i := 0; i < len(ids); i++ {
|
||||
if err := w.f(i); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", w.name, err)
|
||||
}
|
||||
}
|
||||
ms := float64(time.Since(start).Nanoseconds()) / 1e6 / float64(len(ids))
|
||||
res.Queries[w.name] = ms
|
||||
res.Slowest[w.name] = ms
|
||||
}
|
||||
|
||||
// The writes must be there.
|
||||
raw, err := e.ByID(ids[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !strings.Contains(string(raw), `"moved"`) {
|
||||
return nil, fmt.Errorf("the move was not stored: %s", raw)
|
||||
}
|
||||
|
||||
for _, q := range queryOrder {
|
||||
res.TotalMs += res.Queries[q]
|
||||
res.TotalSlow += res.Slowest[q]
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func report(results []Result, p params) {
|
||||
ok := results[:0:0]
|
||||
for _, r := range results {
|
||||
if r.Error == "" {
|
||||
ok = append(ok, r)
|
||||
}
|
||||
}
|
||||
// Fastest first.
|
||||
for i := range ok {
|
||||
for j := i + 1; j < len(ok); j++ {
|
||||
if ok[j].TotalMs < ok[i].TotalMs {
|
||||
ok[i], ok[j] = ok[j], ok[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Printf("\nScale %v: %d cards, %d parameter boards. Milliseconds for one call; total = sum of the six.\n\n", p.Scale, p.Cards, len(p.Boards))
|
||||
fmt.Println("| # | Engine | Total | By id | Board cards | (raw, not decoded) | Count | Edit | Move | Add | Load (s) | Size (MB) |")
|
||||
fmt.Println("| ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |")
|
||||
for i, r := range ok {
|
||||
size := fmt.Sprintf("%.0f", r.SizeMB)
|
||||
if r.SizeMB < 0 {
|
||||
size = "n/a"
|
||||
}
|
||||
fmt.Printf("| %d | %s | **%s** | %s | %s | %s | %s | %s | %s | %s | %.1f | %s |\n", i+1, r.Name, ms(r.TotalMs),
|
||||
ms(r.Queries["byId"]), ms(r.Queries["boardCards"]), ms(r.Queries["boardRaw"]), ms(r.Queries["countBoard"]),
|
||||
ms(r.Queries["update"]), ms(r.Queries["move"]), ms(r.Queries["insert"]), r.LoadMs/1000, size)
|
||||
}
|
||||
for _, r := range results {
|
||||
if r.Error != "" {
|
||||
fmt.Printf("\n%s: %s\n", r.Name, r.Error)
|
||||
}
|
||||
}
|
||||
_ = os.Stdout.Sync()
|
||||
}
|
||||
|
||||
func ms(v float64) string {
|
||||
switch {
|
||||
case v < 0.01:
|
||||
return fmt.Sprintf("%.4f", v)
|
||||
case v < 10:
|
||||
return fmt.Sprintf("%.3f", v)
|
||||
case v < 100:
|
||||
return fmt.Sprintf("%.1f", v)
|
||||
default:
|
||||
return fmt.Sprintf("%.0f", v)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// SQLite as FerretDB uses it: modernc.org/sqlite, WAL, synchronous NORMAL, a 64 MiB
|
||||
// cache and 256 MiB of mmap (FerretDB's defaults, metadata/pool/uri.go). The
|
||||
// document is JSON text and the indexes are on json_extract expressions, which is
|
||||
// how FerretDB indexes a field. "columns" keeps the same document but indexes real
|
||||
// columns, which FerretDB does not do; it shows what SQLite itself could do.
|
||||
type sqliteEng struct {
|
||||
name string
|
||||
db *sql.DB
|
||||
dir string
|
||||
columns bool
|
||||
}
|
||||
|
||||
func openSQLite(name, sync string, columns bool) maker {
|
||||
return maker{name: name, open: func(dir string) (Engine, error) {
|
||||
path := filepath.Join(dir, "cards.sqlite")
|
||||
dsn := fmt.Sprintf("file:%s?_pragma=journal_mode(wal)&_pragma=synchronous(%s)&_pragma=cache_size(-65536)&_pragma=mmap_size(268435456)&_pragma=temp_store(memory)&_pragma=busy_timeout(30000)&_txlock=immediate", path, sync)
|
||||
db, err := sql.Open("sqlite", dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
return &sqliteEng{name: name, db: db, dir: dir, columns: columns}, nil
|
||||
}}
|
||||
}
|
||||
|
||||
func (e *sqliteEng) Name() string { return e.name }
|
||||
|
||||
func (e *sqliteEng) Load(cards []Card) error {
|
||||
create := `CREATE TABLE cards (doc TEXT NOT NULL)`
|
||||
insert := `INSERT INTO cards (doc) VALUES (?)`
|
||||
if e.columns {
|
||||
create = `CREATE TABLE cards (id TEXT PRIMARY KEY, board TEXT, archived INTEGER, doc TEXT NOT NULL)`
|
||||
insert = `INSERT INTO cards (id, board, archived, doc) VALUES (?, ?, ?, ?)`
|
||||
}
|
||||
if _, err := e.db.Exec(create); err != nil {
|
||||
return err
|
||||
}
|
||||
tx, err := e.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st, err := tx.Prepare(insert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range cards {
|
||||
if e.columns {
|
||||
_, err = st.Exec(c.ID, c.Board, boolInt(c.Archived), string(c.Raw))
|
||||
} else {
|
||||
_, err = st.Exec(string(c.Raw))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
idx := []string{
|
||||
`CREATE UNIQUE INDEX cards_id ON cards (json_extract(doc, '$._id'))`,
|
||||
`CREATE INDEX cards_board ON cards (json_extract(doc, '$.boardId'), json_extract(doc, '$.archived'))`,
|
||||
}
|
||||
if e.columns {
|
||||
idx = []string{`CREATE INDEX cards_board ON cards (board, archived)`}
|
||||
}
|
||||
for _, q := range idx {
|
||||
if _, err = e.db.Exec(q); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = e.db.Exec(`ANALYZE`)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *sqliteEng) where() (id, board string) {
|
||||
if e.columns {
|
||||
return `id = ?`, `board = ? AND archived = 0`
|
||||
}
|
||||
return `json_extract(doc, '$._id') = ?`, `json_extract(doc, '$.boardId') = ? AND json_extract(doc, '$.archived') = 0`
|
||||
}
|
||||
|
||||
func (e *sqliteEng) ByID(id string) ([]byte, error) {
|
||||
w, _ := e.where()
|
||||
var doc string
|
||||
err := e.db.QueryRow(`SELECT doc FROM cards WHERE `+w, id).Scan(&doc)
|
||||
return []byte(doc), err
|
||||
}
|
||||
|
||||
func (e *sqliteEng) BoardCards(board string) ([][]byte, error) {
|
||||
_, w := e.where()
|
||||
rows, err := e.db.Query(`SELECT doc FROM cards WHERE `+w, board)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out [][]byte
|
||||
for rows.Next() {
|
||||
var doc string
|
||||
if err := rows.Scan(&doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, []byte(doc))
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (e *sqliteEng) CountBoard(board string) (int, error) {
|
||||
_, w := e.where()
|
||||
var n int
|
||||
err := e.db.QueryRow(`SELECT COUNT(*) FROM cards WHERE `+w, board).Scan(&n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (e *sqliteEng) Update(id string, change func([]byte) ([]byte, error)) error {
|
||||
w, _ := e.where()
|
||||
tx, err := e.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
var doc string
|
||||
if err = tx.QueryRow(`SELECT doc FROM cards WHERE `+w, id).Scan(&doc); err != nil {
|
||||
return err
|
||||
}
|
||||
next, err := change([]byte(doc))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = tx.Exec(`UPDATE cards SET doc = ? WHERE `+w, string(next), id); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (e *sqliteEng) Insert(c Card) error {
|
||||
if e.columns {
|
||||
_, err := e.db.Exec(`INSERT INTO cards (id, board, archived, doc) VALUES (?, ?, ?, ?)`, c.ID, c.Board, boolInt(c.Archived), string(c.Raw))
|
||||
return err
|
||||
}
|
||||
_, err := e.db.Exec(`INSERT INTO cards (doc) VALUES (?)`, string(c.Raw))
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *sqliteEng) SizeBytes() int64 {
|
||||
_, _ = e.db.Exec(`PRAGMA wal_checkpoint(TRUNCATE)`)
|
||||
return dirSize(e.dir)
|
||||
}
|
||||
|
||||
func (e *sqliteEng) Close() error { return e.db.Close() }
|
||||
|
||||
func boolInt(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// dirSize is what the files really occupy: the blocks, not the apparent size, which
|
||||
// counts the sparse files Badger preallocates.
|
||||
func dirSize(dir string) int64 {
|
||||
var n int64
|
||||
_ = filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
|
||||
if err == nil && !info.IsDir() {
|
||||
if st, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
n += st.Blocks * 512
|
||||
} else {
|
||||
n += info.Size()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return n
|
||||
}
|
||||
@@ -99,11 +99,42 @@ function compare(runs, reference) {
|
||||
function machine() {
|
||||
const cpus = os.cpus();
|
||||
return `${cpus[0] ? cpus[0].model.trim() : os.arch()}, ${cpus.length} threads, ` +
|
||||
`${Math.round(os.totalmem() / 2 ** 30)} GiB RAM, Linux ${os.release()}`;
|
||||
`${Math.round(os.totalmem() / 2 ** 30)} GiB RAM, ${os.type()} ${os.release()}`;
|
||||
}
|
||||
|
||||
// What the scale means, in words: 2 is twice a production WeKan, 0.05 a twentieth.
|
||||
function sizeWords(scale) {
|
||||
if (scale === 1) return 'as big as a production WeKan';
|
||||
if (scale === 2) return 'twice a production WeKan';
|
||||
if (scale > 1) return `${scale} times a production WeKan`;
|
||||
const n = Math.round(1 / scale);
|
||||
return Math.abs(1 / scale - n) < 0.01 ? `1/${n} of a production WeKan` : `${Math.round(scale * 100)}% of a production WeKan`;
|
||||
}
|
||||
|
||||
// How many times slower than the fastest, as a person reads it: "1.0x", "4.9x",
|
||||
// "27x", "1,300x".
|
||||
function times(v, fastest) {
|
||||
if (!Number.isFinite(v) || !fastest) return '-';
|
||||
const r = v / fastest;
|
||||
if (r < 10) return `${r.toFixed(1)}x`;
|
||||
return `${Math.round(r).toLocaleString('en-US')}x`;
|
||||
}
|
||||
|
||||
function render(runs, sameness, reference, dir) {
|
||||
const sorted = [...runs].sort((a, b) => total(a) - total(b) || a.label.localeCompare(b.label));
|
||||
// Fastest first, by the sum of a row. Every table below is ordered by ITS OWN
|
||||
// numbers, so each reads from fastest to slowest.
|
||||
const byTotal = (totalOf) => [...runs].sort((a, b) => totalOf(a) - totalOf(b) || a.label.localeCompare(b.label));
|
||||
const slowestTotal = (run) => QUERIES.reduce((sum, q) => {
|
||||
const r = run.queries[q.id];
|
||||
return r && !r.error && r.slowest !== undefined ? sum + r.slowest : Infinity;
|
||||
}, 0);
|
||||
const loadTotal = (run) => {
|
||||
const l = run.load || {};
|
||||
const parts = ['users', 'boards', 'lists', 'swimlanes', 'cards'].map(c => l[c] && l[c].ms);
|
||||
return parts.some(v => v === undefined) || run.indexTotalMs === undefined
|
||||
? Infinity : parts.reduce((a, b) => a + b, 0) + run.indexTotalMs;
|
||||
};
|
||||
const sorted = byTotal(total);
|
||||
const first = runs.map(r => r.started).sort()[0] || '';
|
||||
const sizes = (runs[0] && runs[0].sizes) || {};
|
||||
const lines = [];
|
||||
@@ -111,16 +142,17 @@ function render(runs, sameness, reference, dir) {
|
||||
lines.push(`Run ${first.slice(0, 10)} on ${machine()}, by`);
|
||||
lines.push('[speedtest.sh](speedtest.sh). How it works, and what was improved because of it:');
|
||||
lines.push('[the README](.).', '');
|
||||
lines.push(`The data is twice a production WeKan: ${['users', 'boards', 'lists', 'swimlanes', 'cards']
|
||||
lines.push(`The data is ${sizeWords(runs[0] && runs[0].scale)}: ${['users', 'boards', 'lists', 'swimlanes', 'cards']
|
||||
.map(c => `${(sizes[c] || 0).toLocaleString('en-US')} ${c}`).join(', ')}` +
|
||||
` (production: ${PRODUCTION.cards.toLocaleString('en-US')} cards). Every database got identical`);
|
||||
lines.push('documents and WeKan\'s own indexes, and was asked identical questions.', '');
|
||||
|
||||
const table = (field, totalOf) => {
|
||||
const order = byTotal(totalOf);
|
||||
const head = ['Database', 'Total', ...QUERIES.map(q => q.title)];
|
||||
lines.push(`| ${head.join(' | ')} |`);
|
||||
lines.push(`| ${head.map((_, i) => (i === 0 ? '---' : '---:')).join(' | ')} |`);
|
||||
for (const run of sorted) {
|
||||
for (const run of order) {
|
||||
const t = totalOf(run);
|
||||
const cells = QUERIES.map((q) => {
|
||||
const r = run.queries[q.id];
|
||||
@@ -132,6 +164,22 @@ function render(runs, sameness, reference, dir) {
|
||||
}
|
||||
lines.push('');
|
||||
};
|
||||
lines.push('## At a glance', '');
|
||||
lines.push('One number per database: the time of one call of each of the common WeKan queries below,');
|
||||
lines.push('added together. **Times slower** is that sum divided by the fastest database\'s. Ordered');
|
||||
lines.push('from fastest to slowest. The second pair of columns is the same for the slowest parameter -');
|
||||
lines.push('the biggest board or the busiest user, who waits longest.', '');
|
||||
lines.push('| # | Database | Total (ms) | Times slower | Slowest user (ms) | Times slower |');
|
||||
lines.push('| ---: | --- | ---: | ---: | ---: | ---: |');
|
||||
const fastestAvg = Math.min(...runs.map(total));
|
||||
const fastestSlow = Math.min(...runs.map(slowestTotal));
|
||||
sorted.forEach((run, i) => {
|
||||
const t = total(run);
|
||||
const w = slowestTotal(run);
|
||||
lines.push(`| ${i + 1} | ${mdCell(run.engine)} | ${Number.isFinite(t) ? ms(t) : 'incomplete'} | ${times(t, fastestAvg)} | ` +
|
||||
`${Number.isFinite(w) ? ms(w) : 'incomplete'} | ${times(w, fastestSlow)} |`);
|
||||
});
|
||||
lines.push('');
|
||||
lines.push('## Speed', '');
|
||||
lines.push('Milliseconds for one call - smaller is faster. Each query is asked with 15 parameters (the');
|
||||
lines.push('3 biggest boards or busiest users and 12 ordinary ones, see below); a cell is the average of');
|
||||
@@ -140,17 +188,16 @@ function render(runs, sameness, reference, dir) {
|
||||
table('average', total);
|
||||
lines.push('### The slowest parameter', '');
|
||||
lines.push('The same, for the slowest of the 15 parameters - nearly always the biggest board or the');
|
||||
lines.push('busiest user. This is how long the person who waits longest waits. Same row order.', '');
|
||||
table('slowest', (run) => QUERIES.reduce((sum, q) => {
|
||||
const r = run.queries[q.id];
|
||||
return r && !r.error && r.slowest !== undefined ? sum + r.slowest : Infinity;
|
||||
}, 0));
|
||||
lines.push('busiest user. This is how long the person who waits longest waits. Ordered by its own');
|
||||
lines.push('total, so the order can differ from the table above.', '');
|
||||
table('slowest', slowestTotal);
|
||||
|
||||
lines.push('## Loading the data', '');
|
||||
lines.push('Inserting the documents in batches of 1,000, then creating every index WeKan creates.', '');
|
||||
lines.push('Inserting the documents in batches of 1,000, then creating every index WeKan creates.');
|
||||
lines.push('Ordered by the whole time, fastest first.', '');
|
||||
lines.push('| Database | users | boards | lists | swimlanes | cards | Indexes | Index errors |');
|
||||
lines.push('| --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |');
|
||||
for (const run of sorted) {
|
||||
for (const run of byTotal(loadTotal)) {
|
||||
const l = run.load || {};
|
||||
const ixErrors = Object.entries(run.indexes || {}).filter(([, v]) => v.error).map(([k]) => mdCell(k));
|
||||
lines.push(`| ${mdCell(run.engine)} | ${['users', 'boards', 'lists', 'swimlanes', 'cards'].map(c => seconds(l[c] && l[c].ms)).join(' | ')} | ` +
|
||||
@@ -182,7 +229,7 @@ function render(runs, sameness, reference, dir) {
|
||||
lines.push('');
|
||||
const calls = runs.length ? Object.values(runs[0].queries).map(r => r.calls).filter(Boolean) : [];
|
||||
lines.push(`Each query is first run once per parameter to warm up (those answers are the ones compared),`);
|
||||
lines.push(`then repeated for up to 5 passes or 10 seconds; ${calls.length ? `${Math.min(...calls)}-${Math.max(...calls)}` : 'the'} calls per query were timed.`);
|
||||
lines.push(`then repeated for up to 5 passes or the time budget (WEKAN_SPEEDTEST_BUDGET_MS); ${calls.length ? `${Math.min(...calls)}-${Math.max(...calls)}` : 'the'} calls per query were timed.`);
|
||||
lines.push('A query too slow to be repeated in that time keeps its warm-up time.');
|
||||
lines.push(`Raw results: \`${path.relative(path.resolve(__dirname, '../../../../..'), dir) || dir}/speedtest-<database>.json\`.`, '');
|
||||
return `${lines.join('\n')}\n`;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"uuid":"fb14521d-8728-4fdb-befd-132ef195c35c","telemetry":false}
|
||||
@@ -0,0 +1,62 @@
|
||||
# Version history in the database: Dolt and TerminusDB, and WeKan's undo and redo
|
||||
|
||||
WeKan's undo and redo (Ctrl+Z, Ctrl+Y, History → Restore, see
|
||||
[History](../../../../Features/General/History/History.md) and
|
||||
[Undo](../../../../Features/General/Undo/Undo.md)) is application code: one
|
||||
`changeHistory` row per change, with who, when and which field, and a restore that
|
||||
reapplies the old values oldest to newest. Some databases version their data
|
||||
themselves. This asks what that would give WeKan, by trying WeKan-shaped changes
|
||||
(edit a card's title, move it, archive it, delete another one) on two of them.
|
||||
|
||||
```sh
|
||||
# Dolt, under FerretDB's MySQL handler - WeKan's writes arrive over the MongoDB protocol
|
||||
docker run -d --name dolttry -p 3307:3306 -e DOLT_ROOT_PASSWORD=rootpw -e DOLT_ROOT_HOST=% \
|
||||
-e DOLT_DATABASE=ferretdb dolthub/dolt-sql-server:latest
|
||||
mysql ... -e 'SET GLOBAL dolt_transaction_commit = 1' # a Dolt commit per transaction
|
||||
ferretdb --handler=mysql --mysql-url='mysql://root:rootpw@127.0.0.1:3307/ferretdb' --listen-addr=127.0.0.1:27022
|
||||
node docs/Databases/FerretDB/1/VersionHistory/dolt-history.cjs
|
||||
|
||||
# TerminusDB - REST only
|
||||
docker run -d --name tdbtry -p 6363:6363 terminusdb/terminusdb-server:latest
|
||||
node docs/Databases/FerretDB/1/VersionHistory/terminusdb-history.cjs
|
||||
```
|
||||
|
||||
Run on 2026-11-03: Dolt (sql-server image, MySQL 8.0.31 protocol) with FerretDB
|
||||
v1.105.0, and TerminusDB 12.0.9. Both are open source (Apache-2.0).
|
||||
|
||||
## What was found
|
||||
|
||||
| WeKan needs | Dolt under FerretDB | TerminusDB |
|
||||
| --- | --- | --- |
|
||||
| A record of every change | **Yes, with no change to WeKan.** With `dolt_transaction_commit` on, every FerretDB write is one Dolt commit (four writes, four commits). | Yes, one commit per write. |
|
||||
| Who made it | **No.** The committer is the SQL user (`root`) for every WeKan user, and the message is always `Transaction commit`. FerretDB would have to set them. | **Yes.** Each commit has the author and message the caller gives: `alice \| edit title`. |
|
||||
| What changed | Whole documents: an edit is a removed row and an added row in `dolt_diff_<table>`, because FerretDB's tables have no primary key and a card is one JSON cell. Fields are found by comparing the two. | **Field by field**: `{"title": {"@before": "Write docs v2", "@after": "Write docs", "@op": "SwapValue"}}`. |
|
||||
| Undo the last change | **Yes.** `CALL DOLT_REVERT('HEAD')` brought a deleted card back, read through FerretDB over the MongoDB protocol; reverting that revert is the redo. Both are commits of their own, so history is never rewritten. | **Yes**: diff the last two commits and apply it backwards (`/api/diff`, `/api/patch`). |
|
||||
| Restore one older change, keep the later ones | **Another document: yes. The same document: no.** Reverting an old title edit of card c1 after c1 was moved and archived failed with `the table(s) ... are in conflict` (the whole document is one cell). The failed revert left nothing behind, and FerretDB writes kept working. | **Yes, by field.** The title was restored while the move and the archive stayed: `title: "Write docs", listId: "l2", archived: true`. A patch whose before-value no longer matches is the conflict. |
|
||||
| The board as it was | **Yes**, in SQL: `SELECT ... AS OF '<commit>'`. FerretDB does not offer it over the MongoDB protocol. | **Yes**: `GET /api/document/.../local/commit/<id>`. |
|
||||
| Undo "my last change" or "this board's last change" | **No.** `HEAD` is the last commit of anyone on anything. A scope means choosing commits by their diff. | The log is per database; a scope is also chosen by the diff. |
|
||||
| Cost per write | **+0.4 ms**: `updateOne` took 2.34 ms with commits off and 2.77 ms with them on (200 calls each), +18%. 223 commits for 200 updates. | About 25 ms per `PUT` of a card, an HTTP call and a commit each. |
|
||||
| Can WeKan run on it | **Yes**: it is a MySQL-protocol database that FerretDB's MySQL handler already runs (it is in the [speed test](../SpeedTest/)). | **No.** It has neither the MongoDB protocol nor SQL; WeKan would need a new data layer. |
|
||||
|
||||
## What it means for WeKan
|
||||
|
||||
- **WeKan's own History is still the right undo.** It knows the user, the field,
|
||||
and the card, list or swimlane a change belongs to, and it restores one field
|
||||
while later changes stay. Dolt cannot do that for a document edited again
|
||||
afterwards, and it does not know who made a change; TerminusDB can, but WeKan
|
||||
cannot run on it.
|
||||
- **Dolt would add something WeKan does not have: the whole database at any
|
||||
commit.** Restoring a board, or all of them, as they were an hour ago is
|
||||
`AS OF` or `DOLT_RESET`, with no code in WeKan. It is a backup and audit layer
|
||||
under the undo, not a replacement for it. Its costs are real: a commit per
|
||||
write of everything WeKan writes (sessions, activities and search sessions
|
||||
included, which are not the user's changes), so the history is mostly noise.
|
||||
And Dolt is slow for WeKan's reads: in the [speed test](../SpeedTest/Results.md)
|
||||
every query over cards takes about 2.8 s on 15,377 cards (MySQL: 1-8 ms), 20,470
|
||||
ms in total against MySQL's 103, because it scans the table instead of using the
|
||||
indexes. A production-size WeKan on it would not be usable as it is.
|
||||
- **What FerretDB would need for Dolt to answer "who"**: set the commit author and
|
||||
message from the MongoDB command's `comment`, or the connection's user. It does
|
||||
neither today.
|
||||
- **Not tried**: Dolt branches and merges for a draft board, and TerminusDB's
|
||||
history of a single document, which would answer "this card's history" directly.
|
||||
@@ -0,0 +1,134 @@
|
||||
'use strict';
|
||||
|
||||
// What a database with version history gives WeKan's undo and redo: Dolt (git for
|
||||
// data, MySQL protocol) under FerretDB's MySQL handler. WeKan writes through the
|
||||
// MongoDB protocol as always; every FerretDB write is a Dolt transaction, and with
|
||||
// dolt_transaction_commit on every transaction is a Dolt commit.
|
||||
//
|
||||
// node docs/Databases/FerretDB/1/VersionHistory/dolt-history.cjs \
|
||||
// --uri 'mongodb://127.0.0.1:27022/?directConnection=true' --sql-port 3307 --sql-password rootpw
|
||||
//
|
||||
// It prints what it finds; README.md next to it explains the findings. The SQL side
|
||||
// is asked through the mysql client of the mysql:9 image, so nothing is installed.
|
||||
|
||||
const { execFileSync } = require('child_process');
|
||||
const { MongoClient } = require('mongodb');
|
||||
|
||||
function arg(name, fallback) {
|
||||
const i = process.argv.indexOf(`--${name}`);
|
||||
return i !== -1 && process.argv[i + 1] ? process.argv[i + 1] : fallback;
|
||||
}
|
||||
const URI = arg('uri', 'mongodb://127.0.0.1:27022/?directConnection=true');
|
||||
const PORT = arg('sql-port', '3307');
|
||||
const PASSWORD = arg('sql-password', 'rootpw');
|
||||
const DB = 'wekanhistory';
|
||||
const TIMING_ONLY = process.argv.includes('--timing');
|
||||
|
||||
function sql(query, { db = DB, allowError = false } = {}) {
|
||||
try {
|
||||
return execFileSync('docker', ['run', '--rm', '--network', 'host', 'mysql:9', 'mysql',
|
||||
'-h', '127.0.0.1', '-P', PORT, '-u', 'root', `-p${PASSWORD}`, '-B', '-N', '-D', db, '-e', query],
|
||||
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
||||
} catch (e) {
|
||||
if (allowError) return `ERROR: ${String(e.stderr || e.message).replace(/mysql: \[Warning\][^\n]*\n/, '').trim()}`;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
const commits = () => Number(sql('SELECT COUNT(*) FROM dolt_log'));
|
||||
const head = () => sql("SELECT commit_hash FROM dolt_log LIMIT 1");
|
||||
const say = (line = '') => console.log(line);
|
||||
const json = (v) => { try { return JSON.parse(v); } catch { return v; } };
|
||||
|
||||
async function main() {
|
||||
const client = await new MongoClient(URI).connect();
|
||||
const db = client.db(DB);
|
||||
await db.dropDatabase().catch(() => {});
|
||||
|
||||
// Setup the way WeKan has it: a board, two lists and some cards.
|
||||
const boards = db.collection('boards');
|
||||
const lists = db.collection('lists');
|
||||
const cards = db.collection('cards');
|
||||
await boards.insertOne({ _id: 'b1', title: 'Board', archived: false });
|
||||
await lists.insertMany([{ _id: 'l1', boardId: 'b1', title: 'To do' }, { _id: 'l2', boardId: 'b1', title: 'Done' }]);
|
||||
await cards.insertMany([
|
||||
{ _id: 'c1', boardId: 'b1', listId: 'l1', title: 'Write docs', sort: 1, archived: false },
|
||||
{ _id: 'c2', boardId: 'b1', listId: 'l1', title: 'Fix bug', sort: 2, archived: false },
|
||||
]);
|
||||
|
||||
const table = sql(`SELECT table_name FROM information_schema.tables WHERE table_schema='${DB}' AND table_name LIKE 'cards%' LIMIT 1`);
|
||||
|
||||
if (TIMING_ONLY) {
|
||||
const N = 200;
|
||||
const t0 = process.hrtime.bigint();
|
||||
for (let i = 0; i < N; i += 1) await cards.updateOne({ _id: 'c1' }, { $set: { title: `Edit ${i}` } });
|
||||
const ms = Number(process.hrtime.bigint() - t0) / 1e6 / N;
|
||||
say(`updateOne: ${ms.toFixed(2)} ms per call, ${commits()} commits, dolt_transaction_commit=${sql('SELECT @@dolt_transaction_commit', { db: 'information_schema' })}`);
|
||||
await client.close();
|
||||
return;
|
||||
}
|
||||
|
||||
say('# 1. A Dolt commit per FerretDB write');
|
||||
const ops = [
|
||||
['edit title', () => cards.updateOne({ _id: 'c1' }, { $set: { title: 'Write docs v2' } })],
|
||||
['move to Done', () => cards.updateOne({ _id: 'c1' }, { $set: { listId: 'l2', sort: 5 } })],
|
||||
['archive (soft delete)', () => cards.updateOne({ _id: 'c1' }, { $set: { archived: true } })],
|
||||
['hard delete c2', () => cards.deleteOne({ _id: 'c2' })],
|
||||
];
|
||||
const hashes = {};
|
||||
for (const [name, run] of ops) {
|
||||
const before = commits();
|
||||
await run();
|
||||
hashes[name] = head();
|
||||
say(`${name.padEnd(24)} commits +${commits() - before} ${hashes[name].slice(0, 10)}`);
|
||||
}
|
||||
say(sql("SELECT CONCAT(committer, ' | ', date, ' | ', message) FROM dolt_log LIMIT 4"));
|
||||
|
||||
say('\n# 2. Who changed what: dolt_diff of the cards table, as WeKan\'s History table would show it');
|
||||
const diff = sql(`SELECT diff_type, to_commit, COALESCE(JSON_UNQUOTE(JSON_EXTRACT(from__ferretdb_sjson, '$.title')), ''), COALESCE(JSON_UNQUOTE(JSON_EXTRACT(to__ferretdb_sjson, '$.title')), ''), ` +
|
||||
`COALESCE(JSON_UNQUOTE(JSON_EXTRACT(from__ferretdb_sjson, '$.listId')), ''), COALESCE(JSON_UNQUOTE(JSON_EXTRACT(to__ferretdb_sjson, '$.listId')), '') ` +
|
||||
`FROM dolt_diff_${table} ORDER BY to_commit_date, to_commit`, { allowError: true });
|
||||
say('diff_type | commit | title from -> to | list from -> to');
|
||||
say(diff.split('\n').map((l) => { const c = l.split('\t'); return `${c[0]} | ${String(c[1]).slice(0, 8)} | ${c[2]} -> ${c[3]} | ${c[4]} -> ${c[5]}`; }).join('\n'));
|
||||
|
||||
say('\n# 3. Undo the last change (WeKan: Ctrl+Z) = DOLT_REVERT of HEAD, then redo = revert the revert');
|
||||
const exists = async (id) => !!(await cards.findOne({ _id: id }));
|
||||
say(`c2 exists before undo: ${await exists('c2')}`);
|
||||
say(sql("CALL DOLT_REVERT('HEAD')", { allowError: true }));
|
||||
say(`c2 exists after undo: ${await exists('c2')} (read through FerretDB, the MongoDB protocol)`);
|
||||
say(sql("CALL DOLT_REVERT('HEAD')", { allowError: true }));
|
||||
say(`c2 exists after redo: ${await exists('c2')}`);
|
||||
say(`commits now: ${commits()} - an undo and a redo are commits of their own, the history is never rewritten`);
|
||||
|
||||
say('\n# 4. Restore ONE older change while later ones stay (WeKan: Restore of a selected History row)');
|
||||
await cards.insertOne({ _id: 'c3', boardId: 'b1', listId: 'l1', title: 'Other card', sort: 3, archived: false });
|
||||
await cards.updateOne({ _id: 'c3' }, { $set: { title: 'Other card v2' } });
|
||||
const c3edit = head();
|
||||
await cards.updateOne({ _id: 'c2x', boardId: 'b1' }, { $set: { title: 'x' } }, { upsert: true });
|
||||
say('revert an old edit of c3 after an unrelated later commit (different row):');
|
||||
say(sql(`CALL DOLT_REVERT('${c3edit}')`, { allowError: true }));
|
||||
say(`c3 title: ${(await cards.findOne({ _id: 'c3' })).title}`);
|
||||
say('revert the OLD title edit of c1 while c1 was changed again afterwards (same row, other field):');
|
||||
say(sql(`CALL DOLT_REVERT('${hashes['edit title']}')`, { allowError: true }));
|
||||
const c1 = await cards.findOne({ _id: 'c1' });
|
||||
say(`c1 now: title=${c1.title} listId=${c1.listId} archived=${c1.archived}`);
|
||||
say(`left behind by the failed revert: ${sql('SELECT COUNT(*) FROM dolt_conflicts', { allowError: true })} conflict table(s), status: ${sql("SELECT GROUP_CONCAT(CONCAT(table_name, ':', status)) FROM dolt_status", { allowError: true })}`);
|
||||
const stuck = await cards.updateOne({ _id: 'c1' }, { $set: { sort: 99 } }).then(() => 'a write still works', (e) => `a write FAILS: ${String(e.message).slice(0, 120)}`);
|
||||
say(`FerretDB write while the conflict is open: ${stuck}`);
|
||||
say(`clean up with DOLT_RESET('--hard'): ${sql("CALL DOLT_RESET('--hard')", { allowError: true })}`);
|
||||
const after = await cards.updateOne({ _id: 'c1' }, { $set: { sort: 100 } }).then(() => 'a write works again', (e) => `a write FAILS: ${String(e.message).slice(0, 120)}`);
|
||||
say(`FerretDB write after the reset: ${after}`);
|
||||
|
||||
say('\n# 5. Time travel: the board as it was (AS OF), read with SQL');
|
||||
const asOf = sql(`SELECT COUNT(*) FROM ${table} AS OF '${hashes['move to Done']}'`, { allowError: true });
|
||||
const nowCount = sql(`SELECT COUNT(*) FROM ${table}`);
|
||||
say(`cards at the "move to Done" commit: ${asOf}; now: ${nowCount}`);
|
||||
const oldTitle = sql(`SELECT JSON_UNQUOTE(JSON_EXTRACT(_ferretdb_sjson, '$.title')) FROM ${table} AS OF '${hashes['edit title']}' WHERE JSON_UNQUOTE(JSON_EXTRACT(_ferretdb_sjson, '$._id')) = 'c1'`, { allowError: true });
|
||||
say(`c1 title as of the "edit title" commit: ${oldTitle}`);
|
||||
|
||||
say('\n# 6. What it costs');
|
||||
say(`commits for ${ops.length + 6} FerretDB writes: ${commits()}`);
|
||||
say(`size of the data database: ${sql(`SELECT COUNT(*) FROM dolt_log`)} commits; chunks are shared, only changed rows are stored again`);
|
||||
await client.close();
|
||||
}
|
||||
|
||||
main().catch((e) => { console.error(e); process.exit(1); });
|
||||
@@ -0,0 +1,95 @@
|
||||
'use strict';
|
||||
|
||||
// The same questions as dolt-history.cjs, asked of TerminusDB (a document and
|
||||
// graph database with version control). TerminusDB speaks neither the MongoDB
|
||||
// protocol nor SQL, so FerretDB cannot sit on it and WeKan cannot use it as it
|
||||
// is; this only shows what its version control would give an undo and redo.
|
||||
//
|
||||
// docker run -d --name tdbtry -p 6363:6363 terminusdb/terminusdb-server:latest
|
||||
// node docs/Databases/FerretDB/1/VersionHistory/terminusdb-history.cjs
|
||||
|
||||
const BASE = process.argv.includes('--url') ? process.argv[process.argv.indexOf('--url') + 1] : 'http://127.0.0.1:6363';
|
||||
const AUTH = `Basic ${Buffer.from('admin:root').toString('base64')}`;
|
||||
const DB = 'wekanhistory';
|
||||
const say = (line = '') => console.log(line);
|
||||
|
||||
async function api(method, path, { query = {}, body, raw = false } = {}) {
|
||||
const url = new URL(`${BASE}${path}`);
|
||||
for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
|
||||
const res = await fetch(url, {
|
||||
method, headers: { Authorization: AUTH, 'Content-Type': 'application/json' },
|
||||
body: body === undefined ? undefined : JSON.stringify(body),
|
||||
});
|
||||
const text = await res.text();
|
||||
let data = text;
|
||||
try { data = JSON.parse(text); } catch { try { data = text.trim().split('\n').map(JSON.parse); } catch { /* text */ } }
|
||||
return raw ? { status: res.status, data } : { status: res.status, data };
|
||||
}
|
||||
const w = (author, message) => ({ author, message });
|
||||
const short = (v) => JSON.stringify(v).slice(0, 220);
|
||||
|
||||
async function main() {
|
||||
await api('DELETE', `/api/db/admin/${DB}`);
|
||||
const created = await api('POST', `/api/db/admin/${DB}`, { body: { label: DB, comment: 'WeKan history test' } });
|
||||
say(`create database: ${created.status}`);
|
||||
|
||||
const schema = [{
|
||||
'@type': 'Class', '@id': 'Card', '@key': { '@type': 'Lexical', '@fields': ['cardId'] },
|
||||
cardId: 'xsd:string', title: 'xsd:string', listId: 'xsd:string', sort: 'xsd:integer', archived: 'xsd:boolean',
|
||||
}];
|
||||
const s = await api('POST', `/api/document/admin/${DB}`, { query: { graph_type: 'schema', ...w('system', 'schema') }, body: schema });
|
||||
say(`schema: ${s.status} ${s.status === 200 ? '' : short(s.data)}`);
|
||||
|
||||
const card = (id, title, listId, sort, archived = false) => ({ '@type': 'Card', cardId: id, title, listId, sort, archived });
|
||||
const log = async () => (await api('GET', `/api/log/admin/${DB}`)).data;
|
||||
|
||||
say('\n# 1. A commit per write, with the author and message of the person who made it');
|
||||
const ins = await api('POST', `/api/document/admin/${DB}`, { query: w('alice', 'add cards'), body: [card('c1', 'Write docs', 'l1', 1), card('c2', 'Fix bug', 'l1', 2)] });
|
||||
say(`insert: ${ins.status} ${short(ins.data)}`);
|
||||
const hashes = {};
|
||||
const ops = [
|
||||
['edit title', 'alice', () => api('PUT', `/api/document/admin/${DB}`, { query: w('alice', 'edit title'), body: card('c1', 'Write docs v2', 'l1', 1) })],
|
||||
['move to Done', 'bob', () => api('PUT', `/api/document/admin/${DB}`, { query: w('bob', 'move to Done'), body: card('c1', 'Write docs v2', 'l2', 5) })],
|
||||
['archive', 'carol', () => api('PUT', `/api/document/admin/${DB}`, { query: w('carol', 'archive'), body: card('c1', 'Write docs v2', 'l2', 5, true) })],
|
||||
['hard delete c2', 'bob', () => api('DELETE', `/api/document/admin/${DB}`, { query: { ...w('bob', 'hard delete c2'), id: 'Card/c2' } })],
|
||||
];
|
||||
for (const [name, , run] of ops) {
|
||||
const before = (await log()).length;
|
||||
const r = await run();
|
||||
const l = await log();
|
||||
hashes[name] = l[0].identifier;
|
||||
say(`${name.padEnd(16)} HTTP ${r.status} commits +${l.length - before} ${l[0].author} | ${l[0].message}`);
|
||||
}
|
||||
say((await log()).slice(0, 5).map((c) => `${c.author} | ${c.message} | ${new Date(c.timestamp * 1000).toISOString()} | ${String(c.identifier).slice(0, 10)}`).join('\n'));
|
||||
|
||||
say('\n# 2. Time travel: the cards as of an earlier commit');
|
||||
const asOf = async (commit) => (await api('GET', `/api/document/admin/${DB}/local/commit/${commit}`, { query: { type: 'Card', as_list: 'true' } })).data;
|
||||
const nowCards = (await api('GET', `/api/document/admin/${DB}`, { query: { type: 'Card', as_list: 'true' } })).data;
|
||||
say(`now: ${nowCards.length} cards; as of "move to Done": ${short(await asOf(hashes['move to Done']))}`);
|
||||
|
||||
say('\n# 3. Undo the last change (WeKan: Ctrl+Z): a diff between two commits, applied backwards');
|
||||
const lastTwo = (await log()).slice(0, 2);
|
||||
const diffBack = await api('POST', `/api/diff/admin/${DB}`, { body: { before_data_version: lastTwo[0].identifier, after_data_version: lastTwo[1].identifier } });
|
||||
say(`diff (hard delete -> before it): ${diffBack.status} ${short(diffBack.data)}`);
|
||||
const patched = await api('POST', `/api/patch/admin/${DB}`, { body: { patch: diffBack.data, ...w('alice', 'undo: hard delete c2') } });
|
||||
say(`patch: ${patched.status} ${short(patched.data)}`);
|
||||
say(`c2 exists after undo: ${(await api('GET', `/api/document/admin/${DB}`, { query: { id: 'Card/c2' } })).status === 200}`);
|
||||
|
||||
say('\n# 4. Restore ONE older change while later ones stay (WeKan: Restore of a selected History row)');
|
||||
// The "edit title" commit and the one before it.
|
||||
const l = await log();
|
||||
const idx = l.findIndex((c) => c.identifier === hashes['edit title']);
|
||||
const titleBack = await api('POST', `/api/diff/admin/${DB}`, { body: { before_data_version: l[idx].identifier, after_data_version: l[idx + 1].identifier } });
|
||||
say(`diff to undo the title edit: ${short(titleBack.data)}`);
|
||||
const applied = await api('POST', `/api/patch/admin/${DB}`, { body: { patch: titleBack.data, ...w('alice', 'restore: title') } });
|
||||
say(`patch onto HEAD, where c1 was moved and archived afterwards: ${applied.status} ${short(applied.data)}`);
|
||||
const c1 = (await api('GET', `/api/document/admin/${DB}`, { query: { id: 'Card/c1' } })).data;
|
||||
say(`c1 now: ${short(c1)}`);
|
||||
|
||||
say('\n# 5. What it costs');
|
||||
const t0 = Date.now();
|
||||
for (let i = 0; i < 100; i += 1) await api('PUT', `/api/document/admin/${DB}`, { query: w('alice', `edit ${i}`), body: card('c1', `Edit ${i}`, 'l2', 5) });
|
||||
say(`PUT of a card: ${((Date.now() - t0) / 100).toFixed(1)} ms per call (an HTTP call each, a commit each); commits: ${(await log()).length}`);
|
||||
}
|
||||
|
||||
main().catch((e) => { console.error(e); process.exit(1); });
|
||||
@@ -521,7 +521,7 @@ checked on the page itself.
|
||||
does recursive search (`startWith`, `connectFromField`, `connectToField`,
|
||||
`maxDepth`), spills to disk above 100 MB, returns results unsorted.
|
||||
- **WeKan's FerretDB v1 fork does not implement `$graphLookup`** (verified in
|
||||
source: `.tools/FerretDB/internal/handler/common/aggregations/stages/stages.go`
|
||||
source: `.tools/w/FerretDB/internal/handler/common/aggregations/stages/stages.go`
|
||||
`unsupportedStages`; `docs/reference/supported-commands.md`; `ROADMAP.md`).
|
||||
Its `$lookup` is the basic equality join only. Upstream FerretDB v2's
|
||||
[compatibility page](https://docs.ferretdb.io/migration/compatibility/) does
|
||||
@@ -707,8 +707,8 @@ MongoDB documents the standard patterns in
|
||||
| Closure table (separate collection of ancestor/descendant pairs) | 1 query | 1 query | many rows | heavy; SQL idiom |
|
||||
|
||||
`$graphLookup` is **not implemented** in WeKan's FerretDB v1 fork:
|
||||
`.tools/FerretDB/internal/handler/common/aggregations/stages/stages.go` lists it
|
||||
in `unsupportedStages`, and `.tools/FerretDB/ROADMAP.md` marks it
|
||||
`.tools/w/FerretDB/internal/handler/common/aggregations/stages/stages.go` lists it
|
||||
in `unsupportedStages`, and `.tools/w/FerretDB/ROADMAP.md` marks it
|
||||
`ErrNotImplemented`. Since FerretDB on SQLite is a default WeKan database, any
|
||||
design that needs `$graphLookup` would break there. **Array of ancestors** needs
|
||||
only plain equality queries on an indexed array, which every backend supports.
|
||||
|
||||
@@ -82,8 +82,20 @@ early.
|
||||
|
||||
## What the bar holds
|
||||
|
||||
In source order: the house and the page title, the logo, the phone/desktop
|
||||
toggle, the drag-handles toggle, and then everything else.
|
||||
In source order: the house and the page title, the logo, the full screen button,
|
||||
the phone/desktop toggle, the drag-handles toggle, and then everything else.
|
||||
|
||||
**Full screen.** A button to the right of the logo puts the whole page in full
|
||||
screen and takes it out again, with the Fullscreen API (`models/lib/fullscreen.js`):
|
||||
the icon is an expand arrow, and a compress arrow while the page is in full screen,
|
||||
and it follows the browser's own way out (Esc) too. Its label is the existing
|
||||
"Maximize", and "Minimize" while the page is in full screen, so the same button
|
||||
says how to leave (Esc and the browser's own control work too). It is drawn only where the browser offers the API and the page may use
|
||||
it - iPad Safari, desktop browsers and Android browsers do; **iPhone Safari does not**
|
||||
(the API is for video there) and neither does a page inside an `<iframe>` without
|
||||
`allowfullscreen`, so no dead button is shown. On an iPhone, "Add to Home Screen"
|
||||
opens WeKan without the browser's bars (the manifest says `display: standalone`).
|
||||
It sits before the first icon the fold button collects, so folding the icons keeps it.
|
||||
|
||||
**The board's controls fold.** A caret leads the group, at its leading edge —
|
||||
left of the lock in a left-to-right page, right of it in Arabic. Folded, the
|
||||
|
||||
@@ -10,7 +10,7 @@ This is without container (without Docker or Snap).
|
||||
|
||||
Right click and download files 1-4:
|
||||
|
||||
1. [wekan-12.90-win64.zip](https://github.com/wekan/wekan/releases/download/v12.90/wekan-12.90-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
|
||||
1. [wekan-13.01-win64.zip](https://github.com/wekan/wekan/releases/download/v13.01/wekan-13.01-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
|
||||
|
||||
2. [node.exe](https://nodejs.org/dist/latest-v26.x/win-x64/node.exe)
|
||||
|
||||
@@ -22,7 +22,7 @@ Right click and download files 1-4:
|
||||
|
||||
6. Double click `mongodb-windows-x86_64-7.0.34-signed.msi` . In installer, uncheck downloading MongoDB compass.
|
||||
|
||||
7. Unzip `wekan-12.90-win64.zip` , inside it is directory `bundle`, to it copy other files:
|
||||
7. Unzip `wekan-13.01-win64.zip` , inside it is directory `bundle`, to it copy other files:
|
||||
|
||||
```
|
||||
bundle (directory)
|
||||
|
||||
@@ -518,7 +518,7 @@ Two ratchets keep that distinction reviewable. `tests/securityRegressionCoverage
|
||||
accounts for every published Hall of Fame name and requires each named regression
|
||||
suite to remain present. As of the 2026-09-05 audit, 71 of 93 have named coverage and
|
||||
22 older fixes remain explicit test gaps. `tests/hallOfFameProblemsCoverage.test.cjs`
|
||||
reads the real `.tools/wekan.fi` Hall of Fame and requires every name to be either a
|
||||
reads the real `.tools/w/wekan.fi` Hall of Fame and requires every name to be either a
|
||||
Problems catalog entry, a reasoned non-attributable case, or an explicit pending
|
||||
review. A missing companion checkout may skip the website comparison, but the
|
||||
obsolete `~/repos/w/wekan.fi` location is never read.
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Koneksi gagai",
|
||||
"cloud-settings-saved": "Peuatôran storan ka geukeubah",
|
||||
"cloud-settings-save-failed": "Gagai keubah peuatôran storan",
|
||||
"cloud": "Awan",
|
||||
"cloud-settings-desc": "Ngon WeKan lam Weddy, Cloud Panel jipeulayan le that domain WeKan nibak saboh server, tiep-tiep ngon data droe. Bak Domain Cloud Panel, SuperAdmin geu-urôh domain ngon CloudFlare.",
|
||||
"cloud-panel-domain": "Domain Cloud Panel",
|
||||
"cloud-superadmin-username": "Nan Ureueng Ngui SuperAdmin",
|
||||
"cloud-saved": "Ka teukeubah.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel cit keu SuperAdmin, bak Domain Cloud Panel.",
|
||||
"cloud-customer-name": "Nan pelanggan",
|
||||
"cloud-customer-email": "Alamat emel pelanggan",
|
||||
"cloud-billing-email": "Alamat emel tagihan",
|
||||
"cloud-start-date": "Tanggai Phôn",
|
||||
"cloud-end-date": "Tanggai Akhe",
|
||||
"cloudflare-api-key": "Kunci API CloudFlare",
|
||||
"cloudflare-api-key-desc": "Ngon kunci API CloudFlare, domain nyang zona jih na lam akun CloudFlare droeneuh meuteumèe rekod DNS proxy ngon sertifikat Origin CloudFlare. Domain laen meuteumèe sertifikat Let's Encrypt. Kunci geukeubah bak server ngon hana geupeuleumah le.",
|
||||
"cloudflare-api-key-set": "Kunci API CloudFlare ka geukeubah.",
|
||||
"cloudflare-api-key-not-set": "Hana kunci API CloudFlare nyang geukeubah: bandum domain meuteumèe sertifikat Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Pindah lampiran u Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Verbinding het misluk",
|
||||
"cloud-settings-saved": "Bergingsinstellings gestoor",
|
||||
"cloud-settings-save-failed": "Kon nie bergingsinstellings stoor nie",
|
||||
"cloud": "Wolk",
|
||||
"cloud-settings-desc": "Met WeKan in Weddy bedien die Cloud Panel baie WeKan-domeine vanaf een bediener, elk met sy eie data. By die Cloud Panel-domein bestuur die SuperAdmin die domeine en CloudFlare.",
|
||||
"cloud-panel-domain": "Cloud Panel-domein",
|
||||
"cloud-superadmin-username": "SuperAdmin-gebruikersnaam",
|
||||
"cloud-saved": "Gestoor.",
|
||||
"cloud-panel-not-allowed": "Die Cloud Panel is slegs vir die SuperAdmin, by die Cloud Panel-domein.",
|
||||
"cloud-customer-name": "Kliënt se naam",
|
||||
"cloud-customer-email": "Kliënt se e-posadres",
|
||||
"cloud-billing-email": "Faktuur-e-posadres",
|
||||
"cloud-start-date": "Begindatum",
|
||||
"cloud-end-date": "Einddatum",
|
||||
"cloudflare-api-key": "CloudFlare API-sleutel",
|
||||
"cloudflare-api-key-desc": "Met 'n CloudFlare API-sleutel kry 'n domein waarvan die sone in jou CloudFlare-rekening is 'n geproksiede DNS-rekord en 'n CloudFlare Origin-sertifikaat. Ander domeine kry 'n Let's Encrypt-sertifikaat. Die sleutel word op die bediener gestoor en word nooit weer gewys nie.",
|
||||
"cloudflare-api-key-set": "'n CloudFlare API-sleutel is gestoor.",
|
||||
"cloudflare-api-key-not-set": "Geen CloudFlare API-sleutel is gestoor nie: elke domein kry 'n Let's Encrypt-sertifikaat.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Skuif aanhangsel na Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Verbinding het misluk",
|
||||
"cloud-settings-saved": "Bergingsinstellings gestoor",
|
||||
"cloud-settings-save-failed": "Kon nie bergingsinstellings stoor nie",
|
||||
"cloud": "Wolk",
|
||||
"cloud-settings-desc": "Met WeKan in Weddy bedien die Cloud Panel baie WeKan-domeine vanaf een bediener, elk met sy eie data. By die Cloud Panel-domein bestuur die SuperAdmin die domeine en CloudFlare.",
|
||||
"cloud-panel-domain": "Cloud Panel-domein",
|
||||
"cloud-superadmin-username": "SuperAdmin-gebruikersnaam",
|
||||
"cloud-saved": "Gestoor.",
|
||||
"cloud-panel-not-allowed": "Die Cloud Panel is slegs vir die SuperAdmin, by die Cloud Panel-domein.",
|
||||
"cloud-customer-name": "Kliënt se naam",
|
||||
"cloud-customer-email": "Kliënt se e-posadres",
|
||||
"cloud-billing-email": "Faktuur-e-posadres",
|
||||
"cloud-start-date": "Begindatum",
|
||||
"cloud-end-date": "Einddatum",
|
||||
"cloudflare-api-key": "CloudFlare API-sleutel",
|
||||
"cloudflare-api-key-desc": "Met 'n CloudFlare API-sleutel kry 'n domein waarvan die sone in jou CloudFlare-rekening is 'n geproksiede DNS-rekord en 'n CloudFlare Origin-sertifikaat. Ander domeine kry 'n Let's Encrypt-sertifikaat. Die sleutel word op die bediener gestoor en word nooit weer gewys nie.",
|
||||
"cloudflare-api-key-set": "'n CloudFlare API-sleutel is gestoor.",
|
||||
"cloudflare-api-key-not-set": "Geen CloudFlare API-sleutel is gestoor nie: elke domein kry 'n Let's Encrypt-sertifikaat.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Skuif aanhangsel na Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Mfomso bi sii wɔ dwumadi yi mu",
|
||||
"cloud-settings-saved": "Wɔasie akorabea nhyehyɛe no",
|
||||
"cloud-settings-save-failed": "Wɔantumi ansie akorabea nhyehyɛe no",
|
||||
"cloud": "Mununkum",
|
||||
"cloud-settings-desc": "Sɛ WeKan wɔ Weddy mu a, Cloud Panel no som WeKan domain pii fi server baako so, na emu biara wɔ ne data. Wɔ Cloud Panel Domain no so, SuperAdmin no hwɛ domain no ne CloudFlare so.",
|
||||
"cloud-panel-domain": "Cloud Panel Domain din",
|
||||
"cloud-superadmin-username": "SuperAdmin dwumadini din",
|
||||
"cloud-saved": "Wɔasie.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel no yɛ SuperAdmin nko ara dea, wɔ Cloud Panel Domain no so.",
|
||||
"cloud-customer-name": "Adetɔfo din",
|
||||
"cloud-customer-email": "Adetɔfo email address",
|
||||
"cloud-billing-email": "Ka tua email address",
|
||||
"cloud-start-date": "Mfitiaseɛ da",
|
||||
"cloud-end-date": "Awieeɛ da",
|
||||
"cloudflare-api-key": "CloudFlare API safe",
|
||||
"cloudflare-api-key-desc": "Sɛ wowɔ CloudFlare API safe a, domain a ne zone wɔ wo CloudFlare account mu no nya DNS record a ɛfa proxy so ne CloudFlare Origin krataa. Domain afoforo nya Let's Encrypt krataa. Wɔde safe no sie server no so na wɔnkyerɛ bio da.",
|
||||
"cloudflare-api-key-set": "Wɔakora CloudFlare API safe.",
|
||||
"cloudflare-api-key-not-set": "Wɔnkoraa CloudFlare API safe biara: domain biara nya Let's Encrypt krataa.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Fa fael a wɔde aka ho no kɔ Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "ግንኙነቱ አልተሳካም",
|
||||
"cloud-settings-saved": "የማከማቻ ቅንብሮች ተቀምጠዋል",
|
||||
"cloud-settings-save-failed": "የማከማቻ ቅንብሮችን ማስቀመጥ አልተሳካም",
|
||||
"cloud": "ደመና",
|
||||
"cloud-settings-desc": "WeKan በWeddy ውስጥ ሲሆን፣ Cloud Panel ብዙ የWeKan ጎራዎችን ከአንድ አገልጋይ ያገለግላል፣ እያንዳንዱም የራሱ ውሂብ አለው። በCloud Panel ጎራ ላይ SuperAdmin ጎራዎቹንና CloudFlareን ያስተዳድራል።",
|
||||
"cloud-panel-domain": "የCloud Panel ጎራ",
|
||||
"cloud-superadmin-username": "የSuperAdmin የተጠቃሚ ስም",
|
||||
"cloud-saved": "ተቀምጧል።",
|
||||
"cloud-panel-not-allowed": "Cloud Panel ለSuperAdmin ብቻ ነው፣ በCloud Panel ጎራ ላይ።",
|
||||
"cloud-customer-name": "የደንበኛ ስም",
|
||||
"cloud-customer-email": "የደንበኛ ኢሜይል አድራሻ",
|
||||
"cloud-billing-email": "የክፍያ ኢሜይል አድራሻ",
|
||||
"cloud-start-date": "የመጀመሪያ ቀን",
|
||||
"cloud-end-date": "የማብቂያ ቀን",
|
||||
"cloudflare-api-key": "የCloudFlare API ቁልፍ",
|
||||
"cloudflare-api-key-desc": "በCloudFlare API ቁልፍ፣ ዞኑ በCloudFlare መለያዎ ውስጥ ያለ ጎራ በፕሮክሲ የሚያልፍ የDNS መዝገብ እና የCloudFlare Origin የምስክር ወረቀት ያገኛል። ሌሎች ጎራዎች የLet's Encrypt የምስክር ወረቀት ያገኛሉ። ቁልፉ በአገልጋዩ ላይ ይቀመጣል እና ዳግም አይታይም።",
|
||||
"cloudflare-api-key-set": "የCloudFlare API ቁልፍ ተቀምጧል።",
|
||||
"cloudflare-api-key-not-set": "ምንም የCloudFlare API ቁልፍ አልተቀመጠም፦ እያንዳንዱ ጎራ የLet's Encrypt የምስክር ወረቀት ያገኛል።",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "አባሪውን ወደ Azure Blob Storage አንቀሳቅስ",
|
||||
|
||||
+215
-200
@@ -227,20 +227,20 @@
|
||||
"attachment-soft-delete-pop": "L'adchunto se saca d'esta tarcheta y o suyo fichero se conserva. Qualsequiera que pueda editar a tarcheta puede restaurar-lo dende l'historial d'a tarcheta.",
|
||||
"attachmentDeletePopup-title": "¿Borrar el adjunto?",
|
||||
"attachments": "Adchuntos",
|
||||
"attach-card": "Attach card",
|
||||
"attachCardPopup-title": "Attach card",
|
||||
"attach-card-hint": "Paste a card link (Copy card link on the other card), or type a card title on this board",
|
||||
"attached-card-unavailable": "An attached card you cannot see",
|
||||
"detach-card": "Remove attached card",
|
||||
"attach-card-self": "A card cannot be attached to itself",
|
||||
"attach-card-limit": "This card already has as many attached cards as it can have",
|
||||
"attach-card-not-found": "No card you can see was found for that link",
|
||||
"attach-card": "Adchuntar tarcheta",
|
||||
"attachCardPopup-title": "Adchuntar tarcheta",
|
||||
"attach-card-hint": "Apega un vinclo de tarcheta (Copiar vinclo d'a tarcheta en l'atra tarcheta), u escribe un titol de tarcheta d'este tablero",
|
||||
"attached-card-unavailable": "Una tarcheta adchunta que no puetz veyer",
|
||||
"detach-card": "Sacar a tarcheta adchunta",
|
||||
"attach-card-self": "Una tarcheta no se puet adchuntar a ella mesma",
|
||||
"attach-card-limit": "Esta tarcheta ya tiene tantas tarchetas adchuntas como puede tener",
|
||||
"attach-card-not-found": "No s'ha trobau garra tarcheta que puedas veyer pa ixe vinclo",
|
||||
"auto-watch": "Suscribir-se automaticament a los tableros quan se crean",
|
||||
"avatar-too-big": "L’avatar ye masiau gran (__size__ como maximo)",
|
||||
"back": "Dezaga",
|
||||
"board-change-color": "Cambiar el color",
|
||||
"board-announcement": "Board announcement",
|
||||
"board-announcement-enabled": "Show it to everyone on this board",
|
||||
"board-announcement": "Anuncio d'o tablero",
|
||||
"board-announcement-enabled": "Amostrar-lo a totz en este tablero",
|
||||
"board-change-background-image": "Cambiar imagen de fondo",
|
||||
"board-background-image-url": "URL d’a imachen de fondo",
|
||||
"add-background-image": "Adhibir imagen de fondo",
|
||||
@@ -284,7 +284,7 @@
|
||||
"zoom-level": "Nivel de zoom",
|
||||
"enter-zoom-level": "Introduz o livel de zoom (50-300%):",
|
||||
"board-view-cal": "Calendario",
|
||||
"board-view-calendar-mode": "Calendar Mode",
|
||||
"board-view-calendar-mode": "Modo calendario",
|
||||
"board-view-multiboard-cal": "Calendario (Toz os tableros)",
|
||||
"board-view-swimlanes": "Carrils",
|
||||
"board-view-collapse": "Contraer",
|
||||
@@ -686,32 +686,32 @@
|
||||
"chooseBoardSourcePopup-title": "Importar un tablero",
|
||||
"createLabelPopup-title": "Crear una etiqueta",
|
||||
"createCustomField": "Crear un campo",
|
||||
"custom-field-links": "Link fields to another card",
|
||||
"custom-field-links-hint": "Custom fields with the same name and type on both cards are linked: when a value changes on one card, it is copied to the other card. Fields that only one card has are left alone.",
|
||||
"custom-field-links-none": "This card's custom fields are not linked to any card.",
|
||||
"custom-field-link-unavailable": "A linked card you cannot see",
|
||||
"custom-field-link-fields": "Linked fields:",
|
||||
"custom-field-link-no-fields": "No fields with the same name and type yet",
|
||||
"custom-field-link-inactive": "Paused: the person who made this link can no longer edit both cards, or a card is archived.",
|
||||
"custom-field-unlink": "Unlink",
|
||||
"custom-field-link-target": "Card link (Copy card link), or search this board's cards",
|
||||
"custom-field-link-direction": "Direction",
|
||||
"custom-field-link-both": "Both ways: changes on either card go to the other",
|
||||
"custom-field-link-send": "One way: changes on this card go to the main card",
|
||||
"custom-field-link-sends": "Changes on this card go to that card",
|
||||
"custom-field-link-receives": "Changes on that card come to this card",
|
||||
"custom-field-link-add": "Link fields",
|
||||
"field-link-self": "A card cannot be linked to itself.",
|
||||
"field-link-limit": "This card, or that card, already has the most linked cards it can have.",
|
||||
"field-link-archived": "An archived card cannot be linked.",
|
||||
"field-link-invalid": "That is not a card link.",
|
||||
"field-link-not-found": "Card not found.",
|
||||
"field-link-not-allowed": "You need to be able to edit both cards.",
|
||||
"cardCustomFieldLinksPopup-title": "Linked custom fields",
|
||||
"custom-field-links": "Vincular campos a una atra tarcheta",
|
||||
"custom-field-links-hint": "Os campos personalizaus con o mesmo nombre y tipo en as dos tarchetas son vinculaus: quan una valor cambia en una tarcheta, se copia en l'atra. Os campos que nomás tiene una tarcheta no se tocan.",
|
||||
"custom-field-links-none": "Os campos personalizaus d'esta tarcheta no son vinculaus a garra tarcheta.",
|
||||
"custom-field-link-unavailable": "Una tarcheta vinculada que no puetz veyer",
|
||||
"custom-field-link-fields": "Campos vinculaus:",
|
||||
"custom-field-link-no-fields": "Encara no i hai campos con o mesmo nombre y tipo",
|
||||
"custom-field-link-inactive": "En pausa: a persona que fació este vinclo ya no puet editar as dos tarchetas, u una tarcheta ye archivada.",
|
||||
"custom-field-unlink": "Desvincular",
|
||||
"custom-field-link-target": "Vinclo de tarcheta (Copiar vinclo d'a tarcheta), u mira entre as tarchetas d'este tablero",
|
||||
"custom-field-link-direction": "Adreza",
|
||||
"custom-field-link-both": "En as dos adrezas: os cambios en qualsequier tarcheta van ta l'atra",
|
||||
"custom-field-link-send": "En una adreza: os cambios en esta tarcheta van ta la tarcheta principal",
|
||||
"custom-field-link-sends": "Os cambios en esta tarcheta van ta ixa tarcheta",
|
||||
"custom-field-link-receives": "Os cambios en ixa tarcheta plegan ta esta tarcheta",
|
||||
"custom-field-link-add": "Vincular campos",
|
||||
"field-link-self": "Una tarcheta no se puet vincular a ella mesma.",
|
||||
"field-link-limit": "Esta tarcheta, u ixa tarcheta, ya tiene o maximo de tarchetas vinculadas que puede tener.",
|
||||
"field-link-archived": "Una tarcheta archivada no se puet vincular.",
|
||||
"field-link-invalid": "Ixo no ye un vinclo de tarcheta.",
|
||||
"field-link-not-found": "No s'ha trobau a tarcheta.",
|
||||
"field-link-not-allowed": "Has de poder editar as dos tarchetas.",
|
||||
"cardCustomFieldLinksPopup-title": "Campos personalizaus vinculaus",
|
||||
"createCustomFieldPopup-title": "Crear un campo",
|
||||
"current": "actual",
|
||||
"custom-color": "Color personalizado",
|
||||
"cards-use-list-color": "Cards without a colour of their own use this colour",
|
||||
"cards-use-list-color": "As tarchetas sin color propia fan servir esta color",
|
||||
"custom-field-delete-pop": "Se borrará iste campo personalizado de todas as tarchetas y se destruirá su historial. Ista acción no puet deshacerse.",
|
||||
"custom-field-checkbox": "Casilla de verificación",
|
||||
"custom-field-currency": "Moneda",
|
||||
@@ -909,20 +909,20 @@
|
||||
"import-board-instruction-zenkit": "Apega un JSON d'exportación de lista de ZenKit ({ \"title\", \"stages\":[...], \"items\":[...] }). As etapas se tornan listas y os elementos se tornan tarchetas.",
|
||||
"import-board-instruction-markdown": "Apega una lista de quefers markdown (a convención que emplegan ferramientas markdown-kanban como Obsidian Kanban): capiters \"## Nombre de lista\", con elementos \"- [ ]\"/\"- [x]\" debaixo. Os capiters se tornan listas y os elementos se tornan tarchetas; una lista simpla con pinchos sin caixetas de selección tamién s'importa, como tarchetas ubiertas.",
|
||||
"import-board-instruction-leo": "Apega un esquema Leo (fichero .leo). Os nodos de primer nivel se tornan listas, os suyos fillos se tornan tarchetas con o cuerpo d'o nodo como descripción, y os nodos más fundos se tornan listas de comprebación. Os nodos marcaus s'importan como feitos.",
|
||||
"import-board-instruction-opml": "Paste an OPML outline, as Workflowy, Dynalist, OmniOutliner, Logseq and other outliners export it. Top-level items become lists, their children become cards with the note as the description, and deeper items become checklists. Completed items import as done.",
|
||||
"import-board-instruction-orgmode": "Paste an Org mode file, as Emacs, Orgzly or Beorg write it. Top-level headings become lists and second-level headings become cards. TODO and DONE, priorities, tags, SCHEDULED, DEADLINE and CLOSED are kept, and checkboxes and deeper headings become checklists.",
|
||||
"import-board-instruction-opml": "Apega un esquema OPML, tal como l'exportan Workflowy, Dynalist, OmniOutliner, Logseq y atros editors d'esquemas. Os elementos d'o primer nivel se tornan listas, os suyos fillos se tornan tarchetas con a nota como descripción, y os elementos mas fondos se tornan listas de comprebación. Os elementos completaus s'importan como feitos.",
|
||||
"import-board-instruction-orgmode": "Apega un fichero Org mode, tal como lo escriben Emacs, Orgzly u Beorg. Os titols d'o primer nivel se tornan listas y os d'o segundo nivel se tornan tarchetas. TODO y DONE, prioridatz, etiquetas, SCHEDULED, DEADLINE y CLOSED se conservan, y as caixetas de verificación y os titols mas fondos se tornan listas de comprebación.",
|
||||
"import-board-instruction-todotxt": "Apega un fichero todo.txt: un quefer por linia, tal como lo escriben l'aplicación todo.txt y as aplicacions compatibles. Os quefers remataus (\"x\") van ta Feito, +project y @context se tornan etiquetas, (A) se torna una etiqueta de prioridat, y due: y t: se tornan datas de vencimiento y d'inicio.",
|
||||
"import-board-instruction-taskwarrior": "Apega a salida de task export: una matriz JSON u una fayena por linia. As fayenas completadas van ta Done, project y priority se convierten en etiquetas, annotations en comentarios y depends en dependencias d’as fayenas que las blocan.",
|
||||
"import-board-instruction-focalboard": "Descomprime o fichero .boardarchive y apega o conteniu d’o board.jsonl d’o tablero, con u sin a linia de versión. A propiedat d’agrupación d’a vista d’o tablero se convierte en listas, as atras propiedatz de selección en etiquetas, os bloques de texto y de caixetas de verificación en a descripción y una lista de comprebación, y os comentarios se conservan. As imachens y os adchuntos no s’importan.",
|
||||
"import-board-instruction-todoist": "In Todoist, open the project menu and choose Export as a template, then paste the CSV file. Sections become lists, tasks become cards, sub-tasks become a checklist, notes become comments, and @labels and priorities p1 to p3 become labels.",
|
||||
"import-board-instruction-planner": "In Microsoft Planner, open the plan's menu and choose Export plan to Excel, then choose that .xlsx file. Buckets become lists and tasks become cards, with their assignees, dates, labels, checklist and description; Progress, Priority and Completed By become custom fields.",
|
||||
"import-board-instruction-meistertask": "In MeisterTask, click the project name, choose Export project and export the CSV, then paste it. Sections become lists and tasks become cards, with their notes, due date, assignee and tags; completed tasks keep their completion date. A CSV written for MeisterTask's own import works too.",
|
||||
"import-board-instruction-obsidian": "In Obsidian, open the Kanban board's file as Markdown (or open the .md file in a text editor), then paste all of it. Lanes become lists with their card limits, cards keep their tags, dates, priority, fields and checklist, and the Archive becomes archived cards.",
|
||||
"import-board-instruction-linear": "In Linear, export the issues as CSV (Settings, Import / Export, Export data, or a view's export), then paste the CSV. Statuses become lists, teams become swimlanes, and issues become cards with their assignee, labels, dates, parent issue, priority, estimate, project and cycle.",
|
||||
"import-board-instruction-ticktick": "In TickTick, open Settings, Account, Backup & Import and generate a backup, then paste the CSV file. Each TickTick list becomes a swimlane and its columns become lists; tasks keep their tags, dates, checklist, priority and subtasks.",
|
||||
"import-board-instruction-clickup": "In ClickUp, export the tasks as CSV (Settings, Imports / Exports, Export Items, or a List or Table view's export with all columns), then paste the CSV. Statuses become lists, ClickUp lists become swimlanes, and tasks become cards with their subtasks, assignees, tags, dates, priority and time.",
|
||||
"import-board-instruction-nullboard": "In Nullboard, open the board, choose Export this board... from the menu, open the .nbx file in a text editor and paste its contents. Lists become lists and notes become cards: a note's first line is the card title and the rest its description. A raw note gets the label raw. A file with several boards imports its first board.",
|
||||
"import-board-instruction-kanri": "In Kanri, open Import & Export and choose Export individual board (or Export all data), then paste the .json file. Columns become lists and cards become cards, with their description, due date, tags and colors; a card's tasks become a checklist. From an all-data export the first board is imported.",
|
||||
"import-board-instruction-meistertask": "En MeisterTask, fe clic en o nombre d'o prochecto, tría Export project y exporta o CSV, dimpués apega-lo. As seccions se tornan listas y as tareas se tornan tarchetas, con as suyas notas, calendata de vencimiento, asignau y etiquetas; as tareas completadas conservan a suya calendata de remate. Un CSV escrito pa la importación propia de MeisterTask tamién funciona.",
|
||||
"import-board-instruction-obsidian": "En Obsidian, ubre o fichero d'o tablero Kanban como Markdown (u ubre o fichero .md en un editor de texto), y dimpués apega-lo tot. Os carrils se tornan listas con os suyos limites de tarchetas, as tarchetas conservan as suyas etiquetas, calendatas, prioridat, campos y lista de comprebación, y l'Archive se torna tarchetas archivadas.",
|
||||
"import-board-instruction-linear": "En Linear, exporta as incidencias como CSV (Settings, Import / Export, Export data, u a exportación d'una vista), dimpués apega o CSV. Os estaus se tornan listas, os equipos se tornan carrils, y as incidencias se tornan tarchetas con o suyo asignau, etiquetas, calendatas, incidencia pai, prioridat, estimación, prochecto y cicle.",
|
||||
"import-board-instruction-ticktick": "En TickTick, ubre Settings, Account, Backup & Import y chenera una copia de seguranza, dimpués apega o fichero CSV. Cada lista de TickTick se torna un carril y as suyas columnas se tornan listas; as tareas conservan as suyas etiquetas, calendatas, lista de comprebación, prioridat y subtareas.",
|
||||
"import-board-instruction-clickup": "En ClickUp, exporta as tareas como CSV (Settings, Imports / Exports, Export Items, u a exportación d'una vista List u Table con todas as columnas), dimpués apega o CSV. Os estaus se tornan listas, as listas de ClickUp se tornan carrils, y as tareas se tornan tarchetas con as suyas subtareas, asignaus, etiquetas, calendatas, prioridat y tiempo.",
|
||||
"import-board-instruction-nullboard": "En Nullboard, ubre o tablero, tría Export this board... d'o menú, ubre o fichero .nbx en un editor de texto y apega o suyo conteniu. As listas se tornan listas y as notas se tornan tarchetas: a primer linia d'una nota ye o titol d'a tarcheta y a resta a suya descripción. Una nota en cruo recibe l'etiqueta raw. D'un fichero con quantos tableros s'importa o primer tablero.",
|
||||
"import-board-instruction-kanri": "En Kanri, ubre Import & Export y tría Export individual board (u Export all data), dimpués apega o fichero .json. As columnas se tornan listas y as tarchetas se tornan tarchetas, con a suya descripción, calendata de vencimiento, etiquetas y colors; as tareas d'una tarcheta se tornan una lista de comprebación. D'una exportación de totz os datos s'importa o primer tablero.",
|
||||
"import-board-instruction-planka": "Escribe l'adreza d'o tuyo Planka (per eixemplo https://planka.example.com), o tuyo nombre d'usuario y clau de paso de Planka, y dimpués preta Listar. Nomás s'usan pa recullir o tablero y no se alzan. Tría un tablero y preta Recullir: WeKan lo leye dende l'API de Planka, con as suyas listas, tarchetas, descripcions, calendatas de vencimiento, etiquetas, miembros, listas de fayenas y comentarios, y dimpués contina a importación como de costumbre. As tarchetas de l'archivo de Planka s'importan archivadas; os adchuntos y as tarchetas d'a paperera no s'importan.",
|
||||
"import-board-instruction-mstodo": "En Microsoft Graph Explorer (developer.microsoft.com/graph/graph-explorer), dentra, consiente o permiso Tasks.Read y copia o token d'a pestanya Access token. Apega-lo aquí y fe clic en Listar; o token nomás s'usa pa esta recullida y no s'alza. Tría totas as listas u una lista y fe clic en Recullir. Cada lista de To Do se fa una lista y cada fayena una tarcheta, con as suyas notas, calendatas de vencimiento y d'inicio, os pasos como lista de comprebación, as categorías como etiquetas, y l'importancia alta y l'estau como etiquetas; as fayenas completadas s'archivan. Os recordatorios y repeticions no s'importan.",
|
||||
"import-board-instruction-kanbanflow": "En KanbanFlow, ubre o menú d'o tablero, tría Settings → API & Webhooks y copia o token d'API d'o tablero. Apega o token aquí, fe clic en Listar, tría o tablero y fe clic en Recullir; o token nomás s'usa pa ixa recullida y no s'alza. As columnas se fan listas y os carrils se quedan carrils; as fayenas mantienen a suya descripción, color, etiquetas, calendata de vencimiento, subfayenas como lista de comprebación, tiempo emplegau, estimación, personas y comentarios.",
|
||||
@@ -941,19 +941,19 @@
|
||||
"api-import-fetched": "Recullido. Revisa os miembros d'abaixo y importa.",
|
||||
"api-import-failed": "A recullida ha fallau",
|
||||
"api-import-nothing": "No i hai cosa que importar con esta cuenta.",
|
||||
"import-board-instruction-pivotal": "In Pivotal Tracker, open the project's MORE tab and choose Export CSV (or select stories and choose Bulk Actions, CSV), then paste the CSV. Each story state becomes a list and stories become cards, with their type and labels, owners, requester, dates, comments, tasks and blockers; Estimate becomes Story points and iterations become sprints.",
|
||||
"import-board-instruction-tasksorg": "In Tasks.org, open Settings, Backups and choose Export tasks, then paste the contents of that .json file. Lists become lists and tasks become cards, with their notes, tags, dates, comments and subtasks; completed tasks keep their completion date, and priority becomes a custom field.",
|
||||
"import-board-instruction-monday": "In monday.com, open the board menu, choose More actions and Export board to Excel (with updates and subitems if you like), then choose that .xlsx file. The Status column becomes lists and groups become swimlanes; people, dates, timeline and tags keep their places, other columns become custom fields and updates become comments.",
|
||||
"import-board-instruction-superproductivity": "In Super Productivity, open Settings, then Sync & Backup, choose Export Data and paste the contents of the sp-backup .json file. Each project becomes a swimlane and tasks become cards in To Do, In Progress, Backlog or Done, with their notes, tags, due date, time spent and sub-tasks; archived tasks become archived cards.",
|
||||
"import-board-instruction-taiga": "In Taiga, open Admin > Project > Export and download the project dump (unpack a .json.gz file first), then paste its JSON. User story statuses become lists, user stories become cards with their tasks as subtasks, epics and issues get swimlanes of their own, and sprints, tags, comments and custom attributes come along. Attachments are not imported.",
|
||||
"import-board-instruction-vikunja": "In Vikunja, open Settings, choose Data Export and download the .zip file it sends you, then choose that file, or paste the data.json inside it. Kanban buckets become lists and tasks become cards, with their labels, assignees, dates, description, task lists as checklists and comments; priority, percent done and done become custom fields. Attachments are not imported.",
|
||||
"import-board-instruction-quire": "In Quire, open the project menu and choose Export CSV, then paste the CSV. Statuses become lists and tasks become cards, with their subtasks as child cards, assignees, tags as labels, start, due and completed dates, description and priority as a custom field. Comments and attachments are not in Quire's CSV.",
|
||||
"import-board-instruction-wrike": "In Wrike, export a project or folder to Excel, or fill in Wrike's Excel import template, then choose that .xlsx file. Folder and project rows become swimlanes, Status becomes lists and tasks become cards, with their Key, parent task, assignees, start and end dates, dependencies and description; Priority, Duration and other columns become custom fields.",
|
||||
"import-board-instruction-teamwork": "In Teamwork.com, fill in the Excel task import template (Tasklist, Task, Description, Assign to, Start date, Due date, Priority, Estimated time, Tags, Status) and save it as .xlsx, then choose that file. Task lists become lists and tasks become cards; a task starting with -, # or > becomes a subtask of the task above it (--, ## or >> one level deeper). Assignees, dates and tags keep their places; Priority, Estimated time and Complete become custom fields.",
|
||||
"import-board-instruction-businessmap": "In Businessmap (formerly Kanbanize), open Advanced Search, choose Configure results to pick the columns, download the Excel file, then choose that .xlsx file. Columns are read by their English names (Title, Column, Lane, Owner, Deadline ...): if your account uses another language, rename the header row to English first. Column becomes lists and Lane swimlanes; owners, tags, color, dates, comments and parent links keep their places, and Priority, Size, Type and other columns become custom fields.",
|
||||
"import-board-instruction-redmine": "In Redmine, open the project's Issues, choose Also available in: CSV at the bottom of the list, pick the columns (All columns, with Description) and export, then paste the CSV. Redmine names the columns in your language, so set your language to English in My account before exporting. Each status becomes a list and issues become cards, with the tracker as a label, assignee, author, dates, parent task and related issues; priority, category, target version, estimated time, % Done and custom fields become custom fields.",
|
||||
"import-board-instruction-notion": "In Notion, open the database as a full page, choose ••• and Export, pick Markdown & CSV, and choose the .zip file it downloads, or one database CSV from it, or paste that CSV. A column named Status becomes the lists (without one, the first column that looks like a select does, and the import report says which); rows become cards with their title, tags as labels, people as members, dates, and the page text as the description; other columns become custom fields. Relations, images and attachments are not imported.",
|
||||
"import-board-instruction-plane": "In Plane, open Workspace Settings, choose Exports, export the projects as JSON, CSV or Excel and download the .zip file, then choose that file, or one file from inside it, or paste the JSON or CSV. States become lists, projects become swimlanes, and issues become cards with their identifier, parent, assignees, labels, dates, links and comments; priority, estimate, cycle and module become custom fields. Plane's export has no descriptions or attachments, so they are not imported.",
|
||||
"import-board-instruction-pivotal": "En Pivotal Tracker, ubre a pestanya MORE d'o prochecto y tría Export CSV (u selecciona historias y tría Bulk Actions, CSV), dimpués apega o CSV. Cada estau d'historia se torna una lista y as historias se tornan tarchetas, con o suyo tipo y etiquetas, propietarios, solicitant, calendatas, comentarios, tareas y bloqueos; Estimate se torna Story points y as iteracions se tornan sprints.",
|
||||
"import-board-instruction-tasksorg": "En Tasks.org, ubre Settings, Backups y tría Export tasks, dimpués apega o conteniu d'ixe fichero .json. As listas se tornan listas y as tareas se tornan tarchetas, con as suyas notas, etiquetas, calendatas, comentarios y subtareas; as tareas completadas conservan a suya calendata de remate, y a prioridat se torna un campo personalizau.",
|
||||
"import-board-instruction-monday": "En monday.com, ubre o menú d'o tablero, tría More actions y Export board to Excel (con updates y subitems si quiers), dimpués tría ixe fichero .xlsx. A columna Status se torna listas y os grupos se tornan carrils; personas, calendatas, timeline y etiquetas conservan o suyo puesto, as atras columnas se tornan campos personalizaus y os updates se tornan comentarios.",
|
||||
"import-board-instruction-superproductivity": "En Super Productivity, ubre Settings, dimpués Sync & Backup, tría Export Data y apega o conteniu d'o fichero sp-backup .json. Cada prochecto se torna un carril y as tareas se tornan tarchetas en To Do, In Progress, Backlog u Done, con as suyas notas, etiquetas, calendata de vencimiento, tiempo emplegau y subtareas; as tareas archivadas se tornan tarchetas archivadas.",
|
||||
"import-board-instruction-taiga": "En Taiga, ubre Admin > Project > Export y descarga o volcau d'o prochecto (descomprime primero un fichero .json.gz), dimpués apega o suyo JSON. Os estaus d'as historias d'usuario se tornan listas, as historias d'usuario se tornan tarchetas con as suyas tareas como subtareas, as epicas y incidencias tienen carrils propios, y os sprints, etiquetas, comentarios y atributos personalizaus tamién plegan. Os adchuntos no s'importan.",
|
||||
"import-board-instruction-vikunja": "En Vikunja, ubre Settings, tría Data Export y descarga o fichero .zip que te ninvia, dimpués tría ixe fichero, u apega o data.json que i hai adintro. Os cubos Kanban se tornan listas y as tareas se tornan tarchetas, con as suyas etiquetas, asignaus, calendatas, descripción, listas de tareas como listas de comprebación y comentarios; a prioridat, o percentache feito y feito se tornan campos personalizaus. Os adchuntos no s'importan.",
|
||||
"import-board-instruction-quire": "En Quire, ubre o menú d'o prochecto y tría Export CSV, dimpués apega o CSV. Os estaus se tornan listas y as tareas se tornan tarchetas, con as suyas subtareas como tarchetas fillas, asignaus, etiquetas, calendatas d'inicio, vencimiento y remate, descripción y prioridat como campo personalizau. Os comentarios y adchuntos no son en o CSV de Quire.",
|
||||
"import-board-instruction-wrike": "En Wrike, exporta un prochecto u carpeta ta Excel, u replena a plantilla d'importación Excel de Wrike, dimpués tría ixe fichero .xlsx. As filas de carpeta y prochecto se tornan carrils, Status se torna listas y as tareas se tornan tarchetas, con a suya Key, tarea pai, asignaus, calendatas d'inicio y fin, dependencias y descripción; Priority, Duration y atras columnas se tornan campos personalizaus.",
|
||||
"import-board-instruction-teamwork": "En Teamwork.com, replena a plantilla Excel d'importación de tareas (Tasklist, Task, Description, Assign to, Start date, Due date, Priority, Estimated time, Tags, Status) y alza-la como .xlsx, dimpués tría ixe fichero. As listas de tareas se tornan listas y as tareas se tornan tarchetas; una tarea que empecipia con -, # u > se torna una subtarea d'a tarea d'alto (--, ## u >> un nivel mas fondo). Asignaus, calendatas y etiquetas conservan o suyo puesto; Priority, Estimated time y Complete se tornan campos personalizaus.",
|
||||
"import-board-instruction-businessmap": "En Businessmap (antes Kanbanize), ubre Advanced Search, tría Configure results pa eslechir as columnas, descarga o fichero Excel, dimpués tría ixe fichero .xlsx. As columnas se leyen por os suyos nombres en anglés (Title, Column, Lane, Owner, Deadline ...): si a tuya cuenta fa servir unatra luenga, renombra primero a fila de capitero en anglés. Column se torna listas y Lane carrils; propietarios, etiquetas, color, calendatas, comentarios y vinclos pai conservan o suyo puesto, y Priority, Size, Type y atras columnas se tornan campos personalizaus.",
|
||||
"import-board-instruction-redmine": "En Redmine, ubre as Issues d'o prochecto, tría Also available in: CSV en o baixo d'a lista, eslei as columnas (All columns, con Description) y exporta, dimpués apega o CSV. Redmine nombra as columnas en a tuya luenga, asinas que meteba a tuya luenga en anglés en My account antes d'exportar. Cada estau se torna una lista y as incidencias se tornan tarchetas, con o tracker como etiqueta, asignau, autor, calendatas, tarea pai y incidencias relacionadas; prioridat, categoría, versión obchectivo, tiempo estimau, % Done y campos personalizaus se tornan campos personalizaus.",
|
||||
"import-board-instruction-notion": "En Notion, ubre a base de datos como pachina completa, tría ••• y Export, eslei Markdown & CSV, y tría o fichero .zip que se descarga, u un CSV de base de datos d'adintro, u apega ixe CSV. Una columna clamada Status se torna as listas (si no i ye, la fa a primer columna que pareixe una selección, y o informe d'importación diz quala); as filas se tornan tarchetas con o suyo titol, etiquetas como etiquetas, personas como miembros, calendatas, y o texto d'a pachina como descripción; as atras columnas se tornan campos personalizaus. As relacions, imachens y adchuntos no s'importan.",
|
||||
"import-board-instruction-plane": "En Plane, ubre Workspace Settings, tría Exports, exporta os prochectos como JSON, CSV u Excel y descarga o fichero .zip, dimpués tría ixe fichero, u un fichero d'adintro, u apega o JSON u o CSV. Os estaus se tornan listas, os prochectos se tornan carrils, y as incidencias se tornan tarchetas con o suyo identificador, pai, asignaus, etiquetas, calendatas, vinclos y comentarios; prioridat, estimación, cicle y modulo se tornan campos personalizaus. A exportación de Plane no tiene descripcions ni adchuntos, asinas que no s'importan.",
|
||||
"import-board-instruction-trello": "En o tuyo tablero de Trello, ves ta 'Menú', dimpués 'Más', 'Imprimir y exportar', 'Exportar JSON', y copia o texto resultante.",
|
||||
"import-board-instruction-csv": "Apega os tuyos valors separaus por comas (CSV)/valors separaus por tabuladors (TSV).",
|
||||
"import-board-instruction-jira": "Apega o JSON d'a busca d'incidencias de l'API REST de Jira Cloud (GET /rest/api/3/search/jql), u un obchecto equivalent { \"issues\": [...] }. Os estaus de Jira se tornan listas y as incidencias se tornan tarchetas. Opcionalment, incluye una matriz \"automationRules\" ta importar reglas.",
|
||||
@@ -962,33 +962,33 @@
|
||||
"import-board-instruction-wekan": "En o tuyo tablero, ves ta 'Menú', dimpués 'Exportar tablero', y copia o texto d'o fichero descargau.",
|
||||
"import-board-instruction-about-errors": "Si tiens errors en importar o tablero, a vegadas a importación funciona igualment y o tablero ye en a pachina de Todos os tableros.",
|
||||
"import-without-mapping-members": "Importar sin asignar miembros (asignar mas tarde)",
|
||||
"import-members-mode-heading": "People in the file:",
|
||||
"import-members-mode-map": "Choose an existing user for each of them",
|
||||
"import-members-mode-placeholder": "Keep them as placeholder users with their own names, to map to real users later",
|
||||
"import-members-mode-me": "Make them all me",
|
||||
"csv-mapping-title": "Which column is which?",
|
||||
"csv-mapping-description": "Choose the column of the file that holds each card field. The choices were made from the column names; change any that are wrong, and choose (not in the file) for a field the file does not have.",
|
||||
"csv-mapping-not-in-file": "(not in the file)",
|
||||
"csv-mapping-column-number": "Column __number__",
|
||||
"csv-mapping-list-missing": "The file has no column for the list. Choose one above, or type the name of the one list every card goes into.",
|
||||
"csv-mapping-list-name": "List for every card",
|
||||
"csv-mapping-custom-fields": "Other columns to import as text custom fields:",
|
||||
"csv-mapping-boards": "Each sheet becomes its own board:",
|
||||
"csv-mapping-skipped-sheets": "Sheets that are not imported:",
|
||||
"csv-mapping-title-required": "Choose the column that holds the card titles.",
|
||||
"csv-mapping-list-required": "Type the name of the list every card goes into, or choose the column that holds the list.",
|
||||
"invalid-import-mapping": "The column mapping does not match the file.",
|
||||
"import-many-boards": "Import many boards",
|
||||
"import-many-boards-hint": "Choose several export files of this tool, or one .zip that holds them: each file becomes its own board, imported without member mapping. A .zip that is itself one export (Vikunja, Notion, Plane, or WeKan with attachments) is one board. When files are chosen here, Import imports them instead of the field above.",
|
||||
"import-one-board-per-project": "One board per project",
|
||||
"import-one-board-per-project-hint": "Each swimlane the import would make (a project, folder or list of the tool) becomes its own board. Parent cards and dependencies between those boards are listed in the import report.",
|
||||
"import-many-progress": "Importing __done__ of __total__: __name__",
|
||||
"import-many-results-heading": "Imported files",
|
||||
"import-many-imported": "imported",
|
||||
"export-all-boards": "Export all boards",
|
||||
"export-selected-boards": "Export selected boards",
|
||||
"export-all-boards-hint": "Every board you can export, in one download: Excel as one workbook with a sheet per board named after it, every other format as a .zip with a file per board.",
|
||||
"exportAllBoardsPopup-title": "Export all boards",
|
||||
"import-members-mode-heading": "Personas en o fichero:",
|
||||
"import-members-mode-map": "Tría un usuario existent pa cadaguna d'ellas",
|
||||
"import-members-mode-placeholder": "Conservar-las como usuarios provisionals con os suyos nombres, pa asignar-las a usuarios reals mas tardi",
|
||||
"import-members-mode-me": "Fer que totz sigan yo",
|
||||
"csv-mapping-title": "Quala columna ye quala?",
|
||||
"csv-mapping-description": "Tría a columna d'o fichero que tiene cada campo d'a tarcheta. As opcions s'han feito a partir d'os nombres d'as columnas; cambia as que sigan erronias, y tría (no ye en o fichero) pa un campo que o fichero no tiene.",
|
||||
"csv-mapping-not-in-file": "(no ye en o fichero)",
|
||||
"csv-mapping-column-number": "Columna __number__",
|
||||
"csv-mapping-list-missing": "O fichero no tiene columna pa la lista. Tría-ne una d'alto, u escribe o nombre d'a sola lista an que van todas as tarchetas.",
|
||||
"csv-mapping-list-name": "Lista pa todas as tarchetas",
|
||||
"csv-mapping-custom-fields": "Atras columnas a importar como campos personalizaus de texto:",
|
||||
"csv-mapping-boards": "Cada fuella se torna un tablero propio:",
|
||||
"csv-mapping-skipped-sheets": "Fuellas que no s'importan:",
|
||||
"csv-mapping-title-required": "Tría a columna que tiene os titols d'as tarchetas.",
|
||||
"csv-mapping-list-required": "Escribe o nombre d'a lista an que van todas as tarchetas, u tría a columna que tiene a lista.",
|
||||
"invalid-import-mapping": "L'asignación de columnas no coincide con o fichero.",
|
||||
"import-many-boards": "Importar muitos tableros",
|
||||
"import-many-boards-hint": "Tría quantos fichers d'exportación d'esta ferramienta, u un .zip que los contienga: cada fichero se torna un tablero propio, importau sin asignación de miembros. Un .zip que ye ell mesmo una exportación (Vikunja, Notion, Plane, u WeKan con adchuntos) ye un solo tablero. Quan se trían fichers aquí, Importar importa ixos en cuenta d'o campo d'alto.",
|
||||
"import-one-board-per-project": "Un tablero por prochecto",
|
||||
"import-one-board-per-project-hint": "Cada carril que fería a importación (un prochecto, carpeta u lista d'a ferramienta) se torna un tablero propio. As tarchetas pai y as dependencias entre ixos tableros s'amuestran en l'informe d'importación.",
|
||||
"import-many-progress": "Importando __done__ de __total__: __name__",
|
||||
"import-many-results-heading": "Fichers importaus",
|
||||
"import-many-imported": "importau",
|
||||
"export-all-boards": "Exportar totz os tableros",
|
||||
"export-selected-boards": "Exportar os tableros seleccionaus",
|
||||
"export-all-boards-hint": "Totz os tableros que puetz exportar, en una sola descarga: Excel como un libro con una fuella por tablero con o suyo nombre, qualsequier atro formato como un .zip con un fichero por tablero.",
|
||||
"exportAllBoardsPopup-title": "Exportar totz os tableros",
|
||||
"import-json-placeholder": "Apega os tuyos datos JSON validos aquí",
|
||||
"import-csv-placeholder": "Apega os tuyos datos CSV/TSV validos aquí",
|
||||
"import-json-file": "O selecciona un fichero JSON de Trello",
|
||||
@@ -1026,17 +1026,17 @@
|
||||
"trello-import-results": "Resultaus d’a importación",
|
||||
"trello-api-credentials-required": "Introduz tanto a clau API como o token de Trello.",
|
||||
"trello-api-credentials-saved": "A clau API y o token de Trello son alzaus. Puetz listar espacios de treballo sin tornar a introducir-los.",
|
||||
"exportToTrelloPopup-title": "Export to Trello",
|
||||
"trello-export-desc": "Create a copy of this board in Trello through Trello's API, with your Trello API key and token. Swimlanes become labels named \"Swimlane: …\", which WeKan's Trello import turns back into swimlanes.",
|
||||
"trello-export-include-attachments": "Include attachments",
|
||||
"trello-export-start": "Export to Trello",
|
||||
"trello-export-open-board": "Open the board in Trello",
|
||||
"trello-export-warnings": "Not everything could be exported",
|
||||
"sandstorm-trello-access-needed": "A Sandstorm grain has no Internet access of its own. Allow this grain to reach Trello first.",
|
||||
"sandstorm-allow-trello-access": "Allow access to Trello",
|
||||
"sandstorm-trello-access-granted": "This grain may reach Trello.",
|
||||
"sandstorm-forget-access": "Forget",
|
||||
"sandstorm-trello-save-label": "Trello, for importing and exporting boards",
|
||||
"exportToTrelloPopup-title": "Exportar ta Trello",
|
||||
"trello-export-desc": "Creya una copia d'este tablero en Trello a traviés d'a API de Trello, con a tuya clau API y token de Trello. Os carrils se tornan etiquetas clamadas \"Swimlane: …\", que la importación de Trello de WeKan torna a convertir en carrils.",
|
||||
"trello-export-include-attachments": "Incluir adchuntos",
|
||||
"trello-export-start": "Exportar ta Trello",
|
||||
"trello-export-open-board": "Ubrir o tablero en Trello",
|
||||
"trello-export-warnings": "No s'ha puesto exportar tot",
|
||||
"sandstorm-trello-access-needed": "Un grain de Sandstorm no tiene acceso propio a Internet. Permite primero que este grain plegue ta Trello.",
|
||||
"sandstorm-allow-trello-access": "Permitir l'acceso ta Trello",
|
||||
"sandstorm-trello-access-granted": "Este grain puet plegar ta Trello.",
|
||||
"sandstorm-forget-access": "Ixublidar",
|
||||
"sandstorm-trello-save-label": "Trello, pa importar y exportar tableros",
|
||||
"trello-select-boards": "Selecciona t'o menos un tablero ta importar.",
|
||||
"select-all": "Seleccionar todo",
|
||||
"select-none": "No seleccionar ninguno",
|
||||
@@ -1318,9 +1318,9 @@
|
||||
"external-link-pattern-description": "Vincla automaticament os tokens estilo \"#1234\" trobatos en o texto d'a targeta ta un rastrexador de problemas externo. Dixa un campo baleiro ta desactivar-lo.",
|
||||
"external-link-pattern-prefix": "Prefixo d'o token",
|
||||
"external-link-pattern-url": "Plantilla d'URL (fe servir {number})",
|
||||
"external-link-rules": "More link rules",
|
||||
"external-link-rules-description": "One rule per line: the text to find, \" = \", and the address. Use {number} for digits and {identifier} for a word, in both, for example [{identifier}:{number}] = https://tracker.example.com/{identifier}/{number}.",
|
||||
"external-link-identifier-aliases": "Abbreviations for {identifier}, for example TK=Task, IN=Incident",
|
||||
"external-link-rules": "Mas reglas de vinclos",
|
||||
"external-link-rules-description": "Una regla por linia: o texto a mirar, \" = \", y l'adreza. Fe servir {number} pa os numeros y {identifier} pa una parola, en as dos, por eixemplo [{identifier}:{number}] = https://tracker.example.com/{identifier}/{number}.",
|
||||
"external-link-identifier-aliases": "Abreviaturas pa {identifier}, por eixemplo TK=Task, IN=Incident",
|
||||
"username": "Nombre d'usuario",
|
||||
"import-usernames": "Importar Usuarios",
|
||||
"view-it": "Verla",
|
||||
@@ -1345,13 +1345,13 @@
|
||||
"attachment-transfer-limits-title": "Límites de tamaño de fichero de adjuntos y API",
|
||||
"attachment-limits": "Límites",
|
||||
"attachment-retention": "Retención",
|
||||
"attachment-retention-days": "Delete attachments after (days)",
|
||||
"attachment-retention-days-help": "Like Delete on the card: the card history can restore them. Empty or 0: never.",
|
||||
"attachment-retention-mode": "Count the days from",
|
||||
"attachment-retention-purge": "Remove deleted attachments for good after (days)",
|
||||
"attachment-retention-purge-help": "After this they can no longer be restored. Empty or 0: never.",
|
||||
"attachment-retention-dry-run": "Dry run: only count, in the server log, change nothing",
|
||||
"attachment-retention-max-per-run": "At most this many attachments per night",
|
||||
"attachment-retention-days": "Borrar os adchuntos dimpués de (días)",
|
||||
"attachment-retention-days-help": "Como Borrar en a tarcheta: l'historial d'a tarcheta los puet restaurar. Vuedo u 0: nunca.",
|
||||
"attachment-retention-mode": "Contar os días dende",
|
||||
"attachment-retention-purge": "Eliminar pa cutio os adchuntos borraus dimpués de (días)",
|
||||
"attachment-retention-purge-help": "Dimpués d'isto ya no se pueden restaurar. Vuedo u 0: nunca.",
|
||||
"attachment-retention-dry-run": "Proba: nomás contar, en o rechistro d'o servidor, sin cambiar cosa",
|
||||
"attachment-retention-max-per-run": "Como muito tantos adchuntos por nueit",
|
||||
"attachment-transfer-limits-description": "Configura os límites máximos de tamaño de fichero ta subidas y descargas. El almacenamiento en búfer de as transferencias de la API tiene topes de seguridad independientes d'o lado d'o servidor.",
|
||||
"attachment-transfer-limits-saved": "Limites de transferencia d’adchuntos alzaus.",
|
||||
"attachment-transfer-limits-save-failed": "Error t'o alzar os límites de transferencia de adjuntos",
|
||||
@@ -1401,51 +1401,51 @@
|
||||
"error-notAuthorized": "No estás autorizado a veyer ista página.",
|
||||
"webhook-title": "Nombre d'o Webhook",
|
||||
"webhook-token": "Token (opcional ta la autenticación)",
|
||||
"webhook-payload-heading": "Webhook data",
|
||||
"webhook-payload-description": "What outgoing webhooks (Rocket.Chat, Slack, ...) send. Default keeps the inherited setting; with nothing set, webhooks send what they always have.",
|
||||
"webhook-payload-text": "Send the message text",
|
||||
"webhook-payload-fields": "Send these fields",
|
||||
"webhook-payload-field-standard": "Standard fields (WEBHOOKS_ATTRIBUTES)",
|
||||
"webhook-payload-field-ids": "IDs",
|
||||
"webhook-payload-field-names": "Names and titles",
|
||||
"webhook-payload-field-links": "Links to the card and board",
|
||||
"webhook-payload-field-actor": "Who did it",
|
||||
"webhook-payload-field-people": "The member or assignee it is about",
|
||||
"webhook-payload-field-details": "Details: comment, description, values and dates",
|
||||
"webhook-hide-identity": "Leave my name out of outgoing webhooks",
|
||||
"webhook-someone": "A member",
|
||||
"webhook-payload-heading": "Datos d'o webhook",
|
||||
"webhook-payload-description": "Que ninvian os webhooks salients (Rocket.Chat, Slack, ...). Por defecto se conserva a configuración heredada; si no i hai cosa configurada, os webhooks ninvian o que siempre han ninviau.",
|
||||
"webhook-payload-text": "Ninviar o texto d'o mensache",
|
||||
"webhook-payload-fields": "Ninviar estes campos",
|
||||
"webhook-payload-field-standard": "Campos estandar (WEBHOOKS_ATTRIBUTES)",
|
||||
"webhook-payload-field-ids": "Identificadors",
|
||||
"webhook-payload-field-names": "Nombres y titols",
|
||||
"webhook-payload-field-links": "Vinclos ta la tarcheta y o tablero",
|
||||
"webhook-payload-field-actor": "Qui lo fació",
|
||||
"webhook-payload-field-people": "O miembro u asignau a qui se refiere",
|
||||
"webhook-payload-field-details": "Detalles: comentario, descripción, valors y calendatas",
|
||||
"webhook-hide-identity": "No meter o mío nombre en os webhooks salients",
|
||||
"webhook-someone": "Un miembro",
|
||||
"notification-delivery-layout": "Diseño",
|
||||
"notification-delivery-layout-classic": "One sentence (classic)",
|
||||
"notification-delivery-layout-clear": "Clearly arranged: board, who, card in bold",
|
||||
"notification-delivery-parts": "Show",
|
||||
"notification-delivery-part-board": "Board name and link",
|
||||
"notification-delivery-part-actor": "Who did it",
|
||||
"notification-delivery-part-card": "Card title and link",
|
||||
"notification-delivery-layout-classic": "Una frase (clasico)",
|
||||
"notification-delivery-layout-clear": "Ordenau con claridat: tablero, qui, tarcheta en negreta",
|
||||
"notification-delivery-parts": "Amostrar",
|
||||
"notification-delivery-part-board": "Nombre y vinclo d'o tablero",
|
||||
"notification-delivery-part-actor": "Qui lo fació",
|
||||
"notification-delivery-part-card": "Titol y vinclo d'a tarcheta",
|
||||
"notification-delivery-part-list": "Lista",
|
||||
"notification-delivery-part-swimlane": "Carril",
|
||||
"notification-delivery-part-details": "Comment or changed value",
|
||||
"notification-delivery-part-dates": "Dates",
|
||||
"notification-delivery-part-link": "Link on its own line",
|
||||
"notification-delivery-grouping": "Grouping",
|
||||
"notification-delivery-grouping-all": "Everything due in one message",
|
||||
"notification-delivery-grouping-board": "One per board",
|
||||
"notification-delivery-grouping-card": "One per card",
|
||||
"notification-delivery-grouping-none": "Each on its own",
|
||||
"notification-delivery-schedule": "When to send",
|
||||
"notification-delivery-schedule-immediate": "Immediately",
|
||||
"notification-delivery-schedule-interval": "Every few minutes or hours",
|
||||
"notification-delivery-schedule-daily": "Once a day",
|
||||
"notification-delivery-interval": "Every",
|
||||
"notification-delivery-minutes": "__count__ minutes",
|
||||
"notification-delivery-part-details": "Comentario u valor cambiada",
|
||||
"notification-delivery-part-dates": "Calendatas",
|
||||
"notification-delivery-part-link": "Vinclo en a suya propia linia",
|
||||
"notification-delivery-grouping": "Agrupación",
|
||||
"notification-delivery-grouping-all": "Tot o pendient en un solo mensache",
|
||||
"notification-delivery-grouping-board": "Un por tablero",
|
||||
"notification-delivery-grouping-card": "Un por tarcheta",
|
||||
"notification-delivery-grouping-none": "Cadaguno por separau",
|
||||
"notification-delivery-schedule": "Quan ninviar",
|
||||
"notification-delivery-schedule-immediate": "Immediatament",
|
||||
"notification-delivery-schedule-interval": "Cada quantos minutos u horas",
|
||||
"notification-delivery-schedule-daily": "Una vegada a o día",
|
||||
"notification-delivery-interval": "Cada",
|
||||
"notification-delivery-minutes": "__count__ minutos",
|
||||
"notification-delivery-daily-time": "Hora",
|
||||
"notification-delivery-quiet": "Quiet hours (wait until they end)",
|
||||
"notification-delivery-quiet": "Horas de silencio (aguardar dica que rematen)",
|
||||
"notification-delivery-quiet-from": "Dende",
|
||||
"notification-delivery-quiet-to": "Dica",
|
||||
"notification-delivery-quiet-clear": "No quiet hours",
|
||||
"notification-delivery-timezone": "Time zone:",
|
||||
"notification-delivery-invalid": "The setting could not be saved.",
|
||||
"notification-group-card": "__count__ notifications on card __card__",
|
||||
"notification-group-board": "__count__ notifications on board __board__",
|
||||
"notification-delivery-quiet-clear": "Sin horas de silencio",
|
||||
"notification-delivery-timezone": "Zona horaria:",
|
||||
"notification-delivery-invalid": "No s'ha puesto alzar a configuración.",
|
||||
"notification-group-card": "__count__ notificacions en a tarcheta __card__",
|
||||
"notification-group-board": "__count__ notificacions en o tablero __board__",
|
||||
"outgoing-webhooks": "Webhooks salientes",
|
||||
"bidirectional-webhooks": "Webhooks de doble sentiu",
|
||||
"outgoingWebhooksPopup-title": "Webhooks salientes",
|
||||
@@ -1500,7 +1500,7 @@
|
||||
"showLabel-field-on-card": "Amostrar a etiqueta d’o campo en a minitarcheta",
|
||||
"showSum-field-on-list": "Mostrar la suma de os campos en la parte superior de la lista",
|
||||
"admin-only-field": "Visible nomás ta ra almins d'o tablero",
|
||||
"read-only-field": "Read-only: every member sees the value, only board admins change it",
|
||||
"read-only-field": "Nomás de lectura: totz os miembros veyen a valor, nomás os administradors d'o tablero la cambian",
|
||||
"yes": "Sí",
|
||||
"no": "No",
|
||||
"accounts": "Cuentas",
|
||||
@@ -1692,11 +1692,11 @@
|
||||
"r-format-auto": "Detección automática",
|
||||
"r-import-workflow-note": "Importa un flujo de trabajo visual exportado dende n8n o Node-RED. Os nodos de disparador/acción reconocidos se mapean a reglas de WeKan; os nodos no mapeados se informan.",
|
||||
"r-import-unmapped": "No s’ha puesto establir a correspondencia de __count__ linia(s)",
|
||||
"r-wrike-workflow": "Wrike workflow",
|
||||
"r-wrike-workflow-note": "A Wrike workflow is a list of statuses, each in the Active, Completed, Deferred or Cancelled group. Export writes this board's lists as one, named after the board, in the JSON Wrike's API returns for workflows: create that workflow in Wrike before importing the board's Wrike Excel export, so its statuses are applied. Import reads that JSON pasted above, from WeKan or from Wrike's GET /workflows, into the chosen board: a status without a list becomes a list, and a rule marks a card complete when it is moved into a Completed or Cancelled status and incomplete in an Active or Deferred one. Wrike's own automation rules cannot be exported from Wrike.",
|
||||
"r-export-wrike-workflow": "Export Wrike workflow",
|
||||
"r-import-wrike-workflow": "Import Wrike workflow",
|
||||
"r-import-wrike-workflow-done": "Wrike workflow \"__name__\": __lists__ list(s) and __rules__ rule(s) added, __skipped__ part(s) not read",
|
||||
"r-wrike-workflow": "Fluxo de treballo de Wrike",
|
||||
"r-wrike-workflow-note": "Un fluxo de treballo de Wrike ye una lista d'estaus, cadaguno en o grupo Active, Completed, Deferred u Cancelled. A exportación escribe as listas d'este tablero como un solo fluxo, con o nombre d'o tablero, en o JSON que torna a API de Wrike pa os fluxos de treballo: creya ixe fluxo en Wrike antes d'importar a exportación Excel de Wrike d'o tablero, ta que s'apliquen os suyos estaus. A importación leye ixe JSON apegau alto, de WeKan u d'o GET /workflows de Wrike, en o tablero eslechiu: un estau sin lista se torna una lista, y una regla marca una tarcheta como completada quan se mueve ta un estau Completed u Cancelled y como no completada en un estau Active u Deferred. As reglas d'automatización propias de Wrike no se pueden exportar dende Wrike.",
|
||||
"r-export-wrike-workflow": "Exportar o fluxo de treballo de Wrike",
|
||||
"r-import-wrike-workflow": "Importar o fluxo de treballo de Wrike",
|
||||
"r-import-wrike-workflow-done": "Fluxo de treballo de Wrike \"__name__\": __lists__ lista(s) y __rules__ regla(s) adhibidas, __skipped__ parte(s) no leyidas",
|
||||
"r-set-scheduled-triggers": "Disparadores programados",
|
||||
"r-set-button-triggers": "Botones",
|
||||
"r-when-scheduled": "Según un horario",
|
||||
@@ -1752,8 +1752,8 @@
|
||||
"set-filter": "Filtrar",
|
||||
"r-moved-to": "Movido a",
|
||||
"r-moved-from": "Movido dende",
|
||||
"r-moved-forward": "moved forward to a later list",
|
||||
"r-moved-back": "moved back to an earlier list",
|
||||
"r-moved-forward": "moviu enta debant ta una lista posterior",
|
||||
"r-moved-back": "moviu enta zaga ta una lista anterior",
|
||||
"r-archived": "Se archivó",
|
||||
"r-unarchived": "Restaurado d'o fichero",
|
||||
"r-commented": "se comentó",
|
||||
@@ -1801,8 +1801,8 @@
|
||||
"r-add-actinguser-member": "Adibir l'usuario que ha activau ista regla como miembro",
|
||||
"r-remove-all": "Borrar totz os miembros de la tarcheta",
|
||||
"r-assignee": "Asignar",
|
||||
"r-add-actinguser-assignee": "Add the user who triggered this rule as an assignee",
|
||||
"r-remove-all-assignees": "Remove all assignees from the card",
|
||||
"r-add-actinguser-assignee": "Adhibir l'usuario que activó esta regla como asignau",
|
||||
"r-remove-all-assignees": "Sacar totz os asignaus d'a tarcheta",
|
||||
"r-remove-all-labels": "Borrar totz os etiquetas de la tarcheta",
|
||||
"r-set-color": "Cambiar el color a",
|
||||
"r-checklist": "lista de verificación",
|
||||
@@ -1876,10 +1876,10 @@
|
||||
"ldap-test-connection": "Prebar a connexión LDAP",
|
||||
"ldap-test-connection-success": "Connexión con exito",
|
||||
"ldap-test-connection-error": "Ha fallau a connexión: %s",
|
||||
"ldap-sync-now": "Sync LDAP users now",
|
||||
"ldap-sync-now-done": "LDAP sync finished",
|
||||
"ldap-sync-now-error": "LDAP sync failed: %s",
|
||||
"ldap-sync-now-nothing": "Nothing to sync: turn on LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS or LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED first.",
|
||||
"ldap-sync-now": "Sincronizar agora os usuarios LDAP",
|
||||
"ldap-sync-now-done": "Sincronización LDAP rematada",
|
||||
"ldap-sync-now-error": "A sincronización LDAP ha fallau: %s",
|
||||
"ldap-sync-now-nothing": "No i hai cosa que sincronizar: activa primero LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS u LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED.",
|
||||
"oauth-providers-title": "Furnidors d’inicio de sesión OAuth",
|
||||
"oauth-providers-hint": "Cada furnidor se puede activar aquí u con a suya variable d’entorno OAUTH_*_ENABLED; una valor definida aquí anula a variable d’entorno. O secreto se alza en o servidor y nunca no s’amuestra.",
|
||||
"oauth-provider-google": "Google",
|
||||
@@ -1894,7 +1894,7 @@
|
||||
"oauth-provider-secret-set": "Bi ha un secreto definiu",
|
||||
"oauth-providers-login-style": "Estilo d’inicio de sesión",
|
||||
"oauth-providers-merge-existing-users": "Fusionar con una cuenta existent que tienga o mesmo correu electronico",
|
||||
"oauth-providers-allowed-email-domains": "Only these email domains may sign in with these providers (comma separated; empty allows all)",
|
||||
"oauth-providers-allowed-email-domains": "Nomás estes dominios de correu pueden dentrar con estes proveyedors (separaus por comas; vuedo permite totz)",
|
||||
"oauth-account-conflict": "Ya existe una cuenta con iste correu electronico y no se creyó con iste metodo d’inicio de sesión",
|
||||
"sign-in-with": "Iniciar sesión con %s",
|
||||
"login-origin-mismatch": "No se puede completar l’inicio de sesión por meyo d’unatro servicio en esta adreza. WeKan ye configurau ta __expected__, pero esta pachina s’ha ubierto en __actual__. Ubre WeKan en __expected__, u pide a l’administrador que configure ROOT_URL como __actual__.",
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Conexión fallida",
|
||||
"cloud-settings-saved": "Configuración d’almagazenamiento alzada",
|
||||
"cloud-settings-save-failed": "Error t'o alzar la configuración de almacenamiento",
|
||||
"cloud": "Boira",
|
||||
"cloud-settings-desc": "Con WeKan en Weddy, o Cloud Panel sirve muitos dominios de WeKan dende un solo servidor, cadaguno con as suyas datos. En o dominio d'o Cloud Panel, o SuperAdmin chestiona os dominios y CloudFlare.",
|
||||
"cloud-panel-domain": "Dominio d'o Cloud Panel",
|
||||
"cloud-superadmin-username": "Nombre d'usuario d'o SuperAdmin",
|
||||
"cloud-saved": "Alzau.",
|
||||
"cloud-panel-not-allowed": "O Cloud Panel ye nomás pa o SuperAdmin, en o dominio d'o Cloud Panel.",
|
||||
"cloud-customer-name": "Nombre d'o client",
|
||||
"cloud-customer-email": "Adreza de correu d'o client",
|
||||
"cloud-billing-email": "Adreza de correu de facturación",
|
||||
"cloud-start-date": "Calendata d'inicio",
|
||||
"cloud-end-date": "Calendata de fin",
|
||||
"cloudflare-api-key": "Clau API de CloudFlare",
|
||||
"cloudflare-api-key-desc": "Con una clau API de CloudFlare, un dominio que tienga la suya zona en a tuya cuenta de CloudFlare obtién un rechistro DNS con proxy y un certificau CloudFlare Origin. Os atros dominios obtienen un certificau Let's Encrypt. A clau se alza en o servidor y no se amuestra nunca mas.",
|
||||
"cloudflare-api-key-set": "S'ha alzau una clau API de CloudFlare.",
|
||||
"cloudflare-api-key-not-set": "No s'ha alzau garra clau API de CloudFlare: cada dominio obtién un certificau Let's Encrypt.",
|
||||
"move-storage-azure": "Blob d'Azure",
|
||||
"move-storage-gcs": "Almagazenamiento de Google Cloud",
|
||||
"attachment-move-storage-azure": "Mover l’adchunto a Azure Blob Storage",
|
||||
@@ -3276,7 +3291,7 @@
|
||||
"import-report-open-board": "Ubrir o tablero",
|
||||
"import-parts-instruction": "Solo se importan as partes marcadas. La misma selección se usa t'o exportar.",
|
||||
"import-wekan-file": "Importar dende un fichero de exportación .json o .zip:",
|
||||
"import-file-label": "Choose the export file, or paste its text below:",
|
||||
"import-file-label": "Tría o fichero d'exportación, u apega o suyo texto debaixo:",
|
||||
"sum-of-number-fields": "Suma d'os campos numericos personalizatos que amuestran a la parte alta d'a lista",
|
||||
"date-range-of-fields": "Rango de calendata d'os campos personalizaus de calendata marcaus ta amostrar en a parte alta d'a lista",
|
||||
"more-options": "Mas opcions",
|
||||
@@ -3303,9 +3318,9 @@
|
||||
"wip-limit-group-select-swimlane": "Triar un carril",
|
||||
"wip-limit-group-apply-swimlane": "Aplicar Carril",
|
||||
"subtask-inherit-parent-labels": "Heredar as etiquetas d'a tarcheta pai",
|
||||
"subtask-mark-done": "Mark subtask as done",
|
||||
"subtask-mark-not-done": "Mark subtask as not done",
|
||||
"subtask-done-no-permission": "You cannot change whether this subtask is done",
|
||||
"subtask-mark-done": "Marcar a subtarea como feita",
|
||||
"subtask-mark-not-done": "Marcar a subtarea como no feita",
|
||||
"subtask-done-no-permission": "No puetz cambiar si esta subtarea ye feita",
|
||||
"chart-forecast-none-remaining": "No queda cosa ta prochectar - toda tarcheta ye feita.",
|
||||
"chart-forecast-no-velocity": "__remaining__ tarchetas encara ubiertas; no i ha finalizacions recients ta prochectar una calendata.",
|
||||
"chart-forecast-projected": "A lo ritmo recient de __average__ tarchetas/semana, as __remaining__ tarchetas encara ubiertas habrían d'estar feitas enta __date__.",
|
||||
@@ -4380,52 +4395,52 @@
|
||||
"rule-email-legacy-attempt-exists": "Iste correu ya s'ha ninviau, intentau ninviar u descartau.",
|
||||
"rule-email-legacy-not-legacy": "Iste correu ya s'ha tornau a vincular.",
|
||||
"rule-email-legacy-failed": "No s'ha puesto completar ista acción. Esviella y torna a intentar-lo.",
|
||||
"stuck-sync-operation-heading": "List Sync operations that cannot be replayed",
|
||||
"stuck-sync-operation-description": "Each of these saved List Sync operations stopped part way and can no longer be finished, because its list was removed, recreated or reconfigured, or the user who started it lost write access. While one is pending, that list cannot Sync. Discard it to let the list Sync again: changes already applied stay, the remaining saved changes are never written, and the next Sync compares the list with its source again.",
|
||||
"stuck-sync-operation-list": "Board / list",
|
||||
"stuck-sync-operation-heading": "Operacions de sincronización de lista que no se pueden tornar a executar",
|
||||
"stuck-sync-operation-description": "Cadaguna d'estas operacions alzadas de sincronización de lista s'aturó a metat y ya no se puet rematar, porque a suya lista s'eliminó, se tornó a creyar u se reconfiguró, u l'usuario que la empecipió perdió l'acceso d'escritura. Mientres una ye pendient, ixa lista no se puet sincronizar. Descarta-la pa que a lista se pueda sincronizar de nuevo: os cambios ya aplicaus se conservan, os cambios alzaus que quedan nunca s'escriben, y a siguient sincronización compara de nuevo a lista con o suyo orichen.",
|
||||
"stuck-sync-operation-list": "Tablero / lista",
|
||||
"stuck-sync-operation-progress": "Progreso",
|
||||
"stuck-sync-operation-reason": "Motivo",
|
||||
"stuck-sync-operation-applied": "__applied__ of __total__ changes applied",
|
||||
"stuck-sync-operation-reason-scope-changed": "The list was removed, recreated or its Sync settings changed",
|
||||
"stuck-sync-operation-reason-access-denied": "The user who started it no longer has write access to the whole list",
|
||||
"stuck-sync-operation-reason-trigger-unknown": "Saved by an earlier WeKan version without what started it",
|
||||
"stuck-sync-operation-reason-intent-missing": "The record of who started it is missing",
|
||||
"stuck-sync-operation-reason-unknown": "Could not be checked now",
|
||||
"stuck-sync-operation-replayable-now": "It can be replayed again now and will finish by itself, so it cannot be discarded.",
|
||||
"stuck-sync-operation-applied": "__applied__ de __total__ cambios aplicaus",
|
||||
"stuck-sync-operation-reason-scope-changed": "A lista s'eliminó, se tornó a creyar u cambioron os suyos achustes de sincronización",
|
||||
"stuck-sync-operation-reason-access-denied": "L'usuario que la empecipió ya no tiene acceso d'escritura a toda a lista",
|
||||
"stuck-sync-operation-reason-trigger-unknown": "Alzada por una versión anterior de WeKan sin o que la empecipió",
|
||||
"stuck-sync-operation-reason-intent-missing": "Falta o rechistro de qui la empecipió",
|
||||
"stuck-sync-operation-reason-unknown": "No s'ha puesto comprebar agora",
|
||||
"stuck-sync-operation-replayable-now": "Agora se puet tornar a executar y rematará ella sola, asinas que no se puet descartar.",
|
||||
"stuck-sync-operation-discard": "Descartarla",
|
||||
"stuck-sync-operation-discard-confirm": "Discard this saved List Sync operation? Changes it already applied stay; the rest are never written. The list can then Sync again.",
|
||||
"stuck-sync-operation-discard-confirm": "Descartar esta operación alzada de sincronización de lista? Os cambios que ya aplicó se conservan; a resta nunca s'escribe. Dimpués a lista se puet sincronizar de nuevo.",
|
||||
"stuck-sync-operation-refresh": "Actualizar",
|
||||
"stuck-sync-operation-empty": "No List Sync operations are waiting for review.",
|
||||
"stuck-sync-operation-truncated": "Only the oldest 50 are shown. Discard some and refresh to see more.",
|
||||
"stuck-sync-operation-unavailable": "Could not load the List Sync operations. Refresh to try again.",
|
||||
"stuck-sync-operation-missing": "This operation is no longer pending.",
|
||||
"stuck-sync-operation-not-stuck": "This operation has not been found stuck, so it cannot be discarded.",
|
||||
"stuck-sync-operation-replayable": "This operation can be replayed again now and will finish by itself, so it was not discarded.",
|
||||
"stuck-sync-operation-busy": "This list is syncing right now. Try again in a moment.",
|
||||
"stuck-sync-operation-empty": "No i hai operacions de sincronización de lista aguardando revisión.",
|
||||
"stuck-sync-operation-truncated": "Nomás s'amuestran as 50 mas antigas. Descarta-ne bellas y refresca pa veyer-ne mas.",
|
||||
"stuck-sync-operation-unavailable": "No s'han puesto cargar as operacions de sincronización de lista. Refresca pa tornar-lo a intentar.",
|
||||
"stuck-sync-operation-missing": "Esta operación ya no ye pendient.",
|
||||
"stuck-sync-operation-not-stuck": "Esta operación no s'ha trobau bloqueyada, asinas que no se puet descartar.",
|
||||
"stuck-sync-operation-replayable": "Esta operación se puet tornar a executar agora y rematará ella sola, asinas que no s'ha descartau.",
|
||||
"stuck-sync-operation-busy": "Esta lista se ye sincronizando agora mesmo. Torna-lo a intentar en un momento.",
|
||||
"stuck-sync-operation-failed": "No s'ha puesto completar ista acción. Esviella y torna a intentar-lo.",
|
||||
"interrupted-import-heading": "Board imports that stopped before finishing",
|
||||
"interrupted-import-description": "Each of these board imports stopped part way - the server restarted, or the import failed - and left a partial board. An import cannot be continued, because its source file is not kept. Keep the board as it is, or discard it and import the file again. Discarding removes the board this import created and everything on it, including anything added to it since.",
|
||||
"interrupted-import-heading": "Importacions de tableros que s'aturoron antes de rematar",
|
||||
"interrupted-import-description": "Cadaguna d'estas importacions de tableros s'aturó a metat - o servidor se reinició, u a importación falló - y deixó un tablero parcial. Una importación no se puet continar, porque o suyo fichero d'orichen no se conserva. Conserva o tablero como ye, u descarta-lo y importa de nuevo o fichero. Descartar elimina o tablero que creyó esta importación y tot o que i hai, incluyiu tot o que s'i haiga adhibiu dende alavez.",
|
||||
"interrupted-import-board": "Tablero",
|
||||
"interrupted-import-progress": "Where it stopped",
|
||||
"interrupted-import-created": "Created so far",
|
||||
"interrupted-import-source": "Imported from __source__",
|
||||
"interrupted-import-state-stopped": "Stopped without finishing, at __stage__",
|
||||
"interrupted-import-state-failed": "Failed with __error__, at __stage__",
|
||||
"interrupted-import-state-discarding": "Being discarded; discard again to finish",
|
||||
"interrupted-import-progress": "An que s'aturó",
|
||||
"interrupted-import-created": "Creyau dica agora",
|
||||
"interrupted-import-source": "Importau dende __source__",
|
||||
"interrupted-import-state-stopped": "Aturau sin rematar, en __stage__",
|
||||
"interrupted-import-state-failed": "Fallau con __error__, en __stage__",
|
||||
"interrupted-import-state-discarding": "Descartando-se; descarta de nuevo pa rematar",
|
||||
"interrupted-import-scrum": "A suya fase Scrum tiene o suyo punto de control (__state__, paso __next__ de __total__). Si conservas o tablero, remata u descarta esta fase dende a recuperación d'importación Scrum d'o tablero.",
|
||||
"interrupted-import-counts": "__swimlanes__ swimlanes, __lists__ lists, __cards__ cards, __checklists__ checklists, __comments__ comments, __attachments__ attachments",
|
||||
"interrupted-import-no-board": "No board was created",
|
||||
"interrupted-import-keep": "Keep",
|
||||
"interrupted-import-counts": "__swimlanes__ carrils, __lists__ listas, __cards__ tarchetas, __checklists__ listas de comprebación, __comments__ comentarios, __attachments__ adchuntos",
|
||||
"interrupted-import-no-board": "No s'ha creyau garra tablero",
|
||||
"interrupted-import-keep": "Conservar",
|
||||
"interrupted-import-discard": "Descartarla",
|
||||
"interrupted-import-keep-confirm": "Keep this partial board as it is? Nothing is removed, and the import is no longer listed here.",
|
||||
"interrupted-import-discard-confirm": "Discard this import? The board it created and everything on it are removed permanently.",
|
||||
"interrupted-import-keep-confirm": "Conservar este tablero parcial como ye? No s'elimina cosa, y a importación ya no s'amostrará aquí.",
|
||||
"interrupted-import-discard-confirm": "Descartar esta importación? O tablero que creyó y tot o que i hai s'eliminan pa cutio.",
|
||||
"interrupted-import-refresh": "Actualizar",
|
||||
"interrupted-import-empty": "No interrupted board imports are waiting for review.",
|
||||
"interrupted-import-truncated": "Only the oldest 50 are shown. Keep or discard some and refresh to see more.",
|
||||
"interrupted-import-unavailable": "Could not load the interrupted board imports. Refresh to try again.",
|
||||
"interrupted-import-missing": "This import is no longer recorded.",
|
||||
"interrupted-import-not-interrupted": "This import is not waiting for a decision any more.",
|
||||
"interrupted-import-foreign-board": "The board with this id was not created by this import, so it was not touched.",
|
||||
"interrupted-import-empty": "No i hai importacions de tableros interrumpidas aguardando revisión.",
|
||||
"interrupted-import-truncated": "Nomás s'amuestran as 50 mas antigas. Conserva u descarta bellas y refresca pa veyer-ne mas.",
|
||||
"interrupted-import-unavailable": "No s'han puesto cargar as importacions de tableros interrumpidas. Refresca pa tornar-lo a intentar.",
|
||||
"interrupted-import-missing": "Esta importación ya no ye rechistrada.",
|
||||
"interrupted-import-not-interrupted": "Esta importación ya no aguarda garra decisión.",
|
||||
"interrupted-import-foreign-board": "O tablero con este id no lo creyó esta importación, asinas que no s'ha tocau.",
|
||||
"interrupted-import-scrum-busy": "A suya fase Scrum encara s'está escribindo u recuperando. Torna a intentar-lo en uns pocos minutos.",
|
||||
"interrupted-import-failed": "No s'ha puesto completar ista acción. Esviella y torna a intentar-lo.",
|
||||
"saml-login-not-started": "Iste inicio de sesión SAML no s'ha empecipiau en ista pestanya d'o navegador. Torna a encetar sesión.",
|
||||
@@ -4446,7 +4461,7 @@
|
||||
"header-login": "Inicio de sesión por cabeceras HTTP",
|
||||
"login-setting-clear-secret": "Eliminar a valor alzada en o panel d’administrador",
|
||||
"login-setting-after-restart": "Tiene efecto dimpués de reiniciar WeKan",
|
||||
"login-setting-env-only": "Only the server environment sets these. They are shown here read-only.",
|
||||
"login-setting-env-only": "Nomás l'entorno d'o servidor configura isto. S'amuestra aquí nomás de lectura.",
|
||||
"email-to-card": "Correu electronico a tarcheta",
|
||||
"email-to-card-description": "Un correu electronico ninviau a l'adreza d'o tuyo tablero se fa una tarcheta en ixe tablero.",
|
||||
"email-to-card-admin-enabled": "Recibir correus electronicos en tarchetas",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "فشل الاتصال",
|
||||
"cloud-settings-saved": "تم حفظ إعدادات التخزين",
|
||||
"cloud-settings-save-failed": "فشل حفظ إعدادات التخزين",
|
||||
"cloud": "السحابة",
|
||||
"cloud-settings-desc": "مع WeKan في Weddy، تخدم Cloud Panel العديد من نطاقات WeKan من خادم واحد، ولكل منها بياناته الخاصة. في نطاق Cloud Panel، يدير SuperAdmin النطاقات وCloudFlare.",
|
||||
"cloud-panel-domain": "نطاق Cloud Panel",
|
||||
"cloud-superadmin-username": "اسم مستخدم SuperAdmin",
|
||||
"cloud-saved": "تم الحفظ.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel مخصصة لـ SuperAdmin فقط، على نطاق Cloud Panel.",
|
||||
"cloud-customer-name": "اسم العميل",
|
||||
"cloud-customer-email": "عنوان البريد الإلكتروني للعميل",
|
||||
"cloud-billing-email": "عنوان البريد الإلكتروني للفوترة",
|
||||
"cloud-start-date": "تاريخ البدء",
|
||||
"cloud-end-date": "تاريخ الانتهاء",
|
||||
"cloudflare-api-key": "مفتاح CloudFlare API",
|
||||
"cloudflare-api-key-desc": "باستخدام مفتاح CloudFlare API، يحصل النطاق الذي توجد منطقته في حساب CloudFlare الخاص بك على سجل DNS عبر الوكيل وشهادة CloudFlare Origin. تحصل النطاقات الأخرى على شهادة Let's Encrypt. يُحفظ المفتاح على الخادم ولا يُعرض مرة أخرى أبدًا.",
|
||||
"cloudflare-api-key-set": "تم حفظ مفتاح CloudFlare API.",
|
||||
"cloudflare-api-key-not-set": "لم يتم حفظ أي مفتاح CloudFlare API: يحصل كل نطاق على شهادة Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "نقل المرفق إلى Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "فشل الاتصال",
|
||||
"cloud-settings-saved": "تم حفظ إعدادات التخزين",
|
||||
"cloud-settings-save-failed": "فشل حفظ إعدادات التخزين",
|
||||
"cloud": "السحابة",
|
||||
"cloud-settings-desc": "مع WeKan في Weddy، تخدم Cloud Panel العديد من نطاقات WeKan من خادم واحد، ولكل منها بياناته الخاصة. في نطاق Cloud Panel، يدير SuperAdmin النطاقات وCloudFlare.",
|
||||
"cloud-panel-domain": "نطاق Cloud Panel",
|
||||
"cloud-superadmin-username": "اسم مستخدم SuperAdmin",
|
||||
"cloud-saved": "تم الحفظ.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel مخصصة لـ SuperAdmin فقط، على نطاق Cloud Panel.",
|
||||
"cloud-customer-name": "اسم العميل",
|
||||
"cloud-customer-email": "عنوان البريد الإلكتروني للعميل",
|
||||
"cloud-billing-email": "عنوان البريد الإلكتروني للفوترة",
|
||||
"cloud-start-date": "تاريخ البدء",
|
||||
"cloud-end-date": "تاريخ الانتهاء",
|
||||
"cloudflare-api-key": "مفتاح CloudFlare API",
|
||||
"cloudflare-api-key-desc": "باستخدام مفتاح CloudFlare API، يحصل النطاق الذي توجد منطقته في حساب CloudFlare الخاص بك على سجل DNS عبر الوكيل وشهادة CloudFlare Origin. تحصل النطاقات الأخرى على شهادة Let's Encrypt. يُحفظ المفتاح على الخادم ولا يُعرض مرة أخرى أبدًا.",
|
||||
"cloudflare-api-key-set": "تم حفظ مفتاح CloudFlare API.",
|
||||
"cloudflare-api-key-not-set": "لم يتم حفظ أي مفتاح CloudFlare API: يحصل كل نطاق على شهادة Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "نقل المرفق إلى Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "فشل الاتصال",
|
||||
"cloud-settings-saved": "تم حفظ إعدادات التخزين",
|
||||
"cloud-settings-save-failed": "فشل حفظ إعدادات التخزين",
|
||||
"cloud": "السحابة",
|
||||
"cloud-settings-desc": "مع WeKan في Weddy، تخدم Cloud Panel العديد من نطاقات WeKan من خادم واحد، ولكل منها بياناته الخاصة. في نطاق Cloud Panel، يدير SuperAdmin النطاقات وCloudFlare.",
|
||||
"cloud-panel-domain": "نطاق Cloud Panel",
|
||||
"cloud-superadmin-username": "اسم مستخدم SuperAdmin",
|
||||
"cloud-saved": "تم الحفظ.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel مخصصة لـ SuperAdmin فقط، على نطاق Cloud Panel.",
|
||||
"cloud-customer-name": "اسم العميل",
|
||||
"cloud-customer-email": "عنوان البريد الإلكتروني للعميل",
|
||||
"cloud-billing-email": "عنوان البريد الإلكتروني للفوترة",
|
||||
"cloud-start-date": "تاريخ البدء",
|
||||
"cloud-end-date": "تاريخ الانتهاء",
|
||||
"cloudflare-api-key": "مفتاح CloudFlare API",
|
||||
"cloudflare-api-key-desc": "باستخدام مفتاح CloudFlare API، يحصل النطاق الذي توجد منطقته في حساب CloudFlare الخاص بك على سجل DNS عبر الوكيل وشهادة CloudFlare Origin. تحصل النطاقات الأخرى على شهادة Let's Encrypt. يُحفظ المفتاح على الخادم ولا يُعرض مرة أخرى أبدًا.",
|
||||
"cloudflare-api-key-set": "تم حفظ مفتاح CloudFlare API.",
|
||||
"cloudflare-api-key-not-set": "لم يتم حفظ أي مفتاح CloudFlare API: يحصل كل نطاق على شهادة Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "نقل المرفق إلى Azure Blob Storage",
|
||||
|
||||
+619
-604
File diff suppressed because it is too large.
Load diff
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "সংযোগ বিফল",
|
||||
"cloud-settings-saved": "সংৰক্ষণৰ ছেটিংছ সংৰক্ষণ কৰা হ'ল",
|
||||
"cloud-settings-save-failed": "সংৰক্ষণৰ ছেটিংছ সংৰক্ষণ কৰিব পৰা নগ'ল",
|
||||
"cloud": "ডাৱৰ",
|
||||
"cloud-settings-desc": "Weddyত WeKanৰ সৈতে, Cloud Panel এ এটা ছাৰ্ভাৰৰ পৰা বহুতো WeKan ড'মেইন সেৱা আগবঢ়ায়, প্ৰতিটোৰে নিজা ডাটা থাকে। Cloud Panel ড'মেইনত SuperAdmin এ ড'মেইনসমূহ আৰু CloudFlare পৰিচালনা কৰে।",
|
||||
"cloud-panel-domain": "Cloud Panel ড'মেইন",
|
||||
"cloud-superadmin-username": "SuperAdminৰ ব্যৱহাৰকাৰীৰ নাম",
|
||||
"cloud-saved": "সংৰক্ষণ কৰা হ'ল।",
|
||||
"cloud-panel-not-allowed": "Cloud Panel কেৱল SuperAdminৰ বাবে, Cloud Panel ড'মেইনত।",
|
||||
"cloud-customer-name": "গ্ৰাহকৰ নাম",
|
||||
"cloud-customer-email": "গ্ৰাহকৰ ইমেইল ঠিকনা",
|
||||
"cloud-billing-email": "বিলিং ইমেইল ঠিকনা",
|
||||
"cloud-start-date": "আৰম্ভণিৰ তাৰিখ",
|
||||
"cloud-end-date": "শেষৰ তাৰিখ",
|
||||
"cloudflare-api-key": "CloudFlare API চাবি",
|
||||
"cloudflare-api-key-desc": "CloudFlare API চাবিৰ সৈতে, যি ড'মেইনৰ জ'ন আপোনাৰ CloudFlare একাউণ্টত আছে, সেইটোৱে প্ৰক্সি কৰা DNS ৰেকৰ্ড আৰু CloudFlare Origin প্ৰমাণপত্ৰ পায়। অন্য ড'মেইনে Let's Encrypt প্ৰমাণপত্ৰ পায়। চাবিটো ছাৰ্ভাৰত সংৰক্ষণ কৰা হয় আৰু আৰু কেতিয়াও দেখুওৱা নহয়।",
|
||||
"cloudflare-api-key-set": "এটা CloudFlare API চাবি সংৰক্ষণ কৰা হৈছে।",
|
||||
"cloudflare-api-key-not-set": "কোনো CloudFlare API চাবি সংৰক্ষণ কৰা হোৱা নাই: প্ৰতিটো ড'মেইনে Let's Encrypt প্ৰমাণপত্ৰ পায়।",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "সংলগ্নক Azure Blob Storage-লৈ স্থানান্তৰ কৰক",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Falló la conexón",
|
||||
"cloud-settings-saved": "Guardáronse les preferencies d'almacenamientu",
|
||||
"cloud-settings-save-failed": "Nun se pudieron guardar les preferencies d'almacenamientu",
|
||||
"cloud": "Ñube",
|
||||
"cloud-settings-desc": "Con WeKan en Weddy, el Cloud Panel sirve munchos dominios de WeKan dende un únicu sirvidor, cada ún colos sos datos. Nel dominiu del Cloud Panel, el SuperAdmin xestiona los dominios y CloudFlare.",
|
||||
"cloud-panel-domain": "Dominiu del Cloud Panel",
|
||||
"cloud-superadmin-username": "Nome d'usuariu del SuperAdmin",
|
||||
"cloud-saved": "Guardóse.",
|
||||
"cloud-panel-not-allowed": "El Cloud Panel ye namái pal SuperAdmin, nel dominiu del Cloud Panel.",
|
||||
"cloud-customer-name": "Nome del veceru",
|
||||
"cloud-customer-email": "Direición de corréu del veceru",
|
||||
"cloud-billing-email": "Direición de corréu de facturación",
|
||||
"cloud-start-date": "Data d'aniciu",
|
||||
"cloud-end-date": "Data de fin",
|
||||
"cloudflare-api-key": "Clave API de CloudFlare",
|
||||
"cloudflare-api-key-desc": "Con una clave API de CloudFlare, un dominiu que tenga la so zona na to cuenta de CloudFlare consigue un rexistru DNS con proxy y un certificáu CloudFlare Origin. Los otros dominios consiguen un certificáu Let's Encrypt. La clave guárdase nel sirvidor y enxamás nun se vuelve amosar.",
|
||||
"cloudflare-api-key-set": "Guardóse una clave API de CloudFlare.",
|
||||
"cloudflare-api-key-not-set": "Nun se guardó nenguna clave API de CloudFlare: cada dominiu consigue un certificáu Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Mover l'adjuntu a Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Connection jani phuqhata",
|
||||
"cloud-settings-saved": "Storage wakichäwinaka saved",
|
||||
"cloud-settings-save-failed": "jani phuqhata ukaru imaña storage wakichäwinaka",
|
||||
"cloud": "Qinaya",
|
||||
"cloud-settings-desc": "WeKan Weddy ukan utjipanxa, Cloud Panel ukax walja WeKan dominionak mä servidorat luqtaraki, sapakis pachpa datonakanïtap. Cloud Panel Dominio ukanxa, SuperAdmin ukaw dominionak CloudFlare ukamp apnaqaraki.",
|
||||
"cloud-panel-domain": "Cloud Panel Dominio",
|
||||
"cloud-superadmin-username": "SuperAdmin apnaqirin sutipa",
|
||||
"cloud-saved": "Imatäxiwa.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel ukax SuperAdmin ukatakikiw, Cloud Panel Dominio ukana.",
|
||||
"cloud-customer-name": "Alakipirin sutipa",
|
||||
"cloud-customer-email": "Alakipirin email chiqapa",
|
||||
"cloud-billing-email": "Payllañ email chiqa",
|
||||
"cloud-start-date": "Qallta uru",
|
||||
"cloud-end-date": "Tukuya uru",
|
||||
"cloudflare-api-key": "CloudFlare API llawi",
|
||||
"cloudflare-api-key-desc": "CloudFlare API llawimpixa, CloudFlare cuentaman zonap utjki uka dominiox proxy DNS qillqatampi CloudFlare Origin certificadompi katuqi. Yaqha dominionakax Let's Encrypt certificado katuqapxi. Llawix servidoran imatawa, janiw mayamp uñachayatäkiti.",
|
||||
"cloudflare-api-key-set": "CloudFlare API llawiw imataxi.",
|
||||
"cloudflare-api-key-not-set": "Janiw kuna CloudFlare API llawis imatäkiti: sapa dominiox Let's Encrypt certificado katuqi.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "unxtayaña apkatata ukaru Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Bağlantı uğursuz oldu",
|
||||
"cloud-settings-saved": "Saxlama parametrləri saxlanıldı",
|
||||
"cloud-settings-save-failed": "Saxlama parametrləri saxlanıla bilmədi",
|
||||
"cloud": "Bulud",
|
||||
"cloud-settings-desc": "Weddy-də WeKan ilə Cloud Panel bir serverdən çoxlu WeKan domenlərinə xidmət edir, hər birinin öz məlumatları var. Cloud Panel domenində SuperAdmin domenləri və CloudFlare-i idarə edir.",
|
||||
"cloud-panel-domain": "Cloud Panel domeni",
|
||||
"cloud-superadmin-username": "SuperAdmin istifadəçi adı",
|
||||
"cloud-saved": "Saxlanıldı.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel yalnız SuperAdmin üçündür, Cloud Panel domenində.",
|
||||
"cloud-customer-name": "Müştərinin adı",
|
||||
"cloud-customer-email": "Müştərinin e-poçt ünvanı",
|
||||
"cloud-billing-email": "Hesab-faktura e-poçt ünvanı",
|
||||
"cloud-start-date": "Başlama tarixi",
|
||||
"cloud-end-date": "Bitmə tarixi",
|
||||
"cloudflare-api-key": "CloudFlare API açarı",
|
||||
"cloudflare-api-key-desc": "CloudFlare API açarı ilə zonası CloudFlare hesabınızda olan domen proksilənmiş DNS qeydi və CloudFlare Origin sertifikatı alır. Digər domenlər Let's Encrypt sertifikatı alır. Açar serverdə saxlanılır və bir daha göstərilmir.",
|
||||
"cloudflare-api-key-set": "CloudFlare API açarı saxlanılıb.",
|
||||
"cloudflare-api-key-not-set": "Heç bir CloudFlare API açarı saxlanılmayıb: hər domen Let's Encrypt sertifikatı alır.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Qoşmanı Azure Blob Storage-ə köçür",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Bağlantı uğursuz oldu",
|
||||
"cloud-settings-saved": "Saxlama parametrləri saxlanıldı",
|
||||
"cloud-settings-save-failed": "Saxlama parametrləri saxlanıla bilmədi",
|
||||
"cloud": "Bulud",
|
||||
"cloud-settings-desc": "Weddy-də WeKan ilə Cloud Panel bir serverdən çoxlu WeKan domenlərinə xidmət edir, hər birinin öz məlumatları var. Cloud Panel domenində SuperAdmin domenləri və CloudFlare-i idarə edir.",
|
||||
"cloud-panel-domain": "Cloud Panel domeni",
|
||||
"cloud-superadmin-username": "SuperAdmin istifadəçi adı",
|
||||
"cloud-saved": "Saxlanıldı.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel yalnız SuperAdmin üçündür, Cloud Panel domenində.",
|
||||
"cloud-customer-name": "Müştərinin adı",
|
||||
"cloud-customer-email": "Müştərinin e-poçt ünvanı",
|
||||
"cloud-billing-email": "Hesab-faktura e-poçt ünvanı",
|
||||
"cloud-start-date": "Başlama tarixi",
|
||||
"cloud-end-date": "Bitmə tarixi",
|
||||
"cloudflare-api-key": "CloudFlare API açarı",
|
||||
"cloudflare-api-key-desc": "CloudFlare API açarı ilə zonası CloudFlare hesabınızda olan domen proksilənmiş DNS qeydi və CloudFlare Origin sertifikatı alır. Digər domenlər Let's Encrypt sertifikatı alır. Açar serverdə saxlanılır və bir daha göstərilmir.",
|
||||
"cloudflare-api-key-set": "CloudFlare API açarı saxlanılıb.",
|
||||
"cloudflare-api-key-not-set": "Heç bir CloudFlare API açarı saxlanılmayıb: hər domen Let's Encrypt sertifikatı alır.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Qoşmanı Azure Blob Storage-ə köçür",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Bağlantı uğursuz oldu",
|
||||
"cloud-settings-saved": "Saxlama parametrləri saxlanıldı",
|
||||
"cloud-settings-save-failed": "Saxlama parametrləri saxlanıla bilmədi",
|
||||
"cloud": "Bulud",
|
||||
"cloud-settings-desc": "Weddy-də WeKan ilə Cloud Panel bir serverdən çoxlu WeKan domenlərinə xidmət edir, hər birinin öz məlumatları var. Cloud Panel domenində SuperAdmin domenləri və CloudFlare-i idarə edir.",
|
||||
"cloud-panel-domain": "Cloud Panel domeni",
|
||||
"cloud-superadmin-username": "SuperAdmin istifadəçi adı",
|
||||
"cloud-saved": "Saxlanıldı.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel yalnız SuperAdmin üçündür, Cloud Panel domenində.",
|
||||
"cloud-customer-name": "Müştərinin adı",
|
||||
"cloud-customer-email": "Müştərinin e-poçt ünvanı",
|
||||
"cloud-billing-email": "Hesab-faktura e-poçt ünvanı",
|
||||
"cloud-start-date": "Başlama tarixi",
|
||||
"cloud-end-date": "Bitmə tarixi",
|
||||
"cloudflare-api-key": "CloudFlare API açarı",
|
||||
"cloudflare-api-key-desc": "CloudFlare API açarı ilə zonası CloudFlare hesabınızda olan domen proksilənmiş DNS qeydi və CloudFlare Origin sertifikatı alır. Digər domenlər Let's Encrypt sertifikatı alır. Açar serverdə saxlanılır və bir daha göstərilmir.",
|
||||
"cloudflare-api-key-set": "CloudFlare API açarı saxlanılıb.",
|
||||
"cloudflare-api-key-not-set": "Heç bir CloudFlare API açarı saxlanılmayıb: hər domen Let's Encrypt sertifikatı alır.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Qoşmanı Azure Blob Storage-ə köçür",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Тоташыу уңышһыҙ",
|
||||
"cloud-settings-saved": "Һаҡлау көйләүҙәре һаҡланды",
|
||||
"cloud-settings-save-failed": "Һаҡлау көйләүҙәрен һаҡлап булманы",
|
||||
"cloud": "Болот",
|
||||
"cloud-settings-desc": "Weddy-ҙағы WeKan менән Cloud Panel бер серверҙан күп WeKan домендарына хеҙмәт күрһәтә, һәр береһенең үҙ мәғлүмәттәре бар. Cloud Panel доменында SuperAdmin домендарҙы һәм CloudFlare-ҙы идара итә.",
|
||||
"cloud-panel-domain": "Cloud Panel домены",
|
||||
"cloud-superadmin-username": "SuperAdmin ҡулланыусы исеме",
|
||||
"cloud-saved": "Һаҡланды.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel тик SuperAdmin өсөн генә, Cloud Panel доменында.",
|
||||
"cloud-customer-name": "Клиенттың исеме",
|
||||
"cloud-customer-email": "Клиенттың электрон почта адресы",
|
||||
"cloud-billing-email": "Иҫәп-фактура өсөн электрон почта адресы",
|
||||
"cloud-start-date": "Башланыу көнө",
|
||||
"cloud-end-date": "Тамамланыу көнө",
|
||||
"cloudflare-api-key": "CloudFlare API асҡысы",
|
||||
"cloudflare-api-key-desc": "CloudFlare API асҡысы менән зонаһы һеҙҙең CloudFlare иҫәп яҙмаһында булған домен прокси аша DNS яҙмаһын һәм CloudFlare Origin сертификатын ала. Башҡа домендар Let's Encrypt сертификатын ала. Асҡыс серверҙа һаҡлана һәм бүтән бер ҡасан да күрһәтелмәй.",
|
||||
"cloudflare-api-key-set": "CloudFlare API асҡысы һаҡланған.",
|
||||
"cloudflare-api-key-not-set": "Бер ниндәй ҙә CloudFlare API асҡысы һаҡланмаған: һәр домен Let's Encrypt сертификатын ала.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Ҡушымтаны Azure Blob Storage-ға күсерергә",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Падключэнне не ўдалося",
|
||||
"cloud-settings-saved": "Налады сховішча захаваны",
|
||||
"cloud-settings-save-failed": "Не ўдалося захаваць налады сховішча",
|
||||
"cloud": "Воблака",
|
||||
"cloud-settings-desc": "З WeKan у Weddy Cloud Panel абслугоўвае шмат даменаў WeKan з аднаго сервера, кожны са сваімі дадзенымі. На дамене Cloud Panel SuperAdmin кіруе даменамі і CloudFlare.",
|
||||
"cloud-panel-domain": "Дамен Cloud Panel",
|
||||
"cloud-superadmin-username": "Імя карыстальніка SuperAdmin",
|
||||
"cloud-saved": "Захавана.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel толькі для SuperAdmin, на дамене Cloud Panel.",
|
||||
"cloud-customer-name": "Імя кліента",
|
||||
"cloud-customer-email": "Адрас электроннай пошты кліента",
|
||||
"cloud-billing-email": "Адрас электроннай пошты для рахункаў",
|
||||
"cloud-start-date": "Дата пачатку",
|
||||
"cloud-end-date": "Дата заканчэння",
|
||||
"cloudflare-api-key": "Ключ CloudFlare API",
|
||||
"cloudflare-api-key-desc": "З ключом CloudFlare API дамен, зона якога знаходзіцца ў вашым уліковым запісе CloudFlare, атрымлівае праксіраваны запіс DNS і сертыфікат CloudFlare Origin. Іншыя дамены атрымліваюць сертыфікат Let's Encrypt. Ключ захоўваецца на серверы і больш ніколі не паказваецца.",
|
||||
"cloudflare-api-key-set": "Ключ CloudFlare API захаваны.",
|
||||
"cloudflare-api-key-not-set": "Ключ CloudFlare API не захаваны: кожны дамен атрымлівае сертыфікат Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Перамясціць укладанне ў Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Неуспешна връзка",
|
||||
"cloud-settings-saved": "Настройките на хранилището са запазени",
|
||||
"cloud-settings-save-failed": "Неуспешно запазване на настройките на хранилището",
|
||||
"cloud": "Облак",
|
||||
"cloud-settings-desc": "С WeKan в Weddy Cloud Panel обслужва много домейни на WeKan от един сървър, всеки със свои собствени данни. На домейна на Cloud Panel SuperAdmin управлява домейните и CloudFlare.",
|
||||
"cloud-panel-domain": "Домейн на Cloud Panel",
|
||||
"cloud-superadmin-username": "Потребителско име на SuperAdmin",
|
||||
"cloud-saved": "Запазено.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel е само за SuperAdmin, на домейна на Cloud Panel.",
|
||||
"cloud-customer-name": "Име на клиента",
|
||||
"cloud-customer-email": "Имейл адрес на клиента",
|
||||
"cloud-billing-email": "Имейл адрес за фактуриране",
|
||||
"cloud-start-date": "Начална дата",
|
||||
"cloud-end-date": "Крайна дата",
|
||||
"cloudflare-api-key": "CloudFlare API ключ",
|
||||
"cloudflare-api-key-desc": "С CloudFlare API ключ домейн, чиято зона е във вашия CloudFlare акаунт, получава проксиран DNS запис и CloudFlare Origin сертификат. Другите домейни получават Let's Encrypt сертификат. Ключът се запазва на сървъра и никога повече не се показва.",
|
||||
"cloudflare-api-key-set": "CloudFlare API ключ е запазен.",
|
||||
"cloudflare-api-key-not-set": "Няма запазен CloudFlare API ключ: всеки домейн получава Let's Encrypt сертификат.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Премести прикачения файл в Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "जोड़ असफल भइल",
|
||||
"cloud-settings-saved": "भंडार सेटिंग सहेज दिहल गइल",
|
||||
"cloud-settings-save-failed": "भंडार सेटिंग सहेजल असफल भइल",
|
||||
"cloud": "बादल",
|
||||
"cloud-settings-desc": "Weddy में WeKan के साथ, Cloud Panel एगो सर्वर से बहुत सारा WeKan डोमेन चलावेला, हर एक के आपन डेटा होला। Cloud Panel डोमेन पर SuperAdmin डोमेन आ CloudFlare के सँभारेला।",
|
||||
"cloud-panel-domain": "Cloud Panel डोमेन",
|
||||
"cloud-superadmin-username": "SuperAdmin उपयोगकर्ता नाम",
|
||||
"cloud-saved": "सहेजा गइल।",
|
||||
"cloud-panel-not-allowed": "Cloud Panel खाली SuperAdmin खातिर बा, Cloud Panel डोमेन पर।",
|
||||
"cloud-customer-name": "ग्राहक के नाम",
|
||||
"cloud-customer-email": "ग्राहक के ईमेल पता",
|
||||
"cloud-billing-email": "बिलिंग ईमेल पता",
|
||||
"cloud-start-date": "शुरुआत के तारीख",
|
||||
"cloud-end-date": "अंत के तारीख",
|
||||
"cloudflare-api-key": "CloudFlare API कुंजी",
|
||||
"cloudflare-api-key-desc": "CloudFlare API कुंजी से, जवना डोमेन के ज़ोन रउआ CloudFlare खाता में बा, ओकरा प्रॉक्सी वाला DNS रिकॉर्ड आ CloudFlare Origin प्रमाणपत्र मिलेला। दोसर डोमेन के Let's Encrypt प्रमाणपत्र मिलेला। कुंजी सर्वर पर सहेजल जाला आ फेर कबो ना देखावल जाला।",
|
||||
"cloudflare-api-key-set": "एगो CloudFlare API कुंजी सहेजल बा।",
|
||||
"cloudflare-api-key-not-set": "कवनो CloudFlare API कुंजी सहेजल नइखे: हर डोमेन के Let's Encrypt प्रमाणपत्र मिलेला।",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "संलग्नक Azure Blob Storage में सरकाईं",
|
||||
|
||||
+238
-223
@@ -227,20 +227,20 @@
|
||||
"attachment-soft-delete-pop": "Atajmen ia i aot long kad ia, be fael blong hem i stap yet. Eni man we i save jenisim kad ia i save putum bak long histri blong kad.",
|
||||
"attachmentDeletePopup-title": "Diletem Fael we i joen?",
|
||||
"attachments": "Ol atajmen",
|
||||
"attach-card": "Attach card",
|
||||
"attachCardPopup-title": "Attach card",
|
||||
"attach-card-hint": "Paste a card link (Copy card link on the other card), or type a card title on this board",
|
||||
"attached-card-unavailable": "An attached card you cannot see",
|
||||
"detach-card": "Remove attached card",
|
||||
"attach-card-self": "A card cannot be attached to itself",
|
||||
"attach-card-limit": "This card already has as many attached cards as it can have",
|
||||
"attach-card-not-found": "No card you can see was found for that link",
|
||||
"attach-card": "Joenem kad",
|
||||
"attachCardPopup-title": "Joenem kad",
|
||||
"attach-card-hint": "Pestem link blong wan kad (Kopiem link blong kad long narafala kad), o raetem nem blong wan kad long bod ia",
|
||||
"attached-card-unavailable": "Wan kad we i joen we yu no save luk",
|
||||
"detach-card": "Tekemaot kad we i joen",
|
||||
"attach-card-self": "Yu no save joenem wan kad long hem wan",
|
||||
"attach-card-limit": "Kad ia i gat finis olgeta kad we i save joen long hem",
|
||||
"attach-card-not-found": "I no gat wan kad we yu save luk long link ia",
|
||||
"auto-watch": "Lukluk ol bod otomatik taem oli mekem",
|
||||
"avatar-too-big": "avatar i bigfala tumas (__size__ i maks)",
|
||||
"back": "Gobak",
|
||||
"board-change-color": "Jenisim kala",
|
||||
"board-announcement": "Board announcement",
|
||||
"board-announcement-enabled": "Show it to everyone on this board",
|
||||
"board-announcement": "Toktok blong bod",
|
||||
"board-announcement-enabled": "Soem long evriwan long bod ia",
|
||||
"board-change-background-image": "Jenisim Pikja blong Baekgraon",
|
||||
"board-background-image-url": "URL blong Pikja blong Baekgraon",
|
||||
"add-background-image": "Adem Pikja blong Baekgraon",
|
||||
@@ -284,7 +284,7 @@
|
||||
"zoom-level": "Saes blong lukluk",
|
||||
"enter-zoom-level": "Putum saes blong lukluk (50-300%):",
|
||||
"board-view-cal": "Kalenda",
|
||||
"board-view-calendar-mode": "Calendar Mode",
|
||||
"board-view-calendar-mode": "Fasin blong kalenda",
|
||||
"board-view-multiboard-cal": "Kalenda blong plante bod",
|
||||
"board-view-swimlanes": "Ol rod",
|
||||
"board-view-collapse": "Foldem",
|
||||
@@ -686,32 +686,32 @@
|
||||
"chooseBoardSourcePopup-title": "Karem i kam bod",
|
||||
"createLabelPopup-title": "Mekem Mak",
|
||||
"createCustomField": "Mekem Fil",
|
||||
"custom-field-links": "Link fields to another card",
|
||||
"custom-field-links-hint": "Custom fields with the same name and type on both cards are linked: when a value changes on one card, it is copied to the other card. Fields that only one card has are left alone.",
|
||||
"custom-field-links-none": "This card's custom fields are not linked to any card.",
|
||||
"custom-field-link-unavailable": "A linked card you cannot see",
|
||||
"custom-field-link-fields": "Linked fields:",
|
||||
"custom-field-link-no-fields": "No fields with the same name and type yet",
|
||||
"custom-field-link-inactive": "Paused: the person who made this link can no longer edit both cards, or a card is archived.",
|
||||
"custom-field-unlink": "Unlink",
|
||||
"custom-field-link-target": "Card link (Copy card link), or search this board's cards",
|
||||
"custom-field-link-direction": "Direction",
|
||||
"custom-field-link-both": "Both ways: changes on either card go to the other",
|
||||
"custom-field-link-send": "One way: changes on this card go to the main card",
|
||||
"custom-field-link-sends": "Changes on this card go to that card",
|
||||
"custom-field-link-receives": "Changes on that card come to this card",
|
||||
"custom-field-link-add": "Link fields",
|
||||
"field-link-self": "A card cannot be linked to itself.",
|
||||
"field-link-limit": "This card, or that card, already has the most linked cards it can have.",
|
||||
"field-link-archived": "An archived card cannot be linked.",
|
||||
"field-link-invalid": "That is not a card link.",
|
||||
"field-link-not-found": "Card not found.",
|
||||
"field-link-not-allowed": "You need to be able to edit both cards.",
|
||||
"cardCustomFieldLinksPopup-title": "Linked custom fields",
|
||||
"custom-field-links": "Joenem ol fil long narafala kad",
|
||||
"custom-field-links-hint": "Ol fil blong yu we i gat sem nem mo sem kaen long tufala kad i joen: taem wan valiu i jenis long wan kad, i kopi i go long narafala kad. Ol fil we wan kad nomo i gat, oli stap olsem.",
|
||||
"custom-field-links-none": "Ol fil blong yu long kad ia oli no joen long eni kad.",
|
||||
"custom-field-link-unavailable": "Wan kad we i joen we yu no save luk",
|
||||
"custom-field-link-fields": "Ol fil we i joen:",
|
||||
"custom-field-link-no-fields": "I no gat fil yet we i gat sem nem mo sem kaen",
|
||||
"custom-field-link-inactive": "I stop smol: man we i mekem link ia i no save jenisim tufala kad bakegen, o wan kad i stap long akaev.",
|
||||
"custom-field-unlink": "Brekem link",
|
||||
"custom-field-link-target": "Link blong kad (Kopiem link blong kad), o lukaotem ol kad blong bod ia",
|
||||
"custom-field-link-direction": "Rod we i go",
|
||||
"custom-field-link-both": "Tu rod: ol jenis long eni kad i go long narafala",
|
||||
"custom-field-link-send": "Wan rod: ol jenis long kad ia i go long bigfala kad",
|
||||
"custom-field-link-sends": "Ol jenis long kad ia i go long kad ia we i stap longwe",
|
||||
"custom-field-link-receives": "Ol jenis long narafala kad i kam long kad ia",
|
||||
"custom-field-link-add": "Joenem ol fil",
|
||||
"field-link-self": "Yu no save joenem wan kad long hem wan.",
|
||||
"field-link-limit": "Kad ia, o narafala kad, i gat finis moa kad we i joen we i save gat.",
|
||||
"field-link-archived": "Yu no save joenem wan kad we i stap long akaev.",
|
||||
"field-link-invalid": "Hemia i no link blong kad.",
|
||||
"field-link-not-found": "Kad i no stap.",
|
||||
"field-link-not-allowed": "Yu mas gat raet blong jenisim tufala kad.",
|
||||
"cardCustomFieldLinksPopup-title": "Ol fil blong yu we i joen",
|
||||
"createCustomFieldPopup-title": "Mekem Fil",
|
||||
"current": "naoia",
|
||||
"custom-color": "Spesel kala",
|
||||
"cards-use-list-color": "Cards without a colour of their own use this colour",
|
||||
"cards-use-list-color": "Ol kad we oli no gat kala blong olgeta oli yusum kala ia",
|
||||
"custom-field-delete-pop": "Yu no save tekem i kambak. Samting ia bae i tekemaot spesel fil ia long evri kad mo rablaotem histri blong hem.",
|
||||
"custom-field-checkbox": "Bokis blong tikim",
|
||||
"custom-field-currency": "Mane",
|
||||
@@ -916,13 +916,13 @@
|
||||
"import-board-instruction-focalboard": "Openem fael .boardarchive we i stap long zip mo putum insaed blong board.jsonl blong bod, wetem laen blong vesin o nogat. Propeti blong mekem ol grup long lukluk blong bod i kam ol lis, ol narafala propeti blong jusum oli kam ol mak, ol blok blong teks mo ol bokis blong tikim oli kam diskripsen mo lis blong jekem, mo ol komen oli stap yet. Ol pikja mo ol fael we oli joenem oli no kam insaed.",
|
||||
"import-board-instruction-todoist": "Long Todoist, openem menyu blong projek mo jusum Export as a template, afta putum kopi blong CSV fael. Ol seksen oli kam ol lis, ol wok oli kam ol kad, ol smol wok oli kam wan lis blong jekem, ol not oli kam ol komen, mo @labels mo ol praoriti p1 kasem p3 oli kam ol lebel.",
|
||||
"import-board-instruction-planner": "Long Microsoft Planner, openem menyu blong plan mo jusum Export plan to Excel, afta jusum fael .xlsx ia. Ol bakit oli kam ol lis mo ol wok oli kam ol kad, wetem ol man we oli givim wok long olgeta, ol deit, ol mak, jeklis mo diskripsen; Progress, Priority mo Completed By oli kam ol spesel fil.",
|
||||
"import-board-instruction-meistertask": "In MeisterTask, click the project name, choose Export project and export the CSV, then paste it. Sections become lists and tasks become cards, with their notes, due date, assignee and tags; completed tasks keep their completion date. A CSV written for MeisterTask's own import works too.",
|
||||
"import-board-instruction-obsidian": "In Obsidian, open the Kanban board's file as Markdown (or open the .md file in a text editor), then paste all of it. Lanes become lists with their card limits, cards keep their tags, dates, priority, fields and checklist, and the Archive becomes archived cards.",
|
||||
"import-board-instruction-linear": "In Linear, export the issues as CSV (Settings, Import / Export, Export data, or a view's export), then paste the CSV. Statuses become lists, teams become swimlanes, and issues become cards with their assignee, labels, dates, parent issue, priority, estimate, project and cycle.",
|
||||
"import-board-instruction-ticktick": "In TickTick, open Settings, Account, Backup & Import and generate a backup, then paste the CSV file. Each TickTick list becomes a swimlane and its columns become lists; tasks keep their tags, dates, checklist, priority and subtasks.",
|
||||
"import-board-instruction-clickup": "In ClickUp, export the tasks as CSV (Settings, Imports / Exports, Export Items, or a List or Table view's export with all columns), then paste the CSV. Statuses become lists, ClickUp lists become swimlanes, and tasks become cards with their subtasks, assignees, tags, dates, priority and time.",
|
||||
"import-board-instruction-nullboard": "In Nullboard, open the board, choose Export this board... from the menu, open the .nbx file in a text editor and paste its contents. Lists become lists and notes become cards: a note's first line is the card title and the rest its description. A raw note gets the label raw. A file with several boards imports its first board.",
|
||||
"import-board-instruction-kanri": "In Kanri, open Import & Export and choose Export individual board (or Export all data), then paste the .json file. Columns become lists and cards become cards, with their description, due date, tags and colors; a card's tasks become a checklist. From an all-data export the first board is imported.",
|
||||
"import-board-instruction-meistertask": "Long MeisterTask, klik long nem blong projek, jusum Export project mo sendem CSV i go, afta pestem. Ol seksen oli kam ol lis mo ol wok oli kam ol kad, wetem not, dedlaen, man we i gat wok mo ol tag blong olgeta; ol wok we i finis oli kipim deit we oli finis. Wan CSV we oli raetem blong MeisterTask i wok tu.",
|
||||
"import-board-instruction-obsidian": "Long Obsidian, openem fael blong Kanban bod olsem Markdown (o openem fael .md long wan teks edita), afta pestem evri samting. Ol lein oli kam ol lis wetem limit blong kad, ol kad oli kipim ol tag, deit, praeoriti, fil mo jeklis blong olgeta, mo Archive i kam ol kad long akaev.",
|
||||
"import-board-instruction-linear": "Long Linear, sendem ol isiu i go olsem CSV (Settings, Import / Export, Export data, o eksport blong wan viu), afta pestem CSV. Ol stetas oli kam ol lis, ol tim oli kam ol rod, mo ol isiu oli kam ol kad wetem man we i gat wok, ol mak, deit, papa isiu, praeoriti, estimet, projek mo saekol blong olgeta.",
|
||||
"import-board-instruction-ticktick": "Long TickTick, openem Settings, Account, Backup & Import mo mekem wan bakap, afta pestem fael CSV. Evri lis blong TickTick i kam wan rod mo ol kolum blong hem oli kam ol lis; ol wok oli kipim ol tag, deit, jeklis, praeoriti mo smol wok blong olgeta.",
|
||||
"import-board-instruction-clickup": "Long ClickUp, sendem ol wok i go olsem CSV (Settings, Imports / Exports, Export Items, o eksport blong wan List o Table viu wetem evri kolum), afta pestem CSV. Ol stetas oli kam ol lis, ol lis blong ClickUp oli kam ol rod, mo ol wok oli kam ol kad wetem smol wok, man we i gat wok, tag, deit, praeoriti mo taem blong olgeta.",
|
||||
"import-board-instruction-nullboard": "Long Nullboard, openem bod, jusum Export this board... long menu, openem fael .nbx long wan teks edita mo pestem samting we i stap insaed. Ol lis oli kam ol lis mo ol not oli kam ol kad: fes laen blong wan not i nem blong kad mo narafala i stori blong hem. Wan not we i raw i kasem mak raw. Long wan fael we i gat plante bod, fes bod nomo i kam.",
|
||||
"import-board-instruction-kanri": "Long Kanri, openem Import & Export mo jusum Export individual board (o Export all data), afta pestem fael .json. Ol kolum oli kam ol lis mo ol kad oli kam ol kad, wetem stori, dedlaen, tag mo kala blong olgeta; ol wok blong wan kad oli kam wan jeklis. Long wan eksport blong evri data, fes bod i kam.",
|
||||
"import-board-instruction-planka": "Raetem adres blong Planka blong yu (olsem https://planka.example.com), nem blong yusa mo paswod blong yu long Planka, nao presem Soem Lis. Oli yusum olgeta blong karem bod nomo mo oli no kipim. Jusum wan bod mo presem Karem: WeKan i ridim long API blong Planka, wetem ol lis, kad, stori, dedlaen, mak, memba, lis blong wok mo toktok blong hem, nao gohed long tekem i kam olsem oltaem. Ol kad long stoa blong Planka oli kam olsem kad we oli stap long stoa; ol atajmen mo ol kad long rabis oli no kam.",
|
||||
"import-board-instruction-mstodo": "Long Microsoft Graph Explorer (developer.microsoft.com/graph/graph-explorer), saen in, agri long tok-orait Tasks.Read mo kopi token long tab Access token. Pastem long ples ia mo kilik Soem Lis; token ia i blong karem ia nomo mo oli no kipim. Jusum evri lis o wan lis mo kilik Karem. Evri lis blong To Do i kam wan lis mo evri wok i kam wan kad, wetem not, dedlaen mo stat det, ol step olsem lis blong jekem, ol kategori olsem mak, mo bigfala impoten mo stejes olsem mak; ol wok we i finis oli go long stoa. Ol rimaenda mo ripit oli no kam.",
|
||||
"import-board-instruction-kanbanflow": "Long KanbanFlow, openem menu blong bod, jusum Settings → API & Webhooks mo kopi API token blong bod. Pastem token long ples ia, kilik Soem Lis, jusum bod mo kilik Karem; token ia i blong karem ia nomo mo oli no kipim. Ol kolum oli kam lis mo ol rod oli stap rod; ol wok oli kipim stori, kala, mak, dedlaen, smol wok olsem lis blong jekem, taem we oli spenem, tingting blong taem, ol man mo toktok blong olgeta.",
|
||||
@@ -941,19 +941,19 @@
|
||||
"api-import-fetched": "I kam finis. Jekem ol memba daon mo tekem i kam.",
|
||||
"api-import-failed": "Karem i no wok",
|
||||
"api-import-nothing": "I no gat samting blong tekem i kam wetem akaon ia.",
|
||||
"import-board-instruction-pivotal": "In Pivotal Tracker, open the project's MORE tab and choose Export CSV (or select stories and choose Bulk Actions, CSV), then paste the CSV. Each story state becomes a list and stories become cards, with their type and labels, owners, requester, dates, comments, tasks and blockers; Estimate becomes Story points and iterations become sprints.",
|
||||
"import-board-instruction-tasksorg": "In Tasks.org, open Settings, Backups and choose Export tasks, then paste the contents of that .json file. Lists become lists and tasks become cards, with their notes, tags, dates, comments and subtasks; completed tasks keep their completion date, and priority becomes a custom field.",
|
||||
"import-board-instruction-monday": "In monday.com, open the board menu, choose More actions and Export board to Excel (with updates and subitems if you like), then choose that .xlsx file. The Status column becomes lists and groups become swimlanes; people, dates, timeline and tags keep their places, other columns become custom fields and updates become comments.",
|
||||
"import-board-instruction-superproductivity": "In Super Productivity, open Settings, then Sync & Backup, choose Export Data and paste the contents of the sp-backup .json file. Each project becomes a swimlane and tasks become cards in To Do, In Progress, Backlog or Done, with their notes, tags, due date, time spent and sub-tasks; archived tasks become archived cards.",
|
||||
"import-board-instruction-taiga": "In Taiga, open Admin > Project > Export and download the project dump (unpack a .json.gz file first), then paste its JSON. User story statuses become lists, user stories become cards with their tasks as subtasks, epics and issues get swimlanes of their own, and sprints, tags, comments and custom attributes come along. Attachments are not imported.",
|
||||
"import-board-instruction-vikunja": "In Vikunja, open Settings, choose Data Export and download the .zip file it sends you, then choose that file, or paste the data.json inside it. Kanban buckets become lists and tasks become cards, with their labels, assignees, dates, description, task lists as checklists and comments; priority, percent done and done become custom fields. Attachments are not imported.",
|
||||
"import-board-instruction-quire": "In Quire, open the project menu and choose Export CSV, then paste the CSV. Statuses become lists and tasks become cards, with their subtasks as child cards, assignees, tags as labels, start, due and completed dates, description and priority as a custom field. Comments and attachments are not in Quire's CSV.",
|
||||
"import-board-instruction-wrike": "In Wrike, export a project or folder to Excel, or fill in Wrike's Excel import template, then choose that .xlsx file. Folder and project rows become swimlanes, Status becomes lists and tasks become cards, with their Key, parent task, assignees, start and end dates, dependencies and description; Priority, Duration and other columns become custom fields.",
|
||||
"import-board-instruction-teamwork": "In Teamwork.com, fill in the Excel task import template (Tasklist, Task, Description, Assign to, Start date, Due date, Priority, Estimated time, Tags, Status) and save it as .xlsx, then choose that file. Task lists become lists and tasks become cards; a task starting with -, # or > becomes a subtask of the task above it (--, ## or >> one level deeper). Assignees, dates and tags keep their places; Priority, Estimated time and Complete become custom fields.",
|
||||
"import-board-instruction-businessmap": "In Businessmap (formerly Kanbanize), open Advanced Search, choose Configure results to pick the columns, download the Excel file, then choose that .xlsx file. Columns are read by their English names (Title, Column, Lane, Owner, Deadline ...): if your account uses another language, rename the header row to English first. Column becomes lists and Lane swimlanes; owners, tags, color, dates, comments and parent links keep their places, and Priority, Size, Type and other columns become custom fields.",
|
||||
"import-board-instruction-redmine": "In Redmine, open the project's Issues, choose Also available in: CSV at the bottom of the list, pick the columns (All columns, with Description) and export, then paste the CSV. Redmine names the columns in your language, so set your language to English in My account before exporting. Each status becomes a list and issues become cards, with the tracker as a label, assignee, author, dates, parent task and related issues; priority, category, target version, estimated time, % Done and custom fields become custom fields.",
|
||||
"import-board-instruction-notion": "In Notion, open the database as a full page, choose ••• and Export, pick Markdown & CSV, and choose the .zip file it downloads, or one database CSV from it, or paste that CSV. A column named Status becomes the lists (without one, the first column that looks like a select does, and the import report says which); rows become cards with their title, tags as labels, people as members, dates, and the page text as the description; other columns become custom fields. Relations, images and attachments are not imported.",
|
||||
"import-board-instruction-plane": "In Plane, open Workspace Settings, choose Exports, export the projects as JSON, CSV or Excel and download the .zip file, then choose that file, or one file from inside it, or paste the JSON or CSV. States become lists, projects become swimlanes, and issues become cards with their identifier, parent, assignees, labels, dates, links and comments; priority, estimate, cycle and module become custom fields. Plane's export has no descriptions or attachments, so they are not imported.",
|
||||
"import-board-instruction-pivotal": "Long Pivotal Tracker, openem tab MORE blong projek mo jusum Export CSV (o jusum ol stori mo jusum Bulk Actions, CSV), afta pestem CSV. Evri stet blong stori i kam wan lis mo ol stori oli kam ol kad, wetem kaen mo ol mak, ona, man we i askem, deit, toktok, wok mo ol samting we i blokem blong olgeta; Estimate i kam Story points mo ol iteresen oli kam ol sprint.",
|
||||
"import-board-instruction-tasksorg": "Long Tasks.org, openem Settings, Backups mo jusum Export tasks, afta pestem samting we i stap insaed long fael .json ia. Ol lis oli kam ol lis mo ol wok oli kam ol kad, wetem not, tag, deit, toktok mo smol wok blong olgeta; ol wok we i finis oli kipim deit we oli finis, mo praeoriti i kam wan fil blong yu.",
|
||||
"import-board-instruction-monday": "Long monday.com, openem menu blong bod, jusum More actions mo Export board to Excel (wetem ol apdet mo ol smol samting sapos yu wantem), afta jusum fael .xlsx ia. Kolum Status i kam ol lis mo ol grup oli kam ol rod; ol man, deit, taemlaen mo tag oli stap long ples blong olgeta, ol narafala kolum oli kam ol fil blong yu mo ol apdet oli kam ol toktok.",
|
||||
"import-board-instruction-superproductivity": "Long Super Productivity, openem Settings, afta Sync & Backup, jusum Export Data mo pestem samting we i stap insaed long fael .json sp-backup. Evri projek i kam wan rod mo ol wok oli kam ol kad long To Do, In Progress, Backlog o Done, wetem not, tag, dedlaen, taem we oli yusum mo smol wok blong olgeta; ol wok long akaev oli kam ol kad long akaev.",
|
||||
"import-board-instruction-taiga": "Long Taiga, openem Admin > Project > Export mo daonlodem damp blong projek (fes, openem fael .json.gz), afta pestem JSON blong hem. Ol stetas blong yusa stori oli kam ol lis, ol yusa stori oli kam ol kad wetem ol wok blong olgeta olsem smol wok, ol epik mo isiu oli kasem rod blong olgeta, mo ol sprint, tag, toktok mo ol atribiut blong yu oli kam tu. Ol atajmen oli no kam.",
|
||||
"import-board-instruction-vikunja": "Long Vikunja, openem Settings, jusum Data Export mo daonlodem fael .zip we i sendem long yu, afta jusum fael ia, o pestem data.json we i stap insaed. Ol baket blong Kanban oli kam ol lis mo ol wok oli kam ol kad, wetem mak, man we i gat wok, deit, stori, lis blong wok olsem jeklis mo toktok blong olgeta; praeoriti, pesen we i finis mo finis oli kam ol fil blong yu. Ol atajmen oli no kam.",
|
||||
"import-board-instruction-quire": "Long Quire, openem menu blong projek mo jusum Export CSV, afta pestem CSV. Ol stetas oli kam ol lis mo ol wok oli kam ol kad, wetem smol wok olsem pikinini kad, man we i gat wok, tag olsem mak, deit blong stat, dedlaen mo deit we i finis, stori mo praeoriti olsem wan fil blong yu. Ol toktok mo atajmen oli no stap long CSV blong Quire.",
|
||||
"import-board-instruction-wrike": "Long Wrike, sendem wan projek o folda i go long Excel, o fulumap templet blong Excel import blong Wrike, afta jusum fael .xlsx ia. Ol laen blong folda mo projek oli kam ol rod, Status i kam ol lis mo ol wok oli kam ol kad, wetem Key, papa wok, man we i gat wok, deit blong stat mo en, ol samting we oli dipen long hem mo stori blong olgeta; Priority, Duration mo ol narafala kolum oli kam ol fil blong yu.",
|
||||
"import-board-instruction-teamwork": "Long Teamwork.com, fulumap templet blong Excel wok import (Tasklist, Task, Description, Assign to, Start date, Due date, Priority, Estimated time, Tags, Status) mo sevem olsem .xlsx, afta jusum fael ia. Ol lis blong wok oli kam ol lis mo ol wok oli kam ol kad; wan wok we i stat wetem -, # o > i kam smol wok blong wok we i stap antap long hem (--, ## o >> wan level daon moa). Man we i gat wok, deit mo tag oli stap long ples blong olgeta; Priority, Estimated time mo Complete oli kam ol fil blong yu.",
|
||||
"import-board-instruction-businessmap": "Long Businessmap (bifo Kanbanize), openem Advanced Search, jusum Configure results blong jusum ol kolum, daonlodem fael Excel, afta jusum fael .xlsx ia. Oli ridim ol kolum long nem blong olgeta long Inglis (Title, Column, Lane, Owner, Deadline ...): sapos akaon blong yu i yusum narafala lanwis, fes, jenisim nem blong laen blong hed i go long Inglis. Column i kam ol lis mo Lane i kam ol rod; ona, tag, kala, deit, toktok mo link blong papa oli stap long ples blong olgeta, mo Priority, Size, Type mo ol narafala kolum oli kam ol fil blong yu.",
|
||||
"import-board-instruction-redmine": "Long Redmine, openem Issues blong projek, jusum Also available in: CSV long botom blong lis, jusum ol kolum (All columns, wetem Description) mo sendem i go, afta pestem CSV. Redmine i putum nem blong ol kolum long lanwis blong yu, ale jenisim lanwis blong yu i go long Inglis long My account bifo yu sendem i go. Evri stetas i kam wan lis mo ol isiu oli kam ol kad, wetem treka olsem mak, man we i gat wok, man we i raetem, deit, papa wok mo ol isiu we oli joen; praeoriti, kategori, vesen we oli wantem kasem, taem we oli tingbaot, % Done mo ol fil blong yu oli kam ol fil blong yu.",
|
||||
"import-board-instruction-notion": "Long Notion, openem database olsem wan fulwan pej, jusum ••• mo Export, jusum Markdown & CSV, mo jusum fael .zip we i daonlod, o wan database CSV long hem, o pestem CSV ia. Wan kolum we nem blong hem Status i kam ol lis (sapos i no gat, fes kolum we i luk olsem wan jus i mekem, mo ripot blong import i talem wanem); ol laen oli kam ol kad wetem nem, tag olsem mak, ol man olsem memba, deit, mo teks blong pej olsem stori; ol narafala kolum oli kam ol fil blong yu. Ol rilesen, pija mo atajmen oli no kam.",
|
||||
"import-board-instruction-plane": "Long Plane, openem Workspace Settings, jusum Exports, sendem ol projek i go olsem JSON, CSV o Excel mo daonlodem fael .zip, afta jusum fael ia, o wan fael insaed long hem, o pestem JSON o CSV. Ol stet oli kam ol lis, ol projek oli kam ol rod, mo ol isiu oli kam ol kad wetem namba, papa, man we i gat wok, mak, deit, link mo toktok blong olgeta; praeoriti, estimet, saekol mo modiul oli kam ol fil blong yu. Eksport blong Plane i no gat stori o atajmen, ale oli no kam.",
|
||||
"import-board-instruction-trello": "Long bod blong Trello blong yu, go long 'Menu', afta 'More', 'Print and Export', 'Export JSON', mo kopiem teks we i kamaot.",
|
||||
"import-board-instruction-csv": "Putum kopi blong ol valiu we koma i seraotem (CSV) / ol valiu we tab i seraotem (TSV).",
|
||||
"import-board-instruction-jira": "Putum kopi blong JSON long lukaot blong isu blong Jira Cloud REST API (GET /rest/api/3/search/jql), o wan objek we i semmak olsem { \"issues\": [...] }. Ol stet blong Jira oli kam ol lis mo ol isu oli kam ol kad. Yu save adem wan array blong \"automationRules\" blong importem ol rul.",
|
||||
@@ -962,33 +962,33 @@
|
||||
"import-board-instruction-wekan": "Long bod blong yu, go long 'Menu', afta 'Export board', mo kopiem teks long fael we yu daonlodem.",
|
||||
"import-board-instruction-about-errors": "Sapos yu kasem ol eroa taem yu karem i kam bod, samtaem karem i kam i wok yet, mo bod i stap long pej blong Olgeta Bod.",
|
||||
"import-without-mapping-members": "Karem i kam we i no joenem ol memba (joenem biaen)",
|
||||
"import-members-mode-heading": "People in the file:",
|
||||
"import-members-mode-map": "Choose an existing user for each of them",
|
||||
"import-members-mode-placeholder": "Keep them as placeholder users with their own names, to map to real users later",
|
||||
"import-members-mode-me": "Make them all me",
|
||||
"csv-mapping-title": "Which column is which?",
|
||||
"csv-mapping-description": "Choose the column of the file that holds each card field. The choices were made from the column names; change any that are wrong, and choose (not in the file) for a field the file does not have.",
|
||||
"csv-mapping-not-in-file": "(not in the file)",
|
||||
"csv-mapping-column-number": "Column __number__",
|
||||
"csv-mapping-list-missing": "The file has no column for the list. Choose one above, or type the name of the one list every card goes into.",
|
||||
"csv-mapping-list-name": "List for every card",
|
||||
"csv-mapping-custom-fields": "Other columns to import as text custom fields:",
|
||||
"csv-mapping-boards": "Each sheet becomes its own board:",
|
||||
"csv-mapping-skipped-sheets": "Sheets that are not imported:",
|
||||
"csv-mapping-title-required": "Choose the column that holds the card titles.",
|
||||
"csv-mapping-list-required": "Type the name of the list every card goes into, or choose the column that holds the list.",
|
||||
"invalid-import-mapping": "The column mapping does not match the file.",
|
||||
"import-many-boards": "Import many boards",
|
||||
"import-many-boards-hint": "Choose several export files of this tool, or one .zip that holds them: each file becomes its own board, imported without member mapping. A .zip that is itself one export (Vikunja, Notion, Plane, or WeKan with attachments) is one board. When files are chosen here, Import imports them instead of the field above.",
|
||||
"import-one-board-per-project": "One board per project",
|
||||
"import-one-board-per-project-hint": "Each swimlane the import would make (a project, folder or list of the tool) becomes its own board. Parent cards and dependencies between those boards are listed in the import report.",
|
||||
"import-many-progress": "Importing __done__ of __total__: __name__",
|
||||
"import-many-results-heading": "Imported files",
|
||||
"import-many-imported": "imported",
|
||||
"export-all-boards": "Export all boards",
|
||||
"export-selected-boards": "Export selected boards",
|
||||
"export-all-boards-hint": "Every board you can export, in one download: Excel as one workbook with a sheet per board named after it, every other format as a .zip with a file per board.",
|
||||
"exportAllBoardsPopup-title": "Export all boards",
|
||||
"import-members-mode-heading": "Ol man long fael:",
|
||||
"import-members-mode-map": "Jusum wan yusa we i stap finis blong evri wan long olgeta",
|
||||
"import-members-mode-placeholder": "Kipim olgeta olsem ol yusa blong ples wetem nem blong olgeta, blong joenem wetem ol tru yusa biaen",
|
||||
"import-members-mode-me": "Mekem olgeta evriwan i kam mi",
|
||||
"csv-mapping-title": "Wanem kolum i wanem?",
|
||||
"csv-mapping-description": "Jusum kolum blong fael we i holem evri fil blong kad. Oli mekem ol jus long nem blong ol kolum; jenisim eni we i rong, mo jusum (i no stap long fael) blong wan fil we fael i no gat.",
|
||||
"csv-mapping-not-in-file": "(i no stap long fael)",
|
||||
"csv-mapping-column-number": "Kolum __number__",
|
||||
"csv-mapping-list-missing": "Fael i no gat kolum blong lis. Jusum wan antap, o raetem nem blong wan lis nomo we evri kad i go long hem.",
|
||||
"csv-mapping-list-name": "Lis blong evri kad",
|
||||
"csv-mapping-custom-fields": "Ol narafala kolum blong karem i kam olsem fil blong yu blong teks:",
|
||||
"csv-mapping-boards": "Evri sit i kam bod blong hem wan:",
|
||||
"csv-mapping-skipped-sheets": "Ol sit we oli no karem i kam:",
|
||||
"csv-mapping-title-required": "Jusum kolum we i holem nem blong ol kad.",
|
||||
"csv-mapping-list-required": "Raetem nem blong lis we evri kad i go long hem, o jusum kolum we i holem lis.",
|
||||
"invalid-import-mapping": "Fasin we ol kolum oli joen i no stret wetem fael.",
|
||||
"import-many-boards": "Tekem plante bod i kam",
|
||||
"import-many-boards-hint": "Jusum plante fael blong eksport blong tul ia, o wan .zip we i holem olgeta: evri fael i kam bod blong hem wan, we i kam be i no joenem ol memba. Wan .zip we hem wan i wan eksport (Vikunja, Notion, Plane, o WeKan wetem atajmen) i wan bod nomo. Taem yu jusum ol fael long ples ia, Import i karem olgeta i kam, i no fil antap.",
|
||||
"import-one-board-per-project": "Wan bod long evri projek",
|
||||
"import-one-board-per-project-hint": "Evri rod we import bambae i mekem (wan projek, folda o lis blong tul) i kam bod blong hem wan. Ol papa kad mo ol samting we oli dipen long olgeta bitwin ol bod ia oli stap long ripot blong import.",
|
||||
"import-many-progress": "I stap karem __done__ long __total__ i kam: __name__",
|
||||
"import-many-results-heading": "Ol fael we oli kam finis",
|
||||
"import-many-imported": "i kam finis",
|
||||
"export-all-boards": "Sendem evri bod i go",
|
||||
"export-selected-boards": "Sendem ol bod we yu jusum i go",
|
||||
"export-all-boards-hint": "Evri bod we yu save sendem i go, long wan daonlod: Excel olsem wan buk wetem wan sit long evri bod we i karem nem blong hem, evri narafala fomat olsem wan .zip wetem wan fael long evri bod.",
|
||||
"exportAllBoardsPopup-title": "Sendem evri bod i go",
|
||||
"import-json-placeholder": "Putum stret data JSON blong yu long ples ia",
|
||||
"import-csv-placeholder": "Putum stret data CSV/TSV blong yu long ples ia",
|
||||
"import-json-file": "O jusum wan fael JSON blong Trello",
|
||||
@@ -1026,17 +1026,17 @@
|
||||
"trello-import-results": "Ol risal blong karem i kam",
|
||||
"trello-api-credentials-required": "Plis raetem tufala ki mo token blong Trello API.",
|
||||
"trello-api-credentials-saved": "Oli sevem ki mo token blong Trello API. Yu save soemaot ol wokples we yu no raetem olgeta bakegen.",
|
||||
"exportToTrelloPopup-title": "Export to Trello",
|
||||
"trello-export-desc": "Create a copy of this board in Trello through Trello's API, with your Trello API key and token. Swimlanes become labels named \"Swimlane: …\", which WeKan's Trello import turns back into swimlanes.",
|
||||
"trello-export-include-attachments": "Include attachments",
|
||||
"trello-export-start": "Export to Trello",
|
||||
"trello-export-open-board": "Open the board in Trello",
|
||||
"trello-export-warnings": "Not everything could be exported",
|
||||
"sandstorm-trello-access-needed": "A Sandstorm grain has no Internet access of its own. Allow this grain to reach Trello first.",
|
||||
"sandstorm-allow-trello-access": "Allow access to Trello",
|
||||
"sandstorm-trello-access-granted": "This grain may reach Trello.",
|
||||
"sandstorm-forget-access": "Forget",
|
||||
"sandstorm-trello-save-label": "Trello, for importing and exporting boards",
|
||||
"exportToTrelloPopup-title": "Sendem i go long Trello",
|
||||
"trello-export-desc": "Mekem wan kopi blong bod ia long Trello tru long API blong Trello, wetem API ki mo token blong Trello blong yu. Ol rod oli kam ol mak we nem blong olgeta \"Swimlane: …\", we Trello import blong WeKan i tanem olgeta i kam bak ol rod.",
|
||||
"trello-export-include-attachments": "Putum ol atajmen tu",
|
||||
"trello-export-start": "Sendem i go long Trello",
|
||||
"trello-export-open-board": "Openem bod long Trello",
|
||||
"trello-export-warnings": "I no save sendem evri samting i go",
|
||||
"sandstorm-trello-access-needed": "Wan Sandstorm grain i no gat Intanet blong hem wan. Fes, letem grain ia i go kasem Trello.",
|
||||
"sandstorm-allow-trello-access": "Letem i go kasem Trello",
|
||||
"sandstorm-trello-access-granted": "Grain ia i save go kasem Trello.",
|
||||
"sandstorm-forget-access": "Fogetem",
|
||||
"sandstorm-trello-save-label": "Trello, blong karem bod i kam mo sendem bod i go",
|
||||
"trello-select-boards": "Plis jusum wan bod o moa blong karem i kam.",
|
||||
"select-all": "Jusum evriwan",
|
||||
"select-none": "No jusum wan samting",
|
||||
@@ -1318,9 +1318,9 @@
|
||||
"external-link-pattern-description": "Hem i save mekem olsem \"#1234\" we oli stap long raeting blong kad i kam wan link i go long wan aotsaed traka blong problem. Livim wan spes nomo blong jampem samting ia.",
|
||||
"external-link-pattern-prefix": "Prefiks blong tok",
|
||||
"external-link-pattern-url": "Templet blong URL (yusum {number})",
|
||||
"external-link-rules": "More link rules",
|
||||
"external-link-rules-description": "One rule per line: the text to find, \" = \", and the address. Use {number} for digits and {identifier} for a word, in both, for example [{identifier}:{number}] = https://tracker.example.com/{identifier}/{number}.",
|
||||
"external-link-identifier-aliases": "Abbreviations for {identifier}, for example TK=Task, IN=Incident",
|
||||
"external-link-rules": "Moa rul blong link",
|
||||
"external-link-rules-description": "Wan rul long wan laen: teks blong faenem, \" = \", mo adres. Yusum {number} blong namba mo {identifier} blong wan wod, long tufala, olsem [{identifier}:{number}] = https://tracker.example.com/{identifier}/{number}.",
|
||||
"external-link-identifier-aliases": "Sotfala nem blong {identifier}, olsem TK=Task, IN=Incident",
|
||||
"username": "Nem blong yusa",
|
||||
"import-usernames": "Karem i kam ol Yusanem",
|
||||
"view-it": "Lukluk hem",
|
||||
@@ -1345,13 +1345,13 @@
|
||||
"attachment-transfer-limits-title": "Ol Limit blong Saes blong Fael we i Joen mo Fael blong API",
|
||||
"attachment-limits": "Ol limit",
|
||||
"attachment-retention": "Taem blong kipim",
|
||||
"attachment-retention-days": "Delete attachments after (days)",
|
||||
"attachment-retention-days-help": "Like Delete on the card: the card history can restore them. Empty or 0: never.",
|
||||
"attachment-retention-mode": "Count the days from",
|
||||
"attachment-retention-purge": "Remove deleted attachments for good after (days)",
|
||||
"attachment-retention-purge-help": "After this they can no longer be restored. Empty or 0: never.",
|
||||
"attachment-retention-dry-run": "Dry run: only count, in the server log, change nothing",
|
||||
"attachment-retention-max-per-run": "At most this many attachments per night",
|
||||
"attachment-retention-days": "Diletem ol atajmen afta (dei)",
|
||||
"attachment-retention-days-help": "Olsem Diletem long kad: histori blong kad i save tekem olgeta i kambak. Emti o 0: neva.",
|
||||
"attachment-retention-mode": "Kaontem ol dei stat long",
|
||||
"attachment-retention-purge": "Tekemaot olgeta ol atajmen we oli diletem finis afta (dei)",
|
||||
"attachment-retention-purge-help": "Afta long hemia, yu no save tekem olgeta i kambak. Emti o 0: neva.",
|
||||
"attachment-retention-dry-run": "Traem nomo: kaontem nomo, long log blong seva, no jenisim wan samting",
|
||||
"attachment-retention-max-per-run": "Bigfala namba blong atajmen long wan naet",
|
||||
"attachment-transfer-limits-description": "Setem ol limit blong saes blong fael we i bigfala olgeta blong aplodem mo daonlodem. Fasin blong API blong holem data i gat ol limit blong sefti blong hem long seva.",
|
||||
"attachment-transfer-limits-saved": "Oli sevem ol limit blong sendem fael we i joen.",
|
||||
"attachment-transfer-limits-save-failed": "I no save sevem ol limit blong sendem fael we i joen",
|
||||
@@ -1401,51 +1401,51 @@
|
||||
"error-notAuthorized": "Yu no gat raet blong lukluk pej ia.",
|
||||
"webhook-title": "Nem blong Webhook",
|
||||
"webhook-token": "Token (sapos yu wantem, blong Jekem hu nao)",
|
||||
"webhook-payload-heading": "Webhook data",
|
||||
"webhook-payload-description": "What outgoing webhooks (Rocket.Chat, Slack, ...) send. Default keeps the inherited setting; with nothing set, webhooks send what they always have.",
|
||||
"webhook-payload-text": "Send the message text",
|
||||
"webhook-payload-fields": "Send these fields",
|
||||
"webhook-payload-field-standard": "Standard fields (WEBHOOKS_ATTRIBUTES)",
|
||||
"webhook-payload-field-ids": "IDs",
|
||||
"webhook-payload-field-names": "Names and titles",
|
||||
"webhook-payload-field-links": "Links to the card and board",
|
||||
"webhook-payload-field-actor": "Who did it",
|
||||
"webhook-payload-field-people": "The member or assignee it is about",
|
||||
"webhook-payload-field-details": "Details: comment, description, values and dates",
|
||||
"webhook-hide-identity": "Leave my name out of outgoing webhooks",
|
||||
"webhook-someone": "A member",
|
||||
"webhook-payload-heading": "Data blong webhook",
|
||||
"webhook-payload-description": "Wanem ol webhook we i go aot (Rocket.Chat, Slack, ...) oli sendem. Defolt i kipim seting we i kam long antap; sapos i no gat seting, ol webhook oli sendem samting we oli stap sendem oltaem.",
|
||||
"webhook-payload-text": "Sendem teks blong mesej",
|
||||
"webhook-payload-fields": "Sendem ol fil ia",
|
||||
"webhook-payload-field-standard": "Ol standet fil (WEBHOOKS_ATTRIBUTES)",
|
||||
"webhook-payload-field-ids": "Ol ID",
|
||||
"webhook-payload-field-names": "Ol nem mo taetel",
|
||||
"webhook-payload-field-links": "Ol link i go long kad mo bod",
|
||||
"webhook-payload-field-actor": "Hu i mekem",
|
||||
"webhook-payload-field-people": "Memba o man we i gat wok we samting ia i tokbaot",
|
||||
"webhook-payload-field-details": "Ol smol samting: toktok, stori, valiu mo deit",
|
||||
"webhook-hide-identity": "No putum nem blong mi long ol webhook we i go aot",
|
||||
"webhook-someone": "Wan memba",
|
||||
"notification-delivery-layout": "Fasin blong putum ol samting",
|
||||
"notification-delivery-layout-classic": "One sentence (classic)",
|
||||
"notification-delivery-layout-clear": "Clearly arranged: board, who, card in bold",
|
||||
"notification-delivery-parts": "Show",
|
||||
"notification-delivery-part-board": "Board name and link",
|
||||
"notification-delivery-part-actor": "Who did it",
|
||||
"notification-delivery-part-card": "Card title and link",
|
||||
"notification-delivery-layout-classic": "Wan sentens (olfala fasin)",
|
||||
"notification-delivery-layout-clear": "Klia: bod, hu, kad long strong raeting",
|
||||
"notification-delivery-parts": "Soem",
|
||||
"notification-delivery-part-board": "Nem mo link blong bod",
|
||||
"notification-delivery-part-actor": "Hu i mekem",
|
||||
"notification-delivery-part-card": "Nem mo link blong kad",
|
||||
"notification-delivery-part-list": "Lis",
|
||||
"notification-delivery-part-swimlane": "Rod",
|
||||
"notification-delivery-part-details": "Comment or changed value",
|
||||
"notification-delivery-part-dates": "Dates",
|
||||
"notification-delivery-part-link": "Link on its own line",
|
||||
"notification-delivery-grouping": "Grouping",
|
||||
"notification-delivery-grouping-all": "Everything due in one message",
|
||||
"notification-delivery-grouping-board": "One per board",
|
||||
"notification-delivery-grouping-card": "One per card",
|
||||
"notification-delivery-grouping-none": "Each on its own",
|
||||
"notification-delivery-schedule": "When to send",
|
||||
"notification-delivery-schedule-immediate": "Immediately",
|
||||
"notification-delivery-schedule-interval": "Every few minutes or hours",
|
||||
"notification-delivery-schedule-daily": "Once a day",
|
||||
"notification-delivery-interval": "Every",
|
||||
"notification-delivery-minutes": "__count__ minutes",
|
||||
"notification-delivery-part-details": "Toktok o valiu we i jenis",
|
||||
"notification-delivery-part-dates": "Ol deit",
|
||||
"notification-delivery-part-link": "Link long laen blong hem wan",
|
||||
"notification-delivery-grouping": "Putum long grup",
|
||||
"notification-delivery-grouping-all": "Evri samting long wan mesej",
|
||||
"notification-delivery-grouping-board": "Wan long evri bod",
|
||||
"notification-delivery-grouping-card": "Wan long evri kad",
|
||||
"notification-delivery-grouping-none": "Evri wan i stap hem wan",
|
||||
"notification-delivery-schedule": "Wetaem blong sendem",
|
||||
"notification-delivery-schedule-immediate": "Stret nao",
|
||||
"notification-delivery-schedule-interval": "Evri smol minit o aoa",
|
||||
"notification-delivery-schedule-daily": "Wan taem long wan dei",
|
||||
"notification-delivery-interval": "Evri",
|
||||
"notification-delivery-minutes": "__count__ minit",
|
||||
"notification-delivery-daily-time": "Long",
|
||||
"notification-delivery-quiet": "Quiet hours (wait until they end)",
|
||||
"notification-delivery-quiet": "Ol aoa blong kwaet (wet gogo oli finis)",
|
||||
"notification-delivery-quiet-from": "Stat long",
|
||||
"notification-delivery-quiet-to": "Go kasem",
|
||||
"notification-delivery-quiet-clear": "No quiet hours",
|
||||
"notification-delivery-timezone": "Time zone:",
|
||||
"notification-delivery-invalid": "The setting could not be saved.",
|
||||
"notification-group-card": "__count__ notifications on card __card__",
|
||||
"notification-group-board": "__count__ notifications on board __board__",
|
||||
"notification-delivery-quiet-clear": "I no gat aoa blong kwaet",
|
||||
"notification-delivery-timezone": "Taem son:",
|
||||
"notification-delivery-invalid": "I no save sevem seting.",
|
||||
"notification-group-card": "__count__ niusem long kad __card__",
|
||||
"notification-group-board": "__count__ niusem long bod __board__",
|
||||
"outgoing-webhooks": "Ol webhook we oli go aot",
|
||||
"bidirectional-webhooks": "Ol webhook blong tufala rod",
|
||||
"outgoingWebhooksPopup-title": "Ol webhook we oli go aot",
|
||||
@@ -1500,7 +1500,7 @@
|
||||
"showLabel-field-on-card": "Soemaot mak blong fil long smol kad",
|
||||
"showSum-field-on-list": "Soemaot totel blong ol fil long antap blong lis",
|
||||
"admin-only-field": "Admin blong bod nomo i save luk",
|
||||
"read-only-field": "Read-only: every member sees the value, only board admins change it",
|
||||
"read-only-field": "Blong ridim nomo: evri memba i luk valiu, ol admin blong bod nomo oli jenisim",
|
||||
"yes": "Io",
|
||||
"no": "Nogat",
|
||||
"accounts": "Ol akaon",
|
||||
@@ -1692,11 +1692,11 @@
|
||||
"r-format-auto": "Faenemaot otomatik",
|
||||
"r-import-workflow-note": "Karem i kam wan pikja blong rod blong wok we oli karem i go long n8n o Node-RED. Ol triga/aksen we oli save oli joen wetem ol rul blong WeKan; ol wan we oli no joen bae oli talem.",
|
||||
"r-import-unmapped": "I no save joenem __count__ laen",
|
||||
"r-wrike-workflow": "Wrike workflow",
|
||||
"r-wrike-workflow-note": "A Wrike workflow is a list of statuses, each in the Active, Completed, Deferred or Cancelled group. Export writes this board's lists as one, named after the board, in the JSON Wrike's API returns for workflows: create that workflow in Wrike before importing the board's Wrike Excel export, so its statuses are applied. Import reads that JSON pasted above, from WeKan or from Wrike's GET /workflows, into the chosen board: a status without a list becomes a list, and a rule marks a card complete when it is moved into a Completed or Cancelled status and incomplete in an Active or Deferred one. Wrike's own automation rules cannot be exported from Wrike.",
|
||||
"r-export-wrike-workflow": "Export Wrike workflow",
|
||||
"r-import-wrike-workflow": "Import Wrike workflow",
|
||||
"r-import-wrike-workflow-done": "Wrike workflow \"__name__\": __lists__ list(s) and __rules__ rule(s) added, __skipped__ part(s) not read",
|
||||
"r-wrike-workflow": "Rod blong wok blong Wrike",
|
||||
"r-wrike-workflow-note": "Wan rod blong wok blong Wrike i wan lis blong ol stetas, evri wan i stap long grup Active, Completed, Deferred o Cancelled. Eksport i raetem ol lis blong bod ia olsem wan, we i karem nem blong bod, long JSON we API blong Wrike i givim blong ol rod blong wok: mekem rod blong wok ia long Wrike bifo yu karem Wrike Excel eksport blong bod i kam, blong oli yusum ol stetas blong hem. Import i ridim JSON ia we yu pestem antap, we i kam long WeKan o long GET /workflows blong Wrike, i go long bod we yu jusum: wan stetas we i no gat lis i kam wan lis, mo wan rul i makem wan kad se i finis taem oli muvum i go long wan stetas Completed o Cancelled, mo se i no finis long Active o Deferred. Yu no save sendem ol rul blong otomesen blong Wrike i kamaot long Wrike.",
|
||||
"r-export-wrike-workflow": "Sendem rod blong wok blong Wrike i go",
|
||||
"r-import-wrike-workflow": "Tekem rod blong wok blong Wrike i kam",
|
||||
"r-import-wrike-workflow-done": "Rod blong wok blong Wrike \"__name__\": __lists__ lis mo __rules__ rul oli kam, __skipped__ pat oli no ridim",
|
||||
"r-set-scheduled-triggers": "Ol triga we oli setem taem blong olgeta",
|
||||
"r-set-button-triggers": "Ol baten",
|
||||
"r-when-scheduled": "Folem wan taem we oli setem",
|
||||
@@ -1752,8 +1752,8 @@
|
||||
"set-filter": "Setem filta",
|
||||
"r-moved-to": "Movem finis long",
|
||||
"r-moved-from": "Movem finis long",
|
||||
"r-moved-forward": "moved forward to a later list",
|
||||
"r-moved-back": "moved back to an earlier list",
|
||||
"r-moved-forward": "i muv i go fored long wan lis we i kam biaen",
|
||||
"r-moved-back": "i muv i go bak long wan lis we i kam fastaem",
|
||||
"r-archived": "Movem finis long Akaev",
|
||||
"r-unarchived": "Kamaot long Akaev",
|
||||
"r-commented": "oli raetem toktok long hem",
|
||||
@@ -1801,8 +1801,8 @@
|
||||
"r-add-actinguser-member": "Adem yusa we i statem rul ia olsem memba",
|
||||
"r-remove-all": "Tekemaot evri memba long kad",
|
||||
"r-assignee": "asaeni",
|
||||
"r-add-actinguser-assignee": "Add the user who triggered this rule as an assignee",
|
||||
"r-remove-all-assignees": "Remove all assignees from the card",
|
||||
"r-add-actinguser-assignee": "Putum yusa we i statem rul ia olsem man we i gat wok",
|
||||
"r-remove-all-assignees": "Tekemaot evri man we i gat wok long kad",
|
||||
"r-remove-all-labels": "Tekemaot evri mak long kad",
|
||||
"r-set-color": "Setem kala long",
|
||||
"r-checklist": "jeklis",
|
||||
@@ -1894,7 +1894,7 @@
|
||||
"oauth-provider-secret-set": "Wan sikret i stap",
|
||||
"oauth-providers-login-style": "Stael blong login",
|
||||
"oauth-providers-merge-existing-users": "Joenem wetem wan akaon we i stap finis mo i gat sem imel",
|
||||
"oauth-providers-allowed-email-domains": "Only these email domains may sign in with these providers (comma separated; empty allows all)",
|
||||
"oauth-providers-allowed-email-domains": "Ol imel domen ia nomo oli save saen in wetem ol provaeda ia (koma i seraotem; emti i letem evriwan)",
|
||||
"oauth-account-conflict": "Wan akaon wetem imel ia i stap finis mo oli no mekem hem wetem login rod ia",
|
||||
"sign-in-with": "Login wetem %s",
|
||||
"login-origin-mismatch": "Yu no save finisim go insaed tru long wan narafala seves long adres ya. Oli setem WeKan blong __expected__, be pej ya i open long __actual__. Openem WeKan long __expected__, o askem admin blong setem ROOT_URL i go long __actual__.",
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Koneksen i no wok",
|
||||
"cloud-settings-saved": "Oli sevem ol seting blong storej",
|
||||
"cloud-settings-save-failed": "I no save sevem ol seting blong storej",
|
||||
"cloud": "Klaod",
|
||||
"cloud-settings-desc": "Wetem WeKan long Weddy, Cloud Panel i givim plante WeKan domen long wan seva nomo, evri wan i gat data blong hem. Long Cloud Panel Domen, SuperAdmin i lukaotem ol domen mo CloudFlare.",
|
||||
"cloud-panel-domain": "Cloud Panel Domen",
|
||||
"cloud-superadmin-username": "Nem blong yusa blong SuperAdmin",
|
||||
"cloud-saved": "Oli sevem finis.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel i blong SuperAdmin nomo, long Cloud Panel Domen.",
|
||||
"cloud-customer-name": "Nem blong kastoma",
|
||||
"cloud-customer-email": "Adres blong imel blong kastoma",
|
||||
"cloud-billing-email": "Adres blong imel blong bil",
|
||||
"cloud-start-date": "Dei blong stat",
|
||||
"cloud-end-date": "Dei blong finis",
|
||||
"cloudflare-api-key": "Ki blong CloudFlare API",
|
||||
"cloudflare-api-key-desc": "Wetem wan ki blong CloudFlare API, wan domen we zon blong hem i stap long akaon blong yu long CloudFlare i kasem wan DNS rekod we i pas long proxy mo wan setifiket blong CloudFlare Origin. Ol narafala domen oli kasem wan setifiket blong Let's Encrypt. Ki ia i stap long seva mo bambae oli no soem bakegen.",
|
||||
"cloudflare-api-key-set": "Wan ki blong CloudFlare API i stap.",
|
||||
"cloudflare-api-key-not-set": "I no gat ki blong CloudFlare API: evri domen i kasem wan setifiket blong Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Movem fael we i joen i go long Azure Blob Storage",
|
||||
@@ -3276,7 +3291,7 @@
|
||||
"import-report-open-board": "Openem bod",
|
||||
"import-parts-instruction": "Ol pat we oli tikim nomo bae oli karem i kam. Sem jois i wok taem yu karem i go.",
|
||||
"import-wekan-file": "Karem i kam long wan fael .json o .zip we oli karem i go:",
|
||||
"import-file-label": "Choose the export file, or paste its text below:",
|
||||
"import-file-label": "Jusum fael blong eksport, o pestem teks blong hem daon long hia:",
|
||||
"sum-of-number-fields": "Sam blong ol namba kastom fil we oli soem antap long lis",
|
||||
"date-range-of-fields": "Taem bitwin ol deit long ol spesel fil we oli makem blong soemaot long antap blong lis",
|
||||
"more-options": "Moa jois",
|
||||
@@ -3303,9 +3318,9 @@
|
||||
"wip-limit-group-select-swimlane": "Jusum Rod",
|
||||
"wip-limit-group-apply-swimlane": "Yusum long swimlane",
|
||||
"subtask-inherit-parent-labels": "Tekem ol mak blong stamba kad",
|
||||
"subtask-mark-done": "Mark subtask as done",
|
||||
"subtask-mark-not-done": "Mark subtask as not done",
|
||||
"subtask-done-no-permission": "You cannot change whether this subtask is done",
|
||||
"subtask-mark-done": "Makem smol wok se i finis",
|
||||
"subtask-mark-not-done": "Makem smol wok se i no finis",
|
||||
"subtask-done-no-permission": "Yu no save jenisim sapos smol wok ia i finis o no",
|
||||
"chart-forecast-none-remaining": "I no gat samting moa blong wokem - evri kad i finis.",
|
||||
"chart-forecast-no-velocity": "__remaining__ kad i stap open yet; i no gat wan we i jas finis blong tokbaot deit.",
|
||||
"chart-forecast-projected": "Long spid blong resen taem blong __average__ kad/wik, __remaining__ kad we oli stap open yet i sapos blong finis long __date__.",
|
||||
@@ -4109,8 +4124,8 @@
|
||||
"scrum-new-sprint": "Niufala sprint",
|
||||
"scrum-releases": "Ol rilisim mo ol niufala haf blong prodak",
|
||||
"scrum-release": "Rilisim",
|
||||
"scrum-release-scope": "Cards in this release",
|
||||
"scrum-releases-select-help": "A card can be in several releases. Hold Ctrl (Cmd on a Mac) to choose more than one; clear them all to remove the card from every release.",
|
||||
"scrum-release-scope": "Ol kad long rilis ia",
|
||||
"scrum-releases-select-help": "Wan kad i save stap long plante rilis. Holem Ctrl (Cmd long Mac) blong jusum moa long wan; klinim evriwan blong tekemaot kad long evri rilis.",
|
||||
"scrum-select-sprint": "Jusum wan sprint blong luk ripot blong hem.",
|
||||
"scrum-backlog": "Lis blong wok we i stap wet",
|
||||
"scrum-backlog-help": "Putum wok long oda blong lis blong wok we i stap wet. Putum ol kad long wan sprint we oli planem o we i aktif.",
|
||||
@@ -4180,22 +4195,22 @@
|
||||
"scrum-import-resume": "Finisim wok blong tekem i kam",
|
||||
"scrum-import-discard": "Kanselem wok blong tekem i kam we i no finis",
|
||||
"scrum-import-recovery-hint": "Wok blong tekem i kam we i stop taem sistem i stat bakegen, yu save finisim long ples ia taem i stop finis. Sipos oli no sevem ful plan blong hem, yu save kanselem nomo; hemia i no jenisim wan samting long bod.",
|
||||
"scrum-import-into-board": "Import Scrum planning into this board",
|
||||
"scrum-import-into-board-hint": "Choose a WeKan board export or Scrum transfer file. Sprints and releases this board already has are linked, never duplicated. Cards are matched by ID, or by card number and title; cards that do not match are listed and left unchanged. Preview first to see what will change.",
|
||||
"scrum-import-into-board": "Tekem plan blong Scrum i kam long bod ia",
|
||||
"scrum-import-into-board-hint": "Jusum wan eksport blong WeKan bod o wan fael blong Scrum. Ol sprint mo rilis we bod ia i gat finis oli joen, oli neva kam tu taem. Oli faenem ol kad long ID, o long namba mo nem blong kad; ol kad we oli no stret oli stap long lis mo oli no jenis. Fes, lukluk bifo blong luk wanem bambae i jenis.",
|
||||
"scrum-import-preview": "Luk fastaem",
|
||||
"scrum-import-choose-file": "Choose a file first.",
|
||||
"scrum-import-invalid-file": "This file is not valid JSON.",
|
||||
"scrum-import-preview-sprints": "Sprints: __created__ new, __matched__ already on this board, __skipped__ not imported",
|
||||
"scrum-import-preview-releases": "Releases: __created__ new, __matched__ already on this board, __skipped__ not imported",
|
||||
"scrum-import-preview-cards": "Cards: __updated__ to update, __unchanged__ unchanged, __unmatched__ not matched",
|
||||
"scrum-import-preview-nothing": "Nothing to change: this board already has this planning.",
|
||||
"scrum-import-into-board-done": "The Scrum planning was imported and recorded in History.",
|
||||
"scrum-import-card-not-matched": "A card was not found on this board: __reference__",
|
||||
"scrum-import-card-ambiguous": "A card matches more than one card on this board: __reference__",
|
||||
"scrum-import-card-on-another-board": "A card belongs to another board and was left unchanged: __reference__",
|
||||
"scrum-import-record-ambiguous": "A sprint or release matches more than one on this board: __reference__",
|
||||
"scrum-import-record-not-imported": "A sprint, release or event could not be imported: __reference__",
|
||||
"scrum-import-sprint-finished": "A card was not moved into a finished sprint: __reference__",
|
||||
"scrum-import-choose-file": "Fes, jusum wan fael.",
|
||||
"scrum-import-invalid-file": "Fael ia i no stret JSON.",
|
||||
"scrum-import-preview-sprints": "Ol sprint: __created__ niu, __matched__ oli stap finis long bod ia, __skipped__ oli no kam",
|
||||
"scrum-import-preview-releases": "Ol rilis: __created__ niu, __matched__ oli stap finis long bod ia, __skipped__ oli no kam",
|
||||
"scrum-import-preview-cards": "Ol kad: __updated__ blong apdetem, __unchanged__ oli no jenis, __unmatched__ oli no faenem",
|
||||
"scrum-import-preview-nothing": "I no gat samting blong jenisim: bod ia i gat plan ia finis.",
|
||||
"scrum-import-into-board-done": "Plan blong Scrum i kam finis mo i stap long Histori.",
|
||||
"scrum-import-card-not-matched": "Wan kad i no stap long bod ia: __reference__",
|
||||
"scrum-import-card-ambiguous": "Wan kad i stret wetem moa long wan kad long bod ia: __reference__",
|
||||
"scrum-import-card-on-another-board": "Wan kad i blong narafala bod mo i no jenis: __reference__",
|
||||
"scrum-import-record-ambiguous": "Wan sprint o rilis i stret wetem moa long wan long bod ia: __reference__",
|
||||
"scrum-import-record-not-imported": "I no save karem wan sprint, rilis o samting we i hapen i kam: __reference__",
|
||||
"scrum-import-sprint-finished": "Wan kad i no muv i go long wan sprint we i finis: __reference__",
|
||||
"sync-conflict-heading": "Stretem ol konflik blong sinkronaes",
|
||||
"sync-conflict-hint": "Jusum wan valiu blong wanwan konflik. Sinkronaes i traem bakegen afta wanwan jus. I no gat wan samting we oli sendem i go long sistem blong sos.",
|
||||
"sync-conflict-local": "Valiu blong WeKan naoia",
|
||||
@@ -4296,10 +4311,10 @@
|
||||
"sync-original-time": "Fas estimet blong taem (aoa)",
|
||||
"sync-remaining-time": "Estimet blong taem we i stap yet (aoa)",
|
||||
"sync-time-estimate-hint": "Ol estimet blong taem oli yusum ol fil blong namba blong fas taem/taem we i stap yet we import blong Jira i mekem. Wan fil nomo we i semmak i mas stap long bod ia. Oli lukluk long ol lokol jenis olsem ol konflik; oli ignorim ol valiu we oli no stap long sos mo wan null we oli talem klia i kliarem valiu.",
|
||||
"sync-planning-sprint": "Sprint (Scrum planning)",
|
||||
"sync-planning-releases": "Releases (Scrum planning)",
|
||||
"sync-planning-fields": "Sprint and releases",
|
||||
"sync-planning-hint": "Sprint and releases are synced only while Scrum is enabled on this board. A sprint or release that is missing is created on this board, with the dates the source gives; an existing one is matched by its source ID first, then by name. A local planning change stays until the source changes that issue's planning, and a first Sync never removes planning.",
|
||||
"sync-planning-sprint": "Sprint (plan blong Scrum)",
|
||||
"sync-planning-releases": "Ol rilis (plan blong Scrum)",
|
||||
"sync-planning-fields": "Sprint mo ol rilis",
|
||||
"sync-planning-hint": "Sprint mo ol rilis oli sinkem nomo taem Scrum i on long bod ia. Wan sprint o rilis we i no stap, oli mekem long bod ia, wetem ol deit we sos i givim; wan we i stap finis, fes oli faenem long ID blong sos, afta long nem. Wan jenis long plan long ples ia i stap gogo sos i jenisim plan blong isiu ia, mo fes Sink i neva tekemaot plan.",
|
||||
"activity-recovery-heading": "Ol notifikasen blong aktiviti we oli stap wet",
|
||||
"activity-recovery-description": "Oli traem ol notifikasen we oli no finis bakegen otomatik. Lukluk long ol aktiviti we oli no stap o we oli jenis bifo yu traem bakegen. Traem bakegen i neva mekem wan aktiviti bakegen.",
|
||||
"activity-recovery-empty": "I no gat notifikasen blong aktiviti we i stap wet.",
|
||||
@@ -4380,53 +4395,53 @@
|
||||
"rule-email-legacy-attempt-exists": "Oli sanem imel ya finis, traem sanem finis, o sakemaot finis.",
|
||||
"rule-email-legacy-not-legacy": "Oli linkim imel ya bakegen finis.",
|
||||
"rule-email-legacy-failed": "Oli no save finisim wok ya. Lodem pej bakegen mo traem bakegen.",
|
||||
"stuck-sync-operation-heading": "List Sync operations that cannot be replayed",
|
||||
"stuck-sync-operation-description": "Each of these saved List Sync operations stopped part way and can no longer be finished, because its list was removed, recreated or reconfigured, or the user who started it lost write access. While one is pending, that list cannot Sync. Discard it to let the list Sync again: changes already applied stay, the remaining saved changes are never written, and the next Sync compares the list with its source again.",
|
||||
"stuck-sync-operation-list": "Board / list",
|
||||
"stuck-sync-operation-heading": "Ol wok blong Lis Sink we i no save mekem bakegen",
|
||||
"stuck-sync-operation-description": "Evri wan long ol wok blong Lis Sink ia we oli sevem i stop long medel mo i no save finis bakegen, from lis blong hem oli tekemaot, mekem bakegen o jenisim seting, o yusa we i statem i lusum raet blong raet. Taem wan i stap wet, lis ia i no save Sink. Sakemaot blong letem lis i Sink bakegen: ol jenis we i hapen finis oli stap, ol narafala jenis we oli sevem oli neva raet, mo nekis Sink i lukluk lis wetem sos blong hem bakegen.",
|
||||
"stuck-sync-operation-list": "Bod / lis",
|
||||
"stuck-sync-operation-progress": "Wok we i gohed",
|
||||
"stuck-sync-operation-reason": "Risen",
|
||||
"stuck-sync-operation-applied": "__applied__ of __total__ changes applied",
|
||||
"stuck-sync-operation-reason-scope-changed": "The list was removed, recreated or its Sync settings changed",
|
||||
"stuck-sync-operation-reason-access-denied": "The user who started it no longer has write access to the whole list",
|
||||
"stuck-sync-operation-reason-trigger-unknown": "Saved by an earlier WeKan version without what started it",
|
||||
"stuck-sync-operation-reason-intent-missing": "The record of who started it is missing",
|
||||
"stuck-sync-operation-reason-unknown": "Could not be checked now",
|
||||
"stuck-sync-operation-replayable-now": "It can be replayed again now and will finish by itself, so it cannot be discarded.",
|
||||
"stuck-sync-operation-applied": "__applied__ long __total__ jenis oli hapen",
|
||||
"stuck-sync-operation-reason-scope-changed": "Oli tekemaot lis, mekem bakegen, o seting blong Sink blong hem i jenis",
|
||||
"stuck-sync-operation-reason-access-denied": "Yusa we i statem i no moa gat raet blong raet long fulwan lis",
|
||||
"stuck-sync-operation-reason-trigger-unknown": "Wan olfala vesen blong WeKan i sevem we i no gat samting we i statem",
|
||||
"stuck-sync-operation-reason-intent-missing": "Rekod blong hu i statem i no stap",
|
||||
"stuck-sync-operation-reason-unknown": "I no save jekem naoia",
|
||||
"stuck-sync-operation-replayable-now": "I save mekem bakegen naoia mo bambae i finis hem wan, ale yu no save sakemaot.",
|
||||
"stuck-sync-operation-discard": "Sakemaot",
|
||||
"stuck-sync-operation-discard-confirm": "Discard this saved List Sync operation? Changes it already applied stay; the rest are never written. The list can then Sync again.",
|
||||
"stuck-sync-operation-discard-confirm": "Sakemaot wok blong Lis Sink ia we oli sevem? Ol jenis we i mekem finis oli stap; ol narafala oli neva raet. Afta lis i save Sink bakegen.",
|
||||
"stuck-sync-operation-refresh": "Rifres",
|
||||
"stuck-sync-operation-empty": "No List Sync operations are waiting for review.",
|
||||
"stuck-sync-operation-truncated": "Only the oldest 50 are shown. Discard some and refresh to see more.",
|
||||
"stuck-sync-operation-unavailable": "Could not load the List Sync operations. Refresh to try again.",
|
||||
"stuck-sync-operation-missing": "This operation is no longer pending.",
|
||||
"stuck-sync-operation-not-stuck": "This operation has not been found stuck, so it cannot be discarded.",
|
||||
"stuck-sync-operation-replayable": "This operation can be replayed again now and will finish by itself, so it was not discarded.",
|
||||
"stuck-sync-operation-busy": "This list is syncing right now. Try again in a moment.",
|
||||
"stuck-sync-operation-empty": "I no gat wok blong Lis Sink we i stap wet blong lukluk.",
|
||||
"stuck-sync-operation-truncated": "50 we oli moa olfala nomo oli soem. Sakemaot sam mo rifres blong luk moa.",
|
||||
"stuck-sync-operation-unavailable": "I no save lodem ol wok blong Lis Sink. Rifres blong traem bakegen.",
|
||||
"stuck-sync-operation-missing": "Wok ia i no moa stap wet.",
|
||||
"stuck-sync-operation-not-stuck": "Oli no faenem se wok ia i stak, ale yu no save sakemaot.",
|
||||
"stuck-sync-operation-replayable": "Wok ia i save mekem bakegen naoia mo bambae i finis hem wan, ale oli no sakemaot.",
|
||||
"stuck-sync-operation-busy": "Lis ia i stap sink stret naoia. Traem bakegen smol taem afta.",
|
||||
"stuck-sync-operation-failed": "Oli no save finisim wok ya. Lodem pej bakegen mo traem bakegen.",
|
||||
"interrupted-import-heading": "Board imports that stopped before finishing",
|
||||
"interrupted-import-description": "Each of these board imports stopped part way - the server restarted, or the import failed - and left a partial board. An import cannot be continued, because its source file is not kept. Keep the board as it is, or discard it and import the file again. Discarding removes the board this import created and everything on it, including anything added to it since.",
|
||||
"interrupted-import-heading": "Ol import blong bod we oli stop bifo oli finis",
|
||||
"interrupted-import-description": "Evri wan long ol import blong bod ia i stop long medel - seva i stat bakegen, o import i no wok - mo i livim wan bod we i no fulwan. Yu no save gohed wetem wan import, from oli no kipim fael blong hem. Kipim bod olsem we i stap, o sakemaot mo karem fael i kam bakegen. Sakemaot i tekemaot bod we import ia i mekem mo evri samting long hem, wetem eni samting we oli putum afta.",
|
||||
"interrupted-import-board": "Bod",
|
||||
"interrupted-import-progress": "Where it stopped",
|
||||
"interrupted-import-created": "Created so far",
|
||||
"interrupted-import-source": "Imported from __source__",
|
||||
"interrupted-import-state-stopped": "Stopped without finishing, at __stage__",
|
||||
"interrupted-import-state-failed": "Failed with __error__, at __stage__",
|
||||
"interrupted-import-state-discarding": "Being discarded; discard again to finish",
|
||||
"interrupted-import-scrum": "Its Scrum stage has its own checkpoint (__state__, __next__ of __total__ steps). If you keep the board, finish or discard it from the board's Scrum import recovery.",
|
||||
"interrupted-import-counts": "__swimlanes__ swimlanes, __lists__ lists, __cards__ cards, __checklists__ checklists, __comments__ comments, __attachments__ attachments",
|
||||
"interrupted-import-no-board": "No board was created",
|
||||
"interrupted-import-keep": "Keep",
|
||||
"interrupted-import-progress": "Wea i stop",
|
||||
"interrupted-import-created": "Samting we i mekem kasem naoia",
|
||||
"interrupted-import-source": "I kam long __source__",
|
||||
"interrupted-import-state-stopped": "I stop be i no finis, long __stage__",
|
||||
"interrupted-import-state-failed": "I no wok wetem __error__, long __stage__",
|
||||
"interrupted-import-state-discarding": "I stap sakemaot; sakemaot bakegen blong finisim",
|
||||
"interrupted-import-scrum": "Stej blong Scrum blong hem i gat jekpoen blong hem wan (__state__, __next__ long __total__ step). Sapos yu kipim bod, finisim o sakemaot long Scrum import rekavri blong bod.",
|
||||
"interrupted-import-counts": "__swimlanes__ rod, __lists__ lis, __cards__ kad, __checklists__ jeklis, __comments__ toktok, __attachments__ atajmen",
|
||||
"interrupted-import-no-board": "I no mekem wan bod",
|
||||
"interrupted-import-keep": "Kipim",
|
||||
"interrupted-import-discard": "Sakemaot",
|
||||
"interrupted-import-keep-confirm": "Keep this partial board as it is? Nothing is removed, and the import is no longer listed here.",
|
||||
"interrupted-import-discard-confirm": "Discard this import? The board it created and everything on it are removed permanently.",
|
||||
"interrupted-import-keep-confirm": "Kipim bod ia we i no fulwan olsem we i stap? I no tekemaot wan samting, mo import i no moa stap long lis ia.",
|
||||
"interrupted-import-discard-confirm": "Sakemaot import ia? Bod we i mekem mo evri samting long hem bambae oli lus olgeta.",
|
||||
"interrupted-import-refresh": "Rifres",
|
||||
"interrupted-import-empty": "No interrupted board imports are waiting for review.",
|
||||
"interrupted-import-truncated": "Only the oldest 50 are shown. Keep or discard some and refresh to see more.",
|
||||
"interrupted-import-unavailable": "Could not load the interrupted board imports. Refresh to try again.",
|
||||
"interrupted-import-missing": "This import is no longer recorded.",
|
||||
"interrupted-import-not-interrupted": "This import is not waiting for a decision any more.",
|
||||
"interrupted-import-foreign-board": "The board with this id was not created by this import, so it was not touched.",
|
||||
"interrupted-import-scrum-busy": "Its Scrum stage is still being written or recovered. Try again in a few minutes.",
|
||||
"interrupted-import-empty": "I no gat import blong bod we i stop we i stap wet blong lukluk.",
|
||||
"interrupted-import-truncated": "50 we oli moa olfala nomo oli soem. Kipim o sakemaot sam mo rifres blong luk moa.",
|
||||
"interrupted-import-unavailable": "I no save lodem ol import blong bod we oli stop. Rifres blong traem bakegen.",
|
||||
"interrupted-import-missing": "Import ia i no moa stap long rekod.",
|
||||
"interrupted-import-not-interrupted": "Import ia i no moa wet blong wan disisen.",
|
||||
"interrupted-import-foreign-board": "Import ia i no mekem bod wetem id ia, ale i no tajem.",
|
||||
"interrupted-import-scrum-busy": "Stej blong Scrum blong hem i stap raet o rekava yet. Traem bakegen long sam minit.",
|
||||
"interrupted-import-failed": "Oli no save finisim wok ya. Lodem pej bakegen mo traem bakegen.",
|
||||
"saml-login-not-started": "Yu no bin statem SAML login ia long tab ia blong brausa. Plis go insaed bakegen.",
|
||||
"move-selection-before": "Bifo",
|
||||
@@ -4436,13 +4451,13 @@
|
||||
"history-request-hint": "Taem yu traem bakegen, hem i ripitim semfala rikwest nomo, mekem se hem i no save tekemaot wan nara jenis.",
|
||||
"history-request-retry": "Traem bakegen",
|
||||
"history-request-forget": "Fogetem",
|
||||
"scrum-history-checkpoint-stuck": "An undo, redo or restore of Scrum changes stopped on a conflict. Scrum edits on this board are blocked until it is resolved.",
|
||||
"scrum-history-checkpoint-counts": "__applied__ of __total__ changes were written, __pending__ were not, and __conflicted__ were changed by someone else since.",
|
||||
"scrum-history-checkpoint-hint": "Rolling back puts every changed record back as it was before; it is offered only when nobody else has changed those records since. Keeping the board as it is removes the stopped operation and changes no records. Either way, it is recorded in Admin Panel, Problems, Recovery.",
|
||||
"scrum-history-checkpoint-rollback": "Roll back",
|
||||
"scrum-history-checkpoint-discard": "Keep the board as it is",
|
||||
"scrum-history-checkpoint-discard-confirm": "Keep every record as it is now, including the changes the stopped operation already wrote?",
|
||||
"scrum-history-checkpoint-ask-admin": "A board administrator can roll it back or keep the board as it is.",
|
||||
"scrum-history-checkpoint-stuck": "Wan andu, ridu o ristoa blong ol jenis blong Scrum i stop long wan rao. Ol jenis blong Scrum long bod ia oli blok gogo oli stretem.",
|
||||
"scrum-history-checkpoint-counts": "__applied__ long __total__ jenis oli raet, __pending__ oli no raet, mo __conflicted__ narafala man i jenisim afta.",
|
||||
"scrum-history-checkpoint-hint": "Tanem i go bak i putum evri rekod we i jenis olsem we i stap bifo; i stap nomo taem i no gat narafala man we i jenisim ol rekod ia afta. Kipim bod olsem we i stap i tekemaot wok we i stop mo i no jenisim eni rekod. Wanem rod yu jusum, i stap long Panel blong admin, Ol problem, Rekavri.",
|
||||
"scrum-history-checkpoint-rollback": "Tanem i go bak",
|
||||
"scrum-history-checkpoint-discard": "Kipim bod olsem we i stap",
|
||||
"scrum-history-checkpoint-discard-confirm": "Kipim evri rekod olsem we i stap naoia, wetem ol jenis we wok we i stop i raetem finis?",
|
||||
"scrum-history-checkpoint-ask-admin": "Wan admin blong bod i save tanem i go bak o kipim bod olsem we i stap.",
|
||||
"header-login": "Go insaed tru long ol heda blong HTTP",
|
||||
"login-setting-clear-secret": "Tekemaot valiu we i stap long panel blong admin",
|
||||
"login-setting-after-restart": "I stat wok afta we WeKan i stat bakegen",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Ɲɔgɔnya ma ɲɛ",
|
||||
"cloud-settings-saved": "Mara yɔrɔ labɛnniw maralen don",
|
||||
"cloud-settings-save-failed": "Mara yɔrɔ labɛnniw marali ma ɲɛ",
|
||||
"cloud": "Sankaba",
|
||||
"cloud-settings-desc": "Ni WeKan ye Weddy kɔnɔ, Cloud Panel bɛ WeKan domɛni caman baara ka bɔ server kelen na, u kelen-kelen bɛɛ ni a yɛrɛ ka kunnafoniw. Cloud Panel Domɛni kan, SuperAdmin bɛ domɛniw ni CloudFlare ɲɛmɔgɔya.",
|
||||
"cloud-panel-domain": "Cloud Panel Domɛni",
|
||||
"cloud-superadmin-username": "SuperAdmin baarakɛla tɔgɔ",
|
||||
"cloud-saved": "A marala.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel ye SuperAdmin dama ta ye, Cloud Panel Domɛni kan.",
|
||||
"cloud-customer-name": "Sannikɛla tɔgɔ",
|
||||
"cloud-customer-email": "Sannikɛla imɛli ladɛrɛsi",
|
||||
"cloud-billing-email": "Sarali imɛli ladɛrɛsi",
|
||||
"cloud-start-date": "Daminɛ don",
|
||||
"cloud-end-date": "Laban don",
|
||||
"cloudflare-api-key": "CloudFlare API kilé",
|
||||
"cloudflare-api-key-desc": "Ni CloudFlare API kilé ye, domɛni min ka zone bɛ i ka CloudFlare jatebɔ la, o bɛ proxy DNS sɛbɛn ni CloudFlare Origin sɛbɛn sɔrɔ. Domɛni tɔw bɛ Let's Encrypt sɛbɛn sɔrɔ. Kilé bɛ mara server kan, a tɛna jira tugun abada.",
|
||||
"cloudflare-api-key-set": "CloudFlare API kilé marala.",
|
||||
"cloudflare-api-key-not-set": "CloudFlare API kilé si ma mara: domɛni bɛɛ bɛ Let's Encrypt sɛbɛn sɔrɔ.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Ɲɔgɔnnan bila Azure Blob Storage la",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "সংযোগ ব্যর্থ",
|
||||
"cloud-settings-saved": "সংরক্ষণাগার সেটিংস সংরক্ষিত হয়েছে",
|
||||
"cloud-settings-save-failed": "সংরক্ষণাগার সেটিংস সংরক্ষণ ব্যর্থ হয়েছে",
|
||||
"cloud": "ক্লাউড",
|
||||
"cloud-settings-desc": "Weddy-তে WeKan দিয়ে Cloud Panel একটি সার্ভার থেকে অনেক WeKan ডোমেইন পরিবেশন করে, প্রতিটির নিজস্ব ডেটা থাকে। Cloud Panel ডোমেইনে SuperAdmin ডোমেইনগুলো এবং CloudFlare পরিচালনা করেন।",
|
||||
"cloud-panel-domain": "Cloud Panel ডোমেইন",
|
||||
"cloud-superadmin-username": "SuperAdmin ব্যবহারকারীর নাম",
|
||||
"cloud-saved": "সংরক্ষিত হয়েছে।",
|
||||
"cloud-panel-not-allowed": "Cloud Panel কেবল SuperAdmin-এর জন্য, Cloud Panel ডোমেইনে।",
|
||||
"cloud-customer-name": "গ্রাহকের নাম",
|
||||
"cloud-customer-email": "গ্রাহকের ইমেইল ঠিকানা",
|
||||
"cloud-billing-email": "বিলিং ইমেইল ঠিকানা",
|
||||
"cloud-start-date": "শুরুর তারিখ",
|
||||
"cloud-end-date": "শেষের তারিখ",
|
||||
"cloudflare-api-key": "CloudFlare API কী",
|
||||
"cloudflare-api-key-desc": "CloudFlare API কী থাকলে, যে ডোমেইনের জোন আপনার CloudFlare অ্যাকাউন্টে আছে সেটি একটি প্রক্সি করা DNS রেকর্ড এবং একটি CloudFlare Origin সার্টিফিকেট পায়। অন্যান্য ডোমেইন একটি Let's Encrypt সার্টিফিকেট পায়। কী-টি সার্ভারে সংরক্ষিত থাকে এবং আর কখনো দেখানো হয় না।",
|
||||
"cloudflare-api-key-set": "একটি CloudFlare API কী সংরক্ষিত আছে।",
|
||||
"cloudflare-api-key-not-set": "কোনো CloudFlare API কী সংরক্ষিত নেই: প্রতিটি ডোমেইন একটি Let's Encrypt সার্টিফিকেট পায়।",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "সংযুক্তি Azure Blob Storage-এ সরান",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "སྦྲེལ་མཐུད་ཕམ་པ།",
|
||||
"cloud-settings-saved": "ཉར་ཚགས་སྒྲིག་ཉར་ཟིན།",
|
||||
"cloud-settings-save-failed": "ཉར་ཚགས་སྒྲིག་ཉར་མ་ཐུབ།",
|
||||
"cloud": "སྤྲིན།",
|
||||
"cloud-settings-desc": "Weddy ནང་གི་ WeKan དང་མཉམ་དུ། Cloud Panel གྱིས་ཞབས་ཞུ་འཕྲུལ་ཆས་གཅིག་ནས་ WeKan ཁྱབ་ཁོངས་མང་པོར་ཞབས་ཞུ་སྒྲུབ། རེ་རེར་རང་གི་གཞི་གྲངས་ཡོད། Cloud Panel ཁྱབ་ཁོངས་སུ་ SuperAdmin གྱིས་ཁྱབ་ཁོངས་དང་ CloudFlare དོ་དམ་བྱེད།",
|
||||
"cloud-panel-domain": "Cloud Panel ཁྱབ་ཁོངས།",
|
||||
"cloud-superadmin-username": "SuperAdmin བེད་སྤྱོད་མཁན་གྱི་མིང་།",
|
||||
"cloud-saved": "ཉར་ཚགས་བྱས་ཟིན།",
|
||||
"cloud-panel-not-allowed": "Cloud Panel ནི་ SuperAdmin ཁོ་ན་ལ་ཡིན། Cloud Panel ཁྱབ་ཁོངས་སུ།",
|
||||
"cloud-customer-name": "མཁོ་མཁན་གྱི་མིང་།",
|
||||
"cloud-customer-email": "མཁོ་མཁན་གྱི་གློག་འཕྲིན་ཁ་བྱང་།",
|
||||
"cloud-billing-email": "འཇལ་ཐོའི་གློག་འཕྲིན་ཁ་བྱང་།",
|
||||
"cloud-start-date": "འགོ་འཛུགས་ཚེས་གྲངས།",
|
||||
"cloud-end-date": "མཇུག་སྒྲིལ་ཚེས་གྲངས།",
|
||||
"cloudflare-api-key": "CloudFlare API ལྡེ་མིག",
|
||||
"cloudflare-api-key-desc": "CloudFlare API ལྡེ་མིག་ཡོད་ན། ཁྱེད་ཀྱི་ CloudFlare རྩིས་ཐོའི་ནང་ zone ཡོད་པའི་ཁྱབ་ཁོངས་ལ་ proxy བྱས་པའི་ DNS ཟིན་ཐོ་དང་ CloudFlare Origin ལག་ཁྱེར་ཐོབ། ཁྱབ་ཁོངས་གཞན་ལ་ Let's Encrypt ལག་ཁྱེར་ཐོབ། ལྡེ་མིག་ཞབས་ཞུ་འཕྲུལ་ཆས་སུ་ཉར་ཞིང་ཡང་བསྐྱར་སྟོན་མི་སྲིད།",
|
||||
"cloudflare-api-key-set": "CloudFlare API ལྡེ་མིག་ཉར་ཟིན།",
|
||||
"cloudflare-api-key-not-set": "CloudFlare API ལྡེ་མིག་གང་ཡང་ཉར་མེད། ཁྱབ་ཁོངས་རེ་རེར་ Let's Encrypt ལག་ཁྱེར་ཐོབ།",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "ཟུར་སྣོན་ Azure Blob Storage ལ་སྤོ།",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "C'hwitet eo ar c'hennask",
|
||||
"cloud-settings-saved": "Enrollet eo arventennoù ar c'hadaviñ",
|
||||
"cloud-settings-save-failed": "C'hwitet eo enrollañ arventennoù ar c'hadaviñ",
|
||||
"cloud": "Koumoul",
|
||||
"cloud-settings-desc": "Gant WeKan e Weddy e servij ar Cloud Panel kalz domanioù WeKan diwar ur servijer hepken, pep hini gant e roadennoù dezhañ. War domani ar Cloud Panel e vera ar SuperAdmin an domanioù ha CloudFlare.",
|
||||
"cloud-panel-domain": "Domani ar Cloud Panel",
|
||||
"cloud-superadmin-username": "Anv implijer ar SuperAdmin",
|
||||
"cloud-saved": "Enrollet.",
|
||||
"cloud-panel-not-allowed": "Evit ar SuperAdmin hepken eo ar Cloud Panel, war domani ar Cloud Panel.",
|
||||
"cloud-customer-name": "Anv ar pratik",
|
||||
"cloud-customer-email": "Chomlec'h postel ar pratik",
|
||||
"cloud-billing-email": "Chomlec'h postel ar fakturennoù",
|
||||
"cloud-start-date": "Deiziad kregiñ",
|
||||
"cloud-end-date": "Deiziad echuiñ",
|
||||
"cloudflare-api-key": "Alc'hwez API CloudFlare",
|
||||
"cloudflare-api-key-desc": "Gant un alc'hwez API CloudFlare e resev un domani e zonenn en ho kont CloudFlare un enrolladenn DNS dre proksi hag un testeni CloudFlare Origin. An domanioù all a resev un testeni Let's Encrypt. Enrollet eo an alc'hwez war ar servijer ha ne vo morse diskouezet en-dro.",
|
||||
"cloudflare-api-key-set": "Enrollet eo un alc'hwez API CloudFlare.",
|
||||
"cloudflare-api-key-not-set": "N'eus alc'hwez API CloudFlare enrollet ebet: pep domani a resev un testeni Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Dilec'hiañ ar stagadenn da Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Veza nije uspjela",
|
||||
"cloud-settings-saved": "Postavke pohranjivanja spremljene",
|
||||
"cloud-settings-save-failed": "Spremanje postavki pohranjivanja nije uspjelo",
|
||||
"cloud": "Oblak",
|
||||
"cloud-settings-desc": "Uz WeKan u Weddyju, Cloud Panel poslužuje mnogo WeKan domena s jednog servera, svaki sa svojim podacima. Na domeni Cloud Panela, SuperAdmin upravlja domenama i CloudFlareom.",
|
||||
"cloud-panel-domain": "Domena Cloud Panela",
|
||||
"cloud-superadmin-username": "Korisničko ime SuperAdmina",
|
||||
"cloud-saved": "Sačuvano.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel je samo za SuperAdmina, na domeni Cloud Panela.",
|
||||
"cloud-customer-name": "Ime kupca",
|
||||
"cloud-customer-email": "Adresa e-pošte kupca",
|
||||
"cloud-billing-email": "Adresa e-pošte za naplatu",
|
||||
"cloud-start-date": "Datum početka",
|
||||
"cloud-end-date": "Datum završetka",
|
||||
"cloudflare-api-key": "CloudFlare API ključ",
|
||||
"cloudflare-api-key-desc": "S CloudFlare API ključem, domena čija je zona u vašem CloudFlare računu dobija DNS zapis preko proxyja i CloudFlare Origin certifikat. Ostale domene dobijaju Let's Encrypt certifikat. Ključ se sprema na serveru i nikada se više ne prikazuje.",
|
||||
"cloudflare-api-key-set": "CloudFlare API ključ je spremljen.",
|
||||
"cloudflare-api-key-not-set": "Nijedan CloudFlare API ključ nije spremljen: svaka domena dobija Let's Encrypt certifikat.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Premjesti privitak u Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Холболто бүтэсэгүй",
|
||||
"cloud-settings-saved": "Хадагаламжын тохируулганууд хадагалагдаба",
|
||||
"cloud-settings-save-failed": "Хадагаламжын тохируулгануудые хадагалжа шадабагүй",
|
||||
"cloud": "Үүлэн",
|
||||
"cloud-settings-desc": "Weddy соо WeKan-тай хамта Cloud Panel нэгэ серверһээ олон WeKan домэнуудые алба хэдэг, тус бүриндэнь өөрын мэдээсэл бии. Cloud Panel домэн дээрэ SuperAdmin домэнуудые болон CloudFlare-ые хүтэлбэрилдэг.",
|
||||
"cloud-panel-domain": "Cloud Panel домэн",
|
||||
"cloud-superadmin-username": "SuperAdmin хэрэглэгшын нэрэ",
|
||||
"cloud-saved": "Хадагалагдаа.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel гансал SuperAdmin-ай түлөө, Cloud Panel домэн дээрэ.",
|
||||
"cloud-customer-name": "Клиентын нэрэ",
|
||||
"cloud-customer-email": "Клиентын сахим захидалай хаяг",
|
||||
"cloud-billing-email": "Түлбэриин сахим захидалай хаяг",
|
||||
"cloud-start-date": "Эхилхэ үдэр",
|
||||
"cloud-end-date": "Дүүрэхэ үдэр",
|
||||
"cloudflare-api-key": "CloudFlare API түлхюур",
|
||||
"cloudflare-api-key-desc": "CloudFlare API түлхюуртай һаа, таны CloudFlare данса соо зонотой домэн прокси дамжуулһан DNS бэшэлгэ болон CloudFlare Origin гэршэлгэ абадаг. Бусад домэнууд Let's Encrypt гэршэлгэ абадаг. Түлхюур серверт хадагалагдадаг, дахин хэзээш харуулагдахагүй.",
|
||||
"cloudflare-api-key-set": "CloudFlare API түлхюур хадагалагдаһан.",
|
||||
"cloudflare-api-key-not-set": "Ямаршье CloudFlare API түлхюур хадагалагдаагүй: домэн бүхэн Let's Encrypt гэршэлгэ абадаг.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Хабсаргалта Azure Blob Storage руу зөөхэ",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "La connexió ha fallat",
|
||||
"cloud-settings-saved": "S'ha desat la configuració de l'emmagatzematge",
|
||||
"cloud-settings-save-failed": "No s'ha pogut desar la configuració de l'emmagatzematge",
|
||||
"cloud": "Núvol",
|
||||
"cloud-settings-desc": "Amb WeKan a Weddy, el Cloud Panel serveix molts dominis de WeKan des d'un sol servidor, cadascun amb les seves dades. Al domini del Cloud Panel, el SuperAdmin gestiona els dominis i CloudFlare.",
|
||||
"cloud-panel-domain": "Domini del Cloud Panel",
|
||||
"cloud-superadmin-username": "Nom d'usuari del SuperAdmin",
|
||||
"cloud-saved": "Desat.",
|
||||
"cloud-panel-not-allowed": "El Cloud Panel és només per al SuperAdmin, al domini del Cloud Panel.",
|
||||
"cloud-customer-name": "Nom del client",
|
||||
"cloud-customer-email": "Adreça de correu electrònic del client",
|
||||
"cloud-billing-email": "Adreça de correu electrònic de facturació",
|
||||
"cloud-start-date": "Data d'inici",
|
||||
"cloud-end-date": "Data de finalització",
|
||||
"cloudflare-api-key": "Clau API de CloudFlare",
|
||||
"cloudflare-api-key-desc": "Amb una clau API de CloudFlare, un domini la zona del qual és al vostre compte de CloudFlare obté un registre DNS amb servidor intermediari i un certificat CloudFlare Origin. Els altres dominis obtenen un certificat Let's Encrypt. La clau es desa al servidor i no es torna a mostrar mai més.",
|
||||
"cloudflare-api-key-set": "S'ha desat una clau API de CloudFlare.",
|
||||
"cloudflare-api-key-not-set": "No s'ha desat cap clau API de CloudFlare: cada domini obté un certificat Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Mou l'adjunt a Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "La connexió ha fallat",
|
||||
"cloud-settings-saved": "S'ha desat la configuració de l'emmagatzematge",
|
||||
"cloud-settings-save-failed": "No s'ha pogut desar la configuració de l'emmagatzematge",
|
||||
"cloud": "Núvol",
|
||||
"cloud-settings-desc": "Amb WeKan en Weddy, el Cloud Panel servix molts dominis de WeKan des d'un sol servidor, cadascun amb les seues dades. En el domini del Cloud Panel, el SuperAdmin gestiona els dominis i CloudFlare.",
|
||||
"cloud-panel-domain": "Domini del Cloud Panel",
|
||||
"cloud-superadmin-username": "Nom d'usuari del SuperAdmin",
|
||||
"cloud-saved": "Desat.",
|
||||
"cloud-panel-not-allowed": "El Cloud Panel és només per al SuperAdmin, en el domini del Cloud Panel.",
|
||||
"cloud-customer-name": "Nom del client",
|
||||
"cloud-customer-email": "Adreça de correu electrònic del client",
|
||||
"cloud-billing-email": "Adreça de correu electrònic de facturació",
|
||||
"cloud-start-date": "Data d'inici",
|
||||
"cloud-end-date": "Data de finalització",
|
||||
"cloudflare-api-key": "Clau API de CloudFlare",
|
||||
"cloudflare-api-key-desc": "Amb una clau API de CloudFlare, un domini la zona del qual està en el vostre compte de CloudFlare obté un registre DNS amb servidor intermediari i un certificat CloudFlare Origin. Els altres dominis obtenen un certificat Let's Encrypt. La clau es guarda en el servidor i no es torna a mostrar mai més.",
|
||||
"cloudflare-api-key-set": "S'ha guardat una clau API de CloudFlare.",
|
||||
"cloudflare-api-key-not-set": "No s'ha guardat cap clau API de CloudFlare: cada domini obté un certificat Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Mou l'adjunt a Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "La connexió ha fallat",
|
||||
"cloud-settings-saved": "S'ha desat la configuració de l'emmagatzematge",
|
||||
"cloud-settings-save-failed": "No s'ha pogut desar la configuració de l'emmagatzematge",
|
||||
"cloud": "Núvol",
|
||||
"cloud-settings-desc": "Amb WeKan a Weddy, el Cloud Panel serveix molts dominis de WeKan des d'un sol servidor, cadascun amb les seves dades. Al domini del Cloud Panel, el SuperAdmin gestiona els dominis i CloudFlare.",
|
||||
"cloud-panel-domain": "Domini del Cloud Panel",
|
||||
"cloud-superadmin-username": "Nom d'usuari del SuperAdmin",
|
||||
"cloud-saved": "Desat.",
|
||||
"cloud-panel-not-allowed": "El Cloud Panel és només per al SuperAdmin, al domini del Cloud Panel.",
|
||||
"cloud-customer-name": "Nom del client",
|
||||
"cloud-customer-email": "Adreça de correu electrònic del client",
|
||||
"cloud-billing-email": "Adreça de correu electrònic de facturació",
|
||||
"cloud-start-date": "Data d'inici",
|
||||
"cloud-end-date": "Data de finalització",
|
||||
"cloudflare-api-key": "Clau API de CloudFlare",
|
||||
"cloudflare-api-key-desc": "Amb una clau API de CloudFlare, un domini la zona del qual és al vostre compte de CloudFlare obté un registre DNS amb servidor intermediari i un certificat CloudFlare Origin. Els altres dominis obtenen un certificat Let's Encrypt. La clau es desa al servidor i no es torna a mostrar mai més.",
|
||||
"cloudflare-api-key-set": "S'ha desat una clau API de CloudFlare.",
|
||||
"cloudflare-api-key-not-set": "No s'ha desat cap clau API de CloudFlare: cada domini obté un certificat Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Mou l'adjunt a Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "ᎠᏚᏓᎸᏗ Ꮭ ᏱᎨᏎᏍᏗ",
|
||||
"cloud-settings-saved": "ᎠᏍᏆᏂᎪᏙᏗ ᏗᎧᎿᏩᏛᏍᏗ ᏗᏍᏆᏂᎪᏙᏅ",
|
||||
"cloud-settings-save-failed": "ᎠᏍᏆᏂᎪᏙᏗ ᏗᎧᎿᏩᏛᏍᏗ ᏗᏍᏆᏂᎪᏙᏗ Ꮭ ᏱᎨᏎᏍᏗ",
|
||||
"cloud": "ᎤᎶᎩᎸ",
|
||||
"cloud-settings-desc": "WeKan Weddy ᎠᏂ ᏥᎨᏒ, Cloud Panel ᎤᏂᏣᏘ WeKan ᏗᏓᏁᎸ ᏌᏉ ᎠᏓᏁᎯ ᏅᏓᏳᏓᎴᏅ ᏕᎦᏘᏍᏗᏍᎪ, ᏑᏓᎴᎩ ᎤᏩᏒ ᏧᎾᏙᎴᏆᏍᏗ ᎤᏁᎯ. Cloud Panel ᏗᏓᏁᎸ ᎾᎿ, SuperAdmin ᏕᎦᏁᎶᏗᏍᎪ ᏗᏓᏁᎸ ᎠᎴ CloudFlare.",
|
||||
"cloud-panel-domain": "Cloud Panel ᏗᏓᏁᎸ",
|
||||
"cloud-superadmin-username": "SuperAdmin ᎤᏬᏢᏗ ᏚᏙᎥ",
|
||||
"cloud-saved": "ᎠᏍᏆᏂᎪᏔᏅᎯ.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel SuperAdmin ᎤᏩᏒᏉ ᎤᎬᏩᎶᏗ, Cloud Panel ᏗᏓᏁᎸ ᎾᎿ.",
|
||||
"cloud-customer-name": "ᎠᏓᏩᏛᎯ ᏚᏙᎥ",
|
||||
"cloud-customer-email": "ᎠᏓᏩᏛᎯ ᏗᎦᎵᏦᏙᏗ ᎦᏙᎬ",
|
||||
"cloud-billing-email": "ᎠᏴᎾᏗ ᏗᎦᎵᏦᏙᏗ ᎦᏙᎬ",
|
||||
"cloud-start-date": "ᏗᏓᎴᏂᏍᎬ ᎢᎦ",
|
||||
"cloud-end-date": "ᎤᎵᏍᏆᏗᏍᏗ ᎢᎦ",
|
||||
"cloudflare-api-key": "CloudFlare API ᏗᏍᏚᎢᏍᏗ",
|
||||
"cloudflare-api-key-desc": "CloudFlare API ᏗᏍᏚᎢᏍᏗ ᏤᎭ ᎢᏳᏃ, ᏗᏓᏁᎸ ᎾᏍᎩ ᎤᏤᎵ zone ᏣᏤᎵ CloudFlare ᎾᎿ ᏤᎭ ᎠᏩᏘᏍᎪ proxy DNS ᎪᏪᎵ ᎠᎴ CloudFlare Origin ᎪᏪᎵ. ᏅᏩᎾᏓᎴ ᏗᏓᏁᎸ ᎠᏂᏩᏘᏍᎪ Let's Encrypt ᎪᏪᎵ. ᏗᏍᏚᎢᏍᏗ ᎠᏓᏁᎯ ᎾᎿ ᎠᏍᏆᏂᎪᏗ ᎠᎴ ᎥᏝ ᎤᏟ ᎢᎸᎯᏳ ᎬᏂᎨᏒ ᏱᎨᏎᏍᏗ.",
|
||||
"cloudflare-api-key-set": "CloudFlare API ᏗᏍᏚᎢᏍᏗ ᎠᏍᏆᏂᎪᏗ.",
|
||||
"cloudflare-api-key-not-set": "ᎥᏝ CloudFlare API ᏗᏍᏚᎢᏍᏗ ᏯᏍᏆᏂᎪᏗ: ᏂᎦᏓ ᏗᏓᏁᎸ ᎠᏩᏘᏍᎪ Let's Encrypt ᎪᏪᎵ.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "ᎦᏛᎯᏍᏔᏅ Azure Blob Storage ᏫᎦᏅᏗ",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "پەیوەندی شکستی هێنا",
|
||||
"cloud-settings-saved": "ڕێکخستنەکانی بیرگە پاشەکەوت کران",
|
||||
"cloud-settings-save-failed": "پاشەکەوتکردنی ڕێکخستنەکانی بیرگە شکستی هێنا",
|
||||
"cloud": "هەور",
|
||||
"cloud-settings-desc": "بە WeKan لە Weddy، Cloud Panel زۆر دۆمەینی WeKan لە یەک ڕاژەکارەوە پێشکەش دەکات، هەر یەکەیان داتای خۆی هەیە. لە دۆمەینی Cloud Panel، SuperAdmin دۆمەینەکان و CloudFlare بەڕێوە دەبات.",
|
||||
"cloud-panel-domain": "دۆمەینی Cloud Panel",
|
||||
"cloud-superadmin-username": "ناوی بەکارهێنەری SuperAdmin",
|
||||
"cloud-saved": "پاشەکەوت کرا.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel تەنها بۆ SuperAdmin ە، لە دۆمەینی Cloud Panel.",
|
||||
"cloud-customer-name": "ناوی کڕیار",
|
||||
"cloud-customer-email": "ناونیشانی ئیمەیلی کڕیار",
|
||||
"cloud-billing-email": "ناونیشانی ئیمەیلی پسوولە",
|
||||
"cloud-start-date": "ڕێکەوتی دەستپێک",
|
||||
"cloud-end-date": "ڕێکەوتی کۆتایی",
|
||||
"cloudflare-api-key": "کلیلی CloudFlare API",
|
||||
"cloudflare-api-key-desc": "بە کلیلی CloudFlare API، دۆمەینێک کە ناوچەکەی لە هەژماری CloudFlare ەکەتدایە تۆمارێکی DNS ی پرۆکسیکراو و بڕوانامەیەکی CloudFlare Origin وەردەگرێت. دۆمەینەکانی تر بڕوانامەیەکی Let's Encrypt وەردەگرن. کلیلەکە لەسەر ڕاژەکار پاشەکەوت دەکرێت و هەرگیز دووبارە پیشان نادرێتەوە.",
|
||||
"cloudflare-api-key-set": "کلیلێکی CloudFlare API پاشەکەوت کراوە.",
|
||||
"cloudflare-api-key-not-set": "هیچ کلیلێکی CloudFlare API پاشەکەوت نەکراوە: هەموو دۆمەینێک بڕوانامەیەکی Let's Encrypt وەردەگرێت.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "هاوپێچ بگوازەوە بۆ Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "连接失败",
|
||||
"cloud-settings-saved": "存储设置已保存",
|
||||
"cloud-settings-save-failed": "保存存储设置失败",
|
||||
"cloud": "云",
|
||||
"cloud-settings-desc": "在 Weddy 中使用 WeKan 时,Cloud Panel 从一台服务器为多个 WeKan 域名提供服务,每个域名都有自己的数据。在 Cloud Panel 域名上,SuperAdmin 管理这些域名和 CloudFlare。",
|
||||
"cloud-panel-domain": "Cloud Panel 域名",
|
||||
"cloud-superadmin-username": "SuperAdmin 用户名",
|
||||
"cloud-saved": "已保存。",
|
||||
"cloud-panel-not-allowed": "Cloud Panel 仅供 SuperAdmin 在 Cloud Panel 域名上使用。",
|
||||
"cloud-customer-name": "客户名称",
|
||||
"cloud-customer-email": "客户电子邮件地址",
|
||||
"cloud-billing-email": "账单电子邮件地址",
|
||||
"cloud-start-date": "开始日期",
|
||||
"cloud-end-date": "结束日期",
|
||||
"cloudflare-api-key": "CloudFlare API 密钥",
|
||||
"cloudflare-api-key-desc": "使用 CloudFlare API 密钥后,其区域位于您的 CloudFlare 账户中的域名将获得代理的 DNS 记录和 CloudFlare Origin 证书。其他域名将获得 Let's Encrypt 证书。密钥保存在服务器上,且不会再次显示。",
|
||||
"cloudflare-api-key-set": "已保存 CloudFlare API 密钥。",
|
||||
"cloudflare-api-key-not-set": "未保存 CloudFlare API 密钥:每个域名都将获得 Let's Encrypt 证书。",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "将附件移动到 Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Cunnessione fiascata",
|
||||
"cloud-settings-saved": "Parametri d’almacenamentu arregistrati",
|
||||
"cloud-settings-save-failed": "Ùn hè statu pussibule d’arregistrà i paràmetri d’almacenamentu",
|
||||
"cloud": "Nivulu",
|
||||
"cloud-settings-desc": "Cù WeKan in Weddy, u Cloud Panel serve parechji duminii WeKan da un solu servitore, ognunu cù i so dati. À u duminiu di u Cloud Panel, u SuperAdmin gestisce i duminii è CloudFlare.",
|
||||
"cloud-panel-domain": "Duminiu di u Cloud Panel",
|
||||
"cloud-superadmin-username": "Nome d'utilizatore di u SuperAdmin",
|
||||
"cloud-saved": "Salvatu.",
|
||||
"cloud-panel-not-allowed": "U Cloud Panel hè solu per u SuperAdmin, à u duminiu di u Cloud Panel.",
|
||||
"cloud-customer-name": "Nome di u cliente",
|
||||
"cloud-customer-email": "Indirizzu elettronicu di u cliente",
|
||||
"cloud-billing-email": "Indirizzu elettronicu di fatturazione",
|
||||
"cloud-start-date": "Data di principiu",
|
||||
"cloud-end-date": "Data di fine",
|
||||
"cloudflare-api-key": "Chjave API CloudFlare",
|
||||
"cloudflare-api-key-desc": "Cù una chjave API CloudFlare, un duminiu chì a so zona hè in u vostru contu CloudFlare riceve un arregistramentu DNS per proxy è un certificatu CloudFlare Origin. L'altri duminii ricevenu un certificatu Let's Encrypt. A chjave hè salvata nant'à u servitore è ùn hè mai più mustrata.",
|
||||
"cloudflare-api-key-set": "Una chjave API CloudFlare hè salvata.",
|
||||
"cloudflare-api-key-not-set": "Nisuna chjave API CloudFlare hè salvata: ogni duminiu riceve un certificatu Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Move l’appicciu in Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Připojení selhalo",
|
||||
"cloud-settings-saved": "Nastavení úložiště uloženo",
|
||||
"cloud-settings-save-failed": "Uložení nastavení úložiště selhalo",
|
||||
"cloud": "Oblak",
|
||||
"cloud-settings-desc": "S WeKanem ve Weddy obsluhuje Cloud Panel mnoho domén WeKanu z jednoho serveru, každou s vlastními daty. Na doméně Cloud Panelu spravuje SuperAdmin domény a CloudFlare.",
|
||||
"cloud-panel-domain": "Doména Cloud Panelu",
|
||||
"cloud-superadmin-username": "Uživatelské jméno SuperAdmina",
|
||||
"cloud-saved": "Uloženo.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel je pouze pro SuperAdmina, na doméně Cloud Panelu.",
|
||||
"cloud-customer-name": "Jméno zákazníka",
|
||||
"cloud-customer-email": "E-mailová adresa zákazníka",
|
||||
"cloud-billing-email": "Fakturační e-mailová adresa",
|
||||
"cloud-start-date": "Datum začátku",
|
||||
"cloud-end-date": "Datum konce",
|
||||
"cloudflare-api-key": "Klíč CloudFlare API",
|
||||
"cloudflare-api-key-desc": "S klíčem CloudFlare API získá doména, jejíž zóna je ve vašem účtu CloudFlare, DNS záznam přes proxy a certifikát CloudFlare Origin. Ostatní domény získají certifikát Let's Encrypt. Klíč se uloží na serveru a už se nikdy nezobrazí.",
|
||||
"cloudflare-api-key-set": "Klíč CloudFlare API je uložen.",
|
||||
"cloudflare-api-key-not-set": "Není uložen žádný klíč CloudFlare API: každá doména získá certifikát Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Přesunout přílohu do Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Připojení selhalo",
|
||||
"cloud-settings-saved": "Nastavení úložiště uloženo",
|
||||
"cloud-settings-save-failed": "Uložení nastavení úložiště selhalo",
|
||||
"cloud": "Oblak",
|
||||
"cloud-settings-desc": "S WeKanem ve Weddy obsluhuje Cloud Panel mnoho domén WeKanu z jednoho serveru, každou s vlastními daty. Na doméně Cloud Panelu spravuje SuperAdmin domény a CloudFlare.",
|
||||
"cloud-panel-domain": "Doména Cloud Panelu",
|
||||
"cloud-superadmin-username": "Uživatelské jméno SuperAdmina",
|
||||
"cloud-saved": "Uloženo.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel je pouze pro SuperAdmina, na doméně Cloud Panelu.",
|
||||
"cloud-customer-name": "Jméno zákazníka",
|
||||
"cloud-customer-email": "E-mailová adresa zákazníka",
|
||||
"cloud-billing-email": "Fakturační e-mailová adresa",
|
||||
"cloud-start-date": "Datum začátku",
|
||||
"cloud-end-date": "Datum konce",
|
||||
"cloudflare-api-key": "Klíč CloudFlare API",
|
||||
"cloudflare-api-key-desc": "S klíčem CloudFlare API získá doména, jejíž zóna je ve vašem účtu CloudFlare, DNS záznam přes proxy a certifikát CloudFlare Origin. Ostatní domény získají certifikát Let's Encrypt. Klíč se uloží na serveru a už se nikdy nezobrazí.",
|
||||
"cloudflare-api-key-set": "Klíč CloudFlare API je uložen.",
|
||||
"cloudflare-api-key-not-set": "Není uložen žádný klíč CloudFlare API: každá doména získá certifikát Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Přesunout přílohu do Azure Blob Storage",
|
||||
|
||||
+217
-202
@@ -227,20 +227,20 @@
|
||||
"attachment-soft-delete-pop": "Przëłącznik je rëmniãti z ti kôrtë, a jegò lopk òstôwô zachòwóny. Kòżdi, chto mòże editowac kôrtã, mòże gò przëwrócëc z historie kôrtë.",
|
||||
"attachmentDeletePopup-title": "Rëmnąc przëłącznik?",
|
||||
"attachments": "Załączniczi",
|
||||
"attach-card": "Attach card",
|
||||
"attachCardPopup-title": "Attach card",
|
||||
"attach-card-hint": "Paste a card link (Copy card link on the other card), or type a card title on this board",
|
||||
"attached-card-unavailable": "An attached card you cannot see",
|
||||
"detach-card": "Remove attached card",
|
||||
"attach-card-self": "A card cannot be attached to itself",
|
||||
"attach-card-limit": "This card already has as many attached cards as it can have",
|
||||
"attach-card-not-found": "No card you can see was found for that link",
|
||||
"attach-card": "Przëłączë kôrtã",
|
||||
"attachCardPopup-title": "Przëłączë kôrtã",
|
||||
"attach-card-hint": "Wklejë lënk kôrtë (Kòpiérëjë lënk kôrtë na jinszi kôrce) abò napiszë titel kôrtë na ti tôfli",
|
||||
"attached-card-unavailable": "Przëłączonô kôrta, chtërny nie widzysz",
|
||||
"detach-card": "Rëmôj przëłączoną kôrtã",
|
||||
"attach-card-self": "Kôrtë nie mòżna przëłączëc do ni sami",
|
||||
"attach-card-limit": "Na kôrta mô ju tëli przëłączonëch kôrtów, kùli mòże miec",
|
||||
"attach-card-not-found": "Do tegò lënkù ni ma niżódny kôrtë, chtërną widzysz",
|
||||
"auto-watch": "Aùtomatno dozérôj tôflów, czej są ùsôdzóné",
|
||||
"avatar-too-big": "Awatar je za wiôldżi (nôwiãcy __size__)",
|
||||
"back": "Nazôd",
|
||||
"board-change-color": "Zmieni farwã",
|
||||
"board-announcement": "Board announcement",
|
||||
"board-announcement-enabled": "Show it to everyone on this board",
|
||||
"board-announcement": "Òbwieszczenié tôflë",
|
||||
"board-announcement-enabled": "Pòkôżë to wszëtczim na ti tôfli",
|
||||
"board-change-background-image": "Zmieni òbrôzk spòdkù",
|
||||
"board-background-image-url": "URL òbrôzka spòdkù",
|
||||
"add-background-image": "Dodôj obraz tła",
|
||||
@@ -284,7 +284,7 @@
|
||||
"zoom-level": "Rówień zwikszeniô",
|
||||
"enter-zoom-level": "Wpiszë rówień zwikszeniô (50-300%):",
|
||||
"board-view-cal": "Kalãdôrz",
|
||||
"board-view-calendar-mode": "Calendar Mode",
|
||||
"board-view-calendar-mode": "Trib kalãdarza",
|
||||
"board-view-multiboard-cal": "Kalãdôrz wielu tôflów",
|
||||
"board-view-swimlanes": "Stegnë",
|
||||
"board-view-collapse": "Zwiń widok",
|
||||
@@ -686,32 +686,32 @@
|
||||
"chooseBoardSourcePopup-title": "Import tôblëcë",
|
||||
"createLabelPopup-title": "Utwòrz etykietę",
|
||||
"createCustomField": "Utwòrz pole",
|
||||
"custom-field-links": "Link fields to another card",
|
||||
"custom-field-links-hint": "Custom fields with the same name and type on both cards are linked: when a value changes on one card, it is copied to the other card. Fields that only one card has are left alone.",
|
||||
"custom-field-links-none": "This card's custom fields are not linked to any card.",
|
||||
"custom-field-link-unavailable": "A linked card you cannot see",
|
||||
"custom-field-link-fields": "Linked fields:",
|
||||
"custom-field-link-no-fields": "No fields with the same name and type yet",
|
||||
"custom-field-link-inactive": "Paused: the person who made this link can no longer edit both cards, or a card is archived.",
|
||||
"custom-field-unlink": "Unlink",
|
||||
"custom-field-link-target": "Card link (Copy card link), or search this board's cards",
|
||||
"custom-field-link-direction": "Direction",
|
||||
"custom-field-link-both": "Both ways: changes on either card go to the other",
|
||||
"custom-field-link-send": "One way: changes on this card go to the main card",
|
||||
"custom-field-link-sends": "Changes on this card go to that card",
|
||||
"custom-field-link-receives": "Changes on that card come to this card",
|
||||
"custom-field-link-add": "Link fields",
|
||||
"field-link-self": "A card cannot be linked to itself.",
|
||||
"field-link-limit": "This card, or that card, already has the most linked cards it can have.",
|
||||
"field-link-archived": "An archived card cannot be linked.",
|
||||
"field-link-invalid": "That is not a card link.",
|
||||
"field-link-not-found": "Card not found.",
|
||||
"field-link-not-allowed": "You need to be able to edit both cards.",
|
||||
"cardCustomFieldLinksPopup-title": "Linked custom fields",
|
||||
"custom-field-links": "Sparłãczë pòla z jinszą kôrtą",
|
||||
"custom-field-links-hint": "Włôsné pòla z tim samim mionã i ôrtã na òbù kôrtach są sparłãczoné: czej wôrtnota zmieni sã na jedny kôrce, je kòpiérowónô na drëgą kôrtã. Pòla, chtërné mô blós jedna kôrta, òstôwają bez zmianów.",
|
||||
"custom-field-links-none": "Włôsné pòla ti kôrtë nie są sparłãczoné z niżódną kôrtą.",
|
||||
"custom-field-link-unavailable": "Sparłãczonô kôrta, chtërny nie widzysz",
|
||||
"custom-field-link-fields": "Sparłãczoné pòla:",
|
||||
"custom-field-link-no-fields": "Jesz ni ma pòlów z tim samim mionã i ôrtã",
|
||||
"custom-field-link-inactive": "Wstrzimóné: òsoba, chtërna zrobia nen lënk, nie mòże ju editowac òbù kôrtów, abò kôrta je zarchiwizowónô.",
|
||||
"custom-field-unlink": "Rozparłãczë",
|
||||
"custom-field-link-target": "Lënk kôrtë (Kòpiérëjë lënk kôrtë) abò szëkôj w kôrtach ti tôflë",
|
||||
"custom-field-link-direction": "Czerënk",
|
||||
"custom-field-link-both": "W òbie stronë: zmianë na kòżdi kôrce jidą na drëgą",
|
||||
"custom-field-link-send": "W jedną stronã: zmianë na ti kôrce jidą na przédnô kôrtã",
|
||||
"custom-field-link-sends": "Zmianë na ti kôrce jidą na tã kôrtã",
|
||||
"custom-field-link-receives": "Zmianë na ti drëgi kôrce przëchôdają na nã kôrtã",
|
||||
"custom-field-link-add": "Sparłãczë pòla",
|
||||
"field-link-self": "Kôrtë nie mòżna sparłãczëc z nią samą.",
|
||||
"field-link-limit": "Na kôrta abò ta drëgô mô ju nôwiãcy sparłãczonëch kôrtów, kùli mòże miec.",
|
||||
"field-link-archived": "Zarchiwizowóny kôrtë nie mòżna sparłãczëc.",
|
||||
"field-link-invalid": "To nie je lënk kôrtë.",
|
||||
"field-link-not-found": "Nie nalazłé kôrtë.",
|
||||
"field-link-not-allowed": "Mùszisz miec prawò editowac òbie kôrtë.",
|
||||
"cardCustomFieldLinksPopup-title": "Sparłãczoné włôsné pòla",
|
||||
"createCustomFieldPopup-title": "Utwòrz pole",
|
||||
"current": "obecnié otwarta",
|
||||
"custom-color": "Farwa niestandardowi",
|
||||
"cards-use-list-color": "Cards without a colour of their own use this colour",
|
||||
"cards-use-list-color": "Kôrtë bez włôsny farwë brëkùją ti farwë",
|
||||
"custom-field-delete-pop": "Tegò nie mòżna cofnąc. To rëmnie no swòjé pòle ze wszëtczich kôrtów i zniszczi jegò historiã.",
|
||||
"custom-field-checkbox": "Pòle òznaczeniô",
|
||||
"custom-field-currency": "Walëta",
|
||||
@@ -909,20 +909,20 @@
|
||||
"import-board-instruction-zenkit": "Wklej eksport JSON lëstë ZenKit ({ \"title\", \"stages\":[...], \"items\":[...] }). Etapy stają się listami, a elementy kartami.",
|
||||
"import-board-instruction-markdown": "Wlëpcë lëstã zadaniów w markdown (kònwencjô, jaką brëkùją nôrzãdzi markdown-kanban jak Obsidian Kanban): title \"## Miono lëstë\", z elementama \"- [ ]\"/\"- [x]\" niżi. Titla stôwają sã lëstama, a elementë kartama; prosti lëstã z pùnktama bez pòlów wëbioru téż je zaimportowónô, jakno òtemkłé kartë.",
|
||||
"import-board-instruction-leo": "Wklej òbrës Leo (lopk .leo). Wãzłë nôwëższégò równia stôwają sã lëstama, jich dzecë — kôrtama z zamkłoscą wãzła jakno òpisã, a głãbszé wãzłë — lëstama kòntrólnyma. Òznaczoné wãzłë są impòrtowóné jakno skùńczoné.",
|
||||
"import-board-instruction-opml": "Paste an OPML outline, as Workflowy, Dynalist, OmniOutliner, Logseq and other outliners export it. Top-level items become lists, their children become cards with the note as the description, and deeper items become checklists. Completed items import as done.",
|
||||
"import-board-instruction-orgmode": "Paste an Org mode file, as Emacs, Orgzly or Beorg write it. Top-level headings become lists and second-level headings become cards. TODO and DONE, priorities, tags, SCHEDULED, DEADLINE and CLOSED are kept, and checkboxes and deeper headings become checklists.",
|
||||
"import-board-instruction-opml": "Wklejë kònspekt OPML, tak jak gò ekspòrtëją Workflowy, Dynalist, OmniOutliner, Logseq i jinszé programë. Elementë z wiérzchnégò rówiznë stôwają sã lëstama, jich dzecë kôrtama z notką jakno òpisënkã, a głãbszé elementë lëstama kòntrolë. Skùńczoné elementë są impòrtowóné jakno zrobioné.",
|
||||
"import-board-instruction-orgmode": "Wklejë lopk Org mode, tak jak gò piszą Emacs, Orgzly abò Beorg. Nôgłówczi z wiérzchnégò rówiznë stôwają sã lëstama, a nôgłówczi drëgégò rówiznë kôrtama. TODO i DONE, priorytetë, tagi, SCHEDULED, DEADLINE i CLOSED òstôwają, a pòla do òbznaczeniô i głãbszé nôgłówczi stôwają sã lëstama kòntrolë.",
|
||||
"import-board-instruction-todotxt": "Wklejë lopk todo.txt: jedno zadanié na rézã, tak jak zapisëje je aplikacëjô todo.txt i zgódné z nią aplikacëje. Skùńczoné zadania (\"x\") jidą do Skùńczoné, +project i @context stôwają sã etichetama, (A) stôwô sã etichetą priòritetu, a due: i t: stôwają sã datama terminu i zaczãcô.",
|
||||
"import-board-instruction-taskwarrior": "Wklej rezultat pòlétu task export: tablica JSON abò jedno zadanié na kòżdi réze. Skùńczoné zadania jidą do Done, project i priority stôwają sã etikétama, annotations kòmentarzama, a depends zanôleżnoscama òd blokùjącëch zadaniów.",
|
||||
"import-board-instruction-focalboard": "Rozpakùj lopk .boardarchive i wklej zamkłosc board.jsonl tôblë, z rézą wersji abò bez ni. Swòjizna grëpòwaniô wëzdrzatkù tôblë stôwô sã lëstama, jinszé swòjiznë wëbiéraniô etikétama, blòczi tekstu i pòla zaznaczaniô òpisã i kòntrolną lëstą, a kòmentarze są zachòwóné. Òbrôzczi i przëdôwczi nie są impòrtowóné.",
|
||||
"import-board-instruction-todoist": "In Todoist, open the project menu and choose Export as a template, then paste the CSV file. Sections become lists, tasks become cards, sub-tasks become a checklist, notes become comments, and @labels and priorities p1 to p3 become labels.",
|
||||
"import-board-instruction-planner": "In Microsoft Planner, open the plan's menu and choose Export plan to Excel, then choose that .xlsx file. Buckets become lists and tasks become cards, with their assignees, dates, labels, checklist and description; Progress, Priority and Completed By become custom fields.",
|
||||
"import-board-instruction-meistertask": "In MeisterTask, click the project name, choose Export project and export the CSV, then paste it. Sections become lists and tasks become cards, with their notes, due date, assignee and tags; completed tasks keep their completion date. A CSV written for MeisterTask's own import works too.",
|
||||
"import-board-instruction-obsidian": "In Obsidian, open the Kanban board's file as Markdown (or open the .md file in a text editor), then paste all of it. Lanes become lists with their card limits, cards keep their tags, dates, priority, fields and checklist, and the Archive becomes archived cards.",
|
||||
"import-board-instruction-linear": "In Linear, export the issues as CSV (Settings, Import / Export, Export data, or a view's export), then paste the CSV. Statuses become lists, teams become swimlanes, and issues become cards with their assignee, labels, dates, parent issue, priority, estimate, project and cycle.",
|
||||
"import-board-instruction-ticktick": "In TickTick, open Settings, Account, Backup & Import and generate a backup, then paste the CSV file. Each TickTick list becomes a swimlane and its columns become lists; tasks keep their tags, dates, checklist, priority and subtasks.",
|
||||
"import-board-instruction-clickup": "In ClickUp, export the tasks as CSV (Settings, Imports / Exports, Export Items, or a List or Table view's export with all columns), then paste the CSV. Statuses become lists, ClickUp lists become swimlanes, and tasks become cards with their subtasks, assignees, tags, dates, priority and time.",
|
||||
"import-board-instruction-nullboard": "In Nullboard, open the board, choose Export this board... from the menu, open the .nbx file in a text editor and paste its contents. Lists become lists and notes become cards: a note's first line is the card title and the rest its description. A raw note gets the label raw. A file with several boards imports its first board.",
|
||||
"import-board-instruction-kanri": "In Kanri, open Import & Export and choose Export individual board (or Export all data), then paste the .json file. Columns become lists and cards become cards, with their description, due date, tags and colors; a card's tasks become a checklist. From an all-data export the first board is imported.",
|
||||
"import-board-instruction-meistertask": "W MeisterTask klëkni miono projektu, wëbierzë Export project i wëekspòrtëjë CSV, pòtemù gò wklejë. Sekcje stôwają sã lëstama, a zadania kôrtama, z jich notkama, terminã, òdpòwiedzalnym i tagama; skùńczoné zadania zachòwùją datum skùńczeniô. CSV napisóny do impòrtu MeisterTask téż fùnguje.",
|
||||
"import-board-instruction-obsidian": "W Obsidian òtemkni lopk tôflë Kanban jakno Markdown (abò òtemkni lopk .md w editorze tekstu), pòtemù wklejë całi. Tore stôwają sã lëstama z jich limitama kôrtów, kôrtë zachòwùją swòje tagi, datumë, priorytet, pòla i lëstã kòntrolë, a Archive stôwô sã zarchiwizowónyma kôrtama.",
|
||||
"import-board-instruction-linear": "W Linear wëekspòrtëjë zgłoszenia jakno CSV (Settings, Import / Export, Export data, abò ekspòrt widoku), pòtemù wklejë CSV. Statusë stôwają sã lëstama, zespòłë stegnama, a zgłoszenia kôrtama z òdpòwiedzalnym, etikétama, datumama, nadrzãdnym zgłoszenim, priorytetã, òcenią, projektã i cyklã.",
|
||||
"import-board-instruction-ticktick": "W TickTick òtemkni Settings, Account, Backup & Import i zrobi zapasową kòpiã, pòtemù wklejë lopk CSV. Kòżdô lësta TickTick stôwô sã stegną, a ji kòlumnë lëstama; zadania zachòwùją swòje tagi, datumë, lëstã kòntrolë, priorytet i pòdzadania.",
|
||||
"import-board-instruction-clickup": "W ClickUp wëekspòrtëjë zadania jakno CSV (Settings, Imports / Exports, Export Items, abò ekspòrt widoku List abò Table ze wszëtczima kòlumnama), pòtemù wklejë CSV. Statusë stôwają sã lëstama, lëstë ClickUp stegnama, a zadania kôrtama z pòdzadaniama, òdpòwiedzalnyma, tagama, datumama, priorytetã i czasã.",
|
||||
"import-board-instruction-nullboard": "W Nullboard òtemkni tôflã, wëbierzë z menu Export this board..., òtemkni lopk .nbx w editorze tekstu i wklejë jegò zamkłosc. Lëstë stôwają sã lëstama, a notczi kôrtama: pierszô réga notczi je titlã kôrtë, a reszta ji òpisënkã. Surowô notka dostôwô etikétã raw. Z lopka z wiele tôflama je impòrtowónô pierszô tôfla.",
|
||||
"import-board-instruction-kanri": "W Kanri òtemkni Import & Export i wëbierzë Export individual board (abò Export all data), pòtemù wklejë lopk .json. Kòlumnë stôwają sã lëstama, a kôrtë kôrtama, z jich òpisënkã, terminã, tagama i farwama; zadania kôrtë stôwają sã lëstą kòntrolë. Z ekspòrtu wszëtczich pòdôwków je impòrtowónô pierszô tôfla.",
|
||||
"import-board-instruction-planka": "Wpiszë adresã twòjegò Planka (na przëmiôr https://planka.example.com), twòje miono brëkòwnika ë parolã Planka, a pòtemù wcësni Wëlistëjë. Są brëkòwóné blós do zladowaniô tôfle ë nie są zapisywóné. Wëbierzë tôflã ë wcësni Zladëjë: WeKan czëtô jã z API Planka, z jejë lëstama, kôrtama, òpisënkama, terminama, etikétama, nôleżnikama, lëstama zadaniów ë kòmentôrzama, a pòtemù jidzë dali z impòrtowanim jak zwëczajno. Kôrtë z archiwùm Planka są impòrtowóné jakno archiwizowóné; załączniczi ë kôrtë z kòsza nie są impòrtowóné.",
|
||||
"import-board-instruction-mstodo": "W Microsoft Graph Explorer (developer.microsoft.com/graph/graph-explorer) wlogùjë sã, zgòdzë sã na prawò Tasks.Read ë skòpiérëjë token z kôrtë Access token. Wklejë gò tuwò ë klëkni Wëlistëjë; token je brëkòwóny blós do negò zladowaniô ë nie je zapisywóny. Wëbierzë wszëtczé lëstë abò jednã lëstã ë klëkni Zladëjë. Kòżdô lësta To Do stôwô sã lëstą, a kòżdé zadanié kôrtą, z jegò notkama, terminama ë datama zaczãcô, krokama jakno kòntrolnô lësta, kategòriama jakno etikétë, a wësokô wôżnota ë sztatus jakno etikétë; skùńczoné zadania są archiwizowóné. Przëbôczenia ë pòwtôrzania nie są impòrtowóné.",
|
||||
"import-board-instruction-kanbanflow": "W KanbanFlow òtemkni menu tôfle, wëbierzë Settings → API & Webhooks ë skòpiérëjë token API tôfle. Wklejë token tuwò, klëkni Wëlistëjë, wëbierzë tôflã ë klëkni Zladëjë; token je brëkòwóny blós do negò zladowaniô ë nie je zapisywóny. Kòlumnë stôwają sã lëstama, a stegnë òstôwają stegnama; zadania trzimają swój òpisënk, farwã, etikétë, termin, pòdzadania jakno kòntrolnô lësta, wëdóny czas, szacënk, lëdzy ë kòmentôrze.",
|
||||
@@ -941,19 +941,19 @@
|
||||
"api-import-fetched": "Zladowóné. Sprawdzë nôleżników niżi ë impòrtëjë.",
|
||||
"api-import-failed": "Zladowanié nie darzëło sã",
|
||||
"api-import-nothing": "Z tim kòntã nie ma nic do impòrtowaniô.",
|
||||
"import-board-instruction-pivotal": "In Pivotal Tracker, open the project's MORE tab and choose Export CSV (or select stories and choose Bulk Actions, CSV), then paste the CSV. Each story state becomes a list and stories become cards, with their type and labels, owners, requester, dates, comments, tasks and blockers; Estimate becomes Story points and iterations become sprints.",
|
||||
"import-board-instruction-tasksorg": "In Tasks.org, open Settings, Backups and choose Export tasks, then paste the contents of that .json file. Lists become lists and tasks become cards, with their notes, tags, dates, comments and subtasks; completed tasks keep their completion date, and priority becomes a custom field.",
|
||||
"import-board-instruction-monday": "In monday.com, open the board menu, choose More actions and Export board to Excel (with updates and subitems if you like), then choose that .xlsx file. The Status column becomes lists and groups become swimlanes; people, dates, timeline and tags keep their places, other columns become custom fields and updates become comments.",
|
||||
"import-board-instruction-superproductivity": "In Super Productivity, open Settings, then Sync & Backup, choose Export Data and paste the contents of the sp-backup .json file. Each project becomes a swimlane and tasks become cards in To Do, In Progress, Backlog or Done, with their notes, tags, due date, time spent and sub-tasks; archived tasks become archived cards.",
|
||||
"import-board-instruction-taiga": "In Taiga, open Admin > Project > Export and download the project dump (unpack a .json.gz file first), then paste its JSON. User story statuses become lists, user stories become cards with their tasks as subtasks, epics and issues get swimlanes of their own, and sprints, tags, comments and custom attributes come along. Attachments are not imported.",
|
||||
"import-board-instruction-vikunja": "In Vikunja, open Settings, choose Data Export and download the .zip file it sends you, then choose that file, or paste the data.json inside it. Kanban buckets become lists and tasks become cards, with their labels, assignees, dates, description, task lists as checklists and comments; priority, percent done and done become custom fields. Attachments are not imported.",
|
||||
"import-board-instruction-quire": "In Quire, open the project menu and choose Export CSV, then paste the CSV. Statuses become lists and tasks become cards, with their subtasks as child cards, assignees, tags as labels, start, due and completed dates, description and priority as a custom field. Comments and attachments are not in Quire's CSV.",
|
||||
"import-board-instruction-wrike": "In Wrike, export a project or folder to Excel, or fill in Wrike's Excel import template, then choose that .xlsx file. Folder and project rows become swimlanes, Status becomes lists and tasks become cards, with their Key, parent task, assignees, start and end dates, dependencies and description; Priority, Duration and other columns become custom fields.",
|
||||
"import-board-instruction-teamwork": "In Teamwork.com, fill in the Excel task import template (Tasklist, Task, Description, Assign to, Start date, Due date, Priority, Estimated time, Tags, Status) and save it as .xlsx, then choose that file. Task lists become lists and tasks become cards; a task starting with -, # or > becomes a subtask of the task above it (--, ## or >> one level deeper). Assignees, dates and tags keep their places; Priority, Estimated time and Complete become custom fields.",
|
||||
"import-board-instruction-businessmap": "In Businessmap (formerly Kanbanize), open Advanced Search, choose Configure results to pick the columns, download the Excel file, then choose that .xlsx file. Columns are read by their English names (Title, Column, Lane, Owner, Deadline ...): if your account uses another language, rename the header row to English first. Column becomes lists and Lane swimlanes; owners, tags, color, dates, comments and parent links keep their places, and Priority, Size, Type and other columns become custom fields.",
|
||||
"import-board-instruction-redmine": "In Redmine, open the project's Issues, choose Also available in: CSV at the bottom of the list, pick the columns (All columns, with Description) and export, then paste the CSV. Redmine names the columns in your language, so set your language to English in My account before exporting. Each status becomes a list and issues become cards, with the tracker as a label, assignee, author, dates, parent task and related issues; priority, category, target version, estimated time, % Done and custom fields become custom fields.",
|
||||
"import-board-instruction-notion": "In Notion, open the database as a full page, choose ••• and Export, pick Markdown & CSV, and choose the .zip file it downloads, or one database CSV from it, or paste that CSV. A column named Status becomes the lists (without one, the first column that looks like a select does, and the import report says which); rows become cards with their title, tags as labels, people as members, dates, and the page text as the description; other columns become custom fields. Relations, images and attachments are not imported.",
|
||||
"import-board-instruction-plane": "In Plane, open Workspace Settings, choose Exports, export the projects as JSON, CSV or Excel and download the .zip file, then choose that file, or one file from inside it, or paste the JSON or CSV. States become lists, projects become swimlanes, and issues become cards with their identifier, parent, assignees, labels, dates, links and comments; priority, estimate, cycle and module become custom fields. Plane's export has no descriptions or attachments, so they are not imported.",
|
||||
"import-board-instruction-pivotal": "W Pivotal Tracker òtemkni zakładkã MORE projektu i wëbierzë Export CSV (abò òbznaczë historie i wëbierzë Bulk Actions, CSV), pòtemù wklejë CSV. Kòżdi stón historie stôwô sã lëstą, a historie kôrtama, z jich ôrtã i etikétama, wëmienikama, zgłôszającym, datumama, kòmentôrzama, zadaniama i blokadama; Estimate stôwô sã Story points, a iteracje sprintama.",
|
||||
"import-board-instruction-tasksorg": "W Tasks.org òtemkni Settings, Backups i wëbierzë Export tasks, pòtemù wklejë zamkłosc tegò lopka .json. Lëstë stôwają sã lëstama, a zadania kôrtama, z jich notkama, tagama, datumama, kòmentôrzama i pòdzadaniama; skùńczoné zadania zachòwùją datum skùńczeniô, a priorytet stôwô sã włôsnym pòlã.",
|
||||
"import-board-instruction-monday": "W monday.com òtemkni menu tôflë, wëbierzë More actions i Export board to Excel (jeżle chcesz, z aktualizacjama i pòdelementama), pòtemù wëbierzë nen lopk .xlsx. Kòlumna Status stôwô sã lëstama, a grëpë stegnama; lëdze, datumë, òs czasu i tagi òstôwają na swòjich môlach, jinszé kòlumnë stôwają sã włôsnyma pòlama, a aktualizacje kòmentôrzama.",
|
||||
"import-board-instruction-superproductivity": "W Super Productivity òtemkni Settings, pòtemù Sync & Backup, wëbierzë Export Data i wklejë zamkłosc lopka .json sp-backup. Kòżdi projekt stôwô sã stegną, a zadania kôrtama w To Do, In Progress, Backlog abò Done, z jich notkama, tagama, terminã, czasã robòtë i pòdzadaniama; zarchiwizowóné zadania stôwają sã zarchiwizowónyma kôrtama.",
|
||||
"import-board-instruction-taiga": "W Taiga òtemkni Admin > Project > Export i scygni zrzut projektu (nôprzód rozpakùjë lopk .json.gz), pòtemù wklejë jegò JSON. Statusë historiów brëkòwnika stôwają sã lëstama, historie brëkòwnika kôrtama z jich zadaniama jakno pòdzadaniama, epiczi i zgłoszenia dostôwają swòje stegnë, a sprintë, tagi, kòmentôrze i włôsné atrybutë jidą téż. Załączniczi nie są impòrtowóné.",
|
||||
"import-board-instruction-vikunja": "W Vikunja òtemkni Settings, wëbierzë Data Export i scygni lopk .zip, chtëren ce przësle, pòtemù wëbierzë nen lopk abò wklejë data.json z jegò strzodka. Kùbełczi Kanban stôwają sã lëstama, a zadania kôrtama, z jich etikétama, òdpòwiedzalnyma, datumama, òpisënkã, lëstama zadaniów jakno lëstama kòntrolë i kòmentôrzama; priorytet, procent zrobionégò i zrobioné stôwają sã włôsnyma pòlama. Załączniczi nie są impòrtowóné.",
|
||||
"import-board-instruction-quire": "W Quire òtemkni menu projektu i wëbierzë Export CSV, pòtemù wklejë CSV. Statusë stôwają sã lëstama, a zadania kôrtama, z pòdzadaniama jakno dzecnyma kôrtama, òdpòwiedzalnyma, tagama jakno etikétama, datumã zôczątkù, terminã i datumã skùńczeniô, òpisënkã i priorytetã jakno włôsnym pòlã. Kòmentôrzów i załączników ni ma w CSV z Quire.",
|
||||
"import-board-instruction-wrike": "W Wrike wëekspòrtëjë projekt abò katalog do Excela, abò wëpełni szablon impòrtu Excel z Wrike, pòtemù wëbierzë nen lopk .xlsx. Rédżi katalogów i projektów stôwają sã stegnama, Status lëstama, a zadania kôrtama, z jich Key, nadrzãdnym zadanim, òdpòwiedzalnyma, datumã zôczątkù i kùńca, zanôleżnoscama i òpisënkã; Priority, Duration i jinszé kòlumnë stôwają sã włôsnyma pòlama.",
|
||||
"import-board-instruction-teamwork": "W Teamwork.com wëpełni szablon impòrtu zadaniów Excel (Tasklist, Task, Description, Assign to, Start date, Due date, Priority, Estimated time, Tags, Status) i zapiszë gò jakno .xlsx, pòtemù wëbierzë nen lopk. Lëstë zadaniów stôwają sã lëstama, a zadania kôrtama; zadanié, co zaczinô sã òd -, # abò >, stôwô sã pòdzadanim zadaniô nad nim (--, ## abò >> jeden rówiznã głãbi). Òdpòwiedzalny, datumë i tagi òstôwają na swòjich môlach; Priority, Estimated time i Complete stôwają sã włôsnyma pòlama.",
|
||||
"import-board-instruction-businessmap": "W Businessmap (przódë Kanbanize) òtemkni Advanced Search, wëbierzë Configure results, żebë wëbrac kòlumnë, scygni lopk Excel, pòtemù wëbierzë nen lopk .xlsx. Kòlumnë są czëtóné pò jich anielsczich mionach (Title, Column, Lane, Owner, Deadline ...): jeżle twòje kònto brëkùje jinszégò jãzëka, nôprzód zmieni miona w régù nôgłówków na anielsczé. Column stôwô sã lëstama, a Lane stegnama; wëmienicë, tagi, farwa, datumë, kòmentôrze i nadrzãdné lënczi òstôwają na swòjich môlach, a Priority, Size, Type i jinszé kòlumnë stôwają sã włôsnyma pòlama.",
|
||||
"import-board-instruction-redmine": "W Redmine òtemkni Issues projektu, wëbierzë Also available in: CSV na spòdkù lëstë, wëbierzë kòlumnë (All columns, z Description) i wëekspòrtëjë, pòtemù wklejë CSV. Redmine dôwô kòlumnóm miona w twòjim jãzëkù, tej przed ekspòrtã nastôwi anielsczi w My account. Kòżdi status stôwô sã lëstą, a zgłoszenia kôrtama, z trackerã jakno etikétą, òdpòwiedzalnym, ùsôdzcą, datumama, nadrzãdnym zadanim i sparłãczonyma zgłoszeniama; priorytet, kategòrëjô, docélowô wersjô, òcenioni czas, % Done i włôsné pòla stôwają sã włôsnyma pòlama.",
|
||||
"import-board-instruction-notion": "W Notion òtemkni bazã pòdôwków jakno całą starnã, wëbierzë ••• i Export, wëbierzë Markdown & CSV i wëbierzë lopk .zip, chtëren sã scygnie, abò jeden CSV bazë z niegò, abò wklejë nen CSV. Kòlumna z mionã Status stôwô sã lëstama (bez ni robi to pierszô kòlumna, chtërna wëzdrzi jak wëbiér, a rapòrt impòrtu gôdô chtërna); rédżi stôwają sã kôrtama z jich titlã, tagama jakno etikétama, lëdzama jakno nôleżnikama, datumama i tekstã starnë jakno òpisënkã; jinszé kòlumnë stôwają sã włôsnyma pòlama. Relacje, òbrazë i załączniczi nie są impòrtowóné.",
|
||||
"import-board-instruction-plane": "W Plane òtemkni Workspace Settings, wëbierzë Exports, wëekspòrtëjë projektë jakno JSON, CSV abò Excel i scygni lopk .zip, pòtemù wëbierzë nen lopk abò jeden lopk z jegò strzodka, abò wklejë JSON abò CSV. Stónë stôwają sã lëstama, projektë stegnama, a zgłoszenia kôrtama z jich identifikatorã, nadrzãdnym, òdpòwiedzalnyma, etikétama, datumama, lënkama i kòmentôrzama; priorytet, òcena, cykl i mòduł stôwają sã włôsnyma pòlama. Ekspòrt Plane ni mô òpisënków ani załączników, tej nie są impòrtowóné.",
|
||||
"import-board-instruction-trello": "W twojej tôblëcë na Trello przejdź do 'Menu', następnié 'Więcej', 'Drukuj i eksportuj', 'Eksportuj jako JSON' i skopiuj wynik",
|
||||
"import-board-instruction-csv": "Wklej wôrtnotë òddzeloné przecënkama (CSV) abò tabùlatorama (TSV).",
|
||||
"import-board-instruction-jira": "Wklejë JSON z szëkaniô zgłoszeniów w Jira Cloud REST API (GET /rest/api/3/search/jql) abò równowôżny òbiekt { \"issues\": [...] }. Stónë Jira stôwają sã lëstama, a zgłoszenia kôrtama. Òpcjonalno dodôj tôbelã \"automationRules\", żebë zaimpòrtowac regle.",
|
||||
@@ -962,33 +962,33 @@
|
||||
"import-board-instruction-wekan": "Na swòji tôfli òtemkni 'Spis òpcjów', pòtem 'Ekspòrtowac tôflã', i skòpiérëjë tekst z pòbrónégò lopka.",
|
||||
"import-board-instruction-about-errors": "Czasami mimo komunikatu o błędzie importowanié tôblëcë przebiega poprawnié i jest ona widoczna na liście tôblëcòw (Wszëtczé tôblëce).",
|
||||
"import-without-mapping-members": "Importuj bez mapowania członkòw (mapuj pòźniej)",
|
||||
"import-members-mode-heading": "People in the file:",
|
||||
"import-members-mode-map": "Choose an existing user for each of them",
|
||||
"import-members-mode-placeholder": "Keep them as placeholder users with their own names, to map to real users later",
|
||||
"import-members-mode-me": "Make them all me",
|
||||
"csv-mapping-title": "Which column is which?",
|
||||
"csv-mapping-description": "Choose the column of the file that holds each card field. The choices were made from the column names; change any that are wrong, and choose (not in the file) for a field the file does not have.",
|
||||
"csv-mapping-not-in-file": "(not in the file)",
|
||||
"csv-mapping-column-number": "Column __number__",
|
||||
"csv-mapping-list-missing": "The file has no column for the list. Choose one above, or type the name of the one list every card goes into.",
|
||||
"csv-mapping-list-name": "List for every card",
|
||||
"csv-mapping-custom-fields": "Other columns to import as text custom fields:",
|
||||
"csv-mapping-boards": "Each sheet becomes its own board:",
|
||||
"csv-mapping-skipped-sheets": "Sheets that are not imported:",
|
||||
"csv-mapping-title-required": "Choose the column that holds the card titles.",
|
||||
"csv-mapping-list-required": "Type the name of the list every card goes into, or choose the column that holds the list.",
|
||||
"invalid-import-mapping": "The column mapping does not match the file.",
|
||||
"import-many-boards": "Import many boards",
|
||||
"import-many-boards-hint": "Choose several export files of this tool, or one .zip that holds them: each file becomes its own board, imported without member mapping. A .zip that is itself one export (Vikunja, Notion, Plane, or WeKan with attachments) is one board. When files are chosen here, Import imports them instead of the field above.",
|
||||
"import-one-board-per-project": "One board per project",
|
||||
"import-one-board-per-project-hint": "Each swimlane the import would make (a project, folder or list of the tool) becomes its own board. Parent cards and dependencies between those boards are listed in the import report.",
|
||||
"import-many-progress": "Importing __done__ of __total__: __name__",
|
||||
"import-many-results-heading": "Imported files",
|
||||
"import-many-imported": "imported",
|
||||
"export-all-boards": "Export all boards",
|
||||
"export-selected-boards": "Export selected boards",
|
||||
"export-all-boards-hint": "Every board you can export, in one download: Excel as one workbook with a sheet per board named after it, every other format as a .zip with a file per board.",
|
||||
"exportAllBoardsPopup-title": "Export all boards",
|
||||
"import-members-mode-heading": "Lëdze w lopkù:",
|
||||
"import-members-mode-map": "Wëbierzë jistniejącégò brëkòwnika dlô kòżdégò z nich",
|
||||
"import-members-mode-placeholder": "Òstawi jich jakno zastãpczich brëkòwników z jich włôsnyma mionama, żebë pózni przëpisac jich do prôwdzëwëch brëkòwników",
|
||||
"import-members-mode-me": "Zrobi jich wszëtczich mną",
|
||||
"csv-mapping-title": "Chtërna kòlumna je chtërna?",
|
||||
"csv-mapping-description": "Wëbierzë kòlumnã lopka, chtërna trzimô kòżdé pòle kôrtë. Wëbiérë òstałë zrobioné pò mionach kòlumnów; zmieni te, co są lëchò, i wëbierzë (ni ma w lopkù) dlô pòla, chtërnégò lopk ni mô.",
|
||||
"csv-mapping-not-in-file": "(ni ma w lopkù)",
|
||||
"csv-mapping-column-number": "Kòlumna __number__",
|
||||
"csv-mapping-list-missing": "Lopk ni mô kòlumnë dlô lëstë. Wëbierzë jedną wëżi abò napiszë miono ti jedny lëstë, do chtërny jidze kòżdô kôrta.",
|
||||
"csv-mapping-list-name": "Lësta dlô kòżdi kôrtë",
|
||||
"csv-mapping-custom-fields": "Jinszé kòlumnë do impòrtu jakno tekstowé włôsné pòla:",
|
||||
"csv-mapping-boards": "Kòżdi arkùsz stôwô sã swòją tôflą:",
|
||||
"csv-mapping-skipped-sheets": "Arkùsze, chtërné nie są impòrtowóné:",
|
||||
"csv-mapping-title-required": "Wëbierzë kòlumnã, chtërna trzimô title kôrtów.",
|
||||
"csv-mapping-list-required": "Napiszë miono lëstë, do chtërny jidze kòżdô kôrta, abò wëbierzë kòlumnã, chtërna trzimô lëstã.",
|
||||
"invalid-import-mapping": "Przëpisanié kòlumnów nie pasëje do lopka.",
|
||||
"import-many-boards": "Impòrtëjë wiele tôflów",
|
||||
"import-many-boards-hint": "Wëbierzë wiele lopków ekspòrtu negò nôrzãdza abò jeden .zip, co je trzimô: kòżdi lopk stôwô sã swòją tôflą, impòrtowóną bez przëpisaniô nôleżników. .zip, chtëren sóm je jednym ekspòrtã (Vikunja, Notion, Plane abò WeKan z załącznikama), je jedną tôflą. Czej wëbierzesz tuwò lopczi, Impòrt impòrtëje je zamiast pòla wëżi.",
|
||||
"import-one-board-per-project": "Jedna tôfla na projekt",
|
||||
"import-one-board-per-project-hint": "Kòżdô stegna, chtërną bë zrobił impòrt (projekt, katalog abò lësta nôrzãdza), stôwô sã swòją tôflą. Nadrzãdné kôrtë i zanôleżnotë midzë tima tôflama są wëmienioné w rapòrce impòrtu.",
|
||||
"import-many-progress": "Impòrt __done__ z __total__: __name__",
|
||||
"import-many-results-heading": "Zaimpòrtowóné lopczi",
|
||||
"import-many-imported": "zaimpòrtowóné",
|
||||
"export-all-boards": "Ekspòrtëjë wszëtczé tôfle",
|
||||
"export-selected-boards": "Ekspòrtëjë wëbróné tôfle",
|
||||
"export-all-boards-hint": "Kòżdô tôfla, chtërną mòżesz ekspòrtowac, w jednym scygniãcym: Excel jakno jeden zesziwk z arkùszã na kòżdą tôflã, mionowónym pò ni, kòżdi jinszi fòrmat jakno .zip z lopkã na kòżdą tôflã.",
|
||||
"exportAllBoardsPopup-title": "Ekspòrtëjë wszëtczé tôfle",
|
||||
"import-json-placeholder": "Wklej tu pòprawné dóné JSON",
|
||||
"import-csv-placeholder": "Wklej tu pòprawné dóné CSV/TSV",
|
||||
"import-json-file": "Abò wëbierzë lopk JSON Trello",
|
||||
@@ -1026,17 +1026,17 @@
|
||||
"trello-import-results": "Rezultatë impòrtu",
|
||||
"trello-api-credentials-required": "Wpiszë i klucz, i token API Trello.",
|
||||
"trello-api-credentials-saved": "Klucz i token API Trello są zapisóné. Mòżesz pòkazac lëstã robòczich rëmów bez jich pòwtórnégò wpisëwaniô.",
|
||||
"exportToTrelloPopup-title": "Export to Trello",
|
||||
"trello-export-desc": "Create a copy of this board in Trello through Trello's API, with your Trello API key and token. Swimlanes become labels named \"Swimlane: …\", which WeKan's Trello import turns back into swimlanes.",
|
||||
"trello-export-include-attachments": "Include attachments",
|
||||
"trello-export-start": "Export to Trello",
|
||||
"trello-export-open-board": "Open the board in Trello",
|
||||
"trello-export-warnings": "Not everything could be exported",
|
||||
"sandstorm-trello-access-needed": "A Sandstorm grain has no Internet access of its own. Allow this grain to reach Trello first.",
|
||||
"sandstorm-allow-trello-access": "Allow access to Trello",
|
||||
"sandstorm-trello-access-granted": "This grain may reach Trello.",
|
||||
"sandstorm-forget-access": "Forget",
|
||||
"sandstorm-trello-save-label": "Trello, for importing and exporting boards",
|
||||
"exportToTrelloPopup-title": "Ekspòrt do Trello",
|
||||
"trello-export-desc": "Zrobi kòpiã ti tôflë w Trello bez API Trello, z twòjim klucã API Trello i tokenã. Stegnë stôwają sã etikétama z mionã \"Swimlane: …\", chtërné impòrt Trello we WeKan zmieniô nazôd na stegnë.",
|
||||
"trello-export-include-attachments": "Z załącznikama",
|
||||
"trello-export-start": "Ekspòrt do Trello",
|
||||
"trello-export-open-board": "Òtemkni tôflã w Trello",
|
||||
"trello-export-warnings": "Nie wszëtkò szło wëekspòrtowac",
|
||||
"sandstorm-trello-access-needed": "Zôrno Sandstorm ni mô swòjégò przistãpù do Internetu. Nôprzód pòzwòlë temù zôrnu dobëc sã do Trello.",
|
||||
"sandstorm-allow-trello-access": "Pòzwòlë na przistãp do Trello",
|
||||
"sandstorm-trello-access-granted": "To zôrno mòże dobëc sã do Trello.",
|
||||
"sandstorm-forget-access": "Zabôczë",
|
||||
"sandstorm-trello-save-label": "Trello, do impòrtu i ekspòrtu tôflów",
|
||||
"trello-select-boards": "Wëbierzë przënômni jedną tôflã do impòrtu.",
|
||||
"select-all": "Wëbierzë wszëtkò",
|
||||
"select-none": "Nie wëbiérôj nick",
|
||||
@@ -1318,9 +1318,9 @@
|
||||
"external-link-pattern-description": "Aùtomatné lëkùje tokeny sztélu \"#1234\" nalazłé w tekstą kartë do zewnãtrznégò tracera problemów. Òstôwiô jedno pòle pùsté, cobë wëłączëc.",
|
||||
"external-link-pattern-prefix": "Prefiks tokena",
|
||||
"external-link-pattern-url": "Szablón URL (brëkùjë {number})",
|
||||
"external-link-rules": "More link rules",
|
||||
"external-link-rules-description": "One rule per line: the text to find, \" = \", and the address. Use {number} for digits and {identifier} for a word, in both, for example [{identifier}:{number}] = https://tracker.example.com/{identifier}/{number}.",
|
||||
"external-link-identifier-aliases": "Abbreviations for {identifier}, for example TK=Task, IN=Incident",
|
||||
"external-link-rules": "Wiãcy prawidłów lënków",
|
||||
"external-link-rules-description": "Jedno prawidło na régã: tekst do szëkaniô, \" = \" i adresa. Brëkùjë {number} dlô cyfrów i {identifier} dlô słowa, w òbù, na przëkłôd [{identifier}:{number}] = https://tracker.example.com/{identifier}/{number}.",
|
||||
"external-link-identifier-aliases": "Skrócënczi dlô {identifier}, na przëkłôd TK=Task, IN=Incident",
|
||||
"username": "Miono brëkòwnika",
|
||||
"import-usernames": "Importuj brëkòwnikòw",
|
||||
"view-it": "Òbezdrzë",
|
||||
@@ -1345,13 +1345,13 @@
|
||||
"attachment-transfer-limits-title": "Ògrańczenia miarë lopków przëłączników i API",
|
||||
"attachment-limits": "Ògrańczenia",
|
||||
"attachment-retention": "Trzymanié",
|
||||
"attachment-retention-days": "Delete attachments after (days)",
|
||||
"attachment-retention-days-help": "Like Delete on the card: the card history can restore them. Empty or 0: never.",
|
||||
"attachment-retention-mode": "Count the days from",
|
||||
"attachment-retention-purge": "Remove deleted attachments for good after (days)",
|
||||
"attachment-retention-purge-help": "After this they can no longer be restored. Empty or 0: never.",
|
||||
"attachment-retention-dry-run": "Dry run: only count, in the server log, change nothing",
|
||||
"attachment-retention-max-per-run": "At most this many attachments per night",
|
||||
"attachment-retention-days": "Rëmôj załączniczi pò (dniach)",
|
||||
"attachment-retention-days-help": "Jak Rëmac na kôrce: historiô kôrtë mòże je przëwrócëc. Pùsté abò 0: nigdë.",
|
||||
"attachment-retention-mode": "Rechùjë dni òd",
|
||||
"attachment-retention-purge": "Rëmôj rëmniãté załączniczi na wiedno pò (dniach)",
|
||||
"attachment-retention-purge-help": "Pòtemù nie mòżna jich ju przëwrócëc. Pùsté abò 0: nigdë.",
|
||||
"attachment-retention-dry-run": "Próba: blós rechùjë w logù serwera, nick nie zmieniôj",
|
||||
"attachment-retention-max-per-run": "Nôwiãcy tëli załączników na noc",
|
||||
"attachment-transfer-limits-description": "Nastôwi nôwiãkszé miarë lopków dlô wgrëwaniô i pòbiéraniô. Bùfòrowanié transferu API mô òsóbné ògrańczenia bezpiecznotë pò stronie serwera.",
|
||||
"attachment-transfer-limits-saved": "Ògrańczenia transferu przëłączników zapisóné.",
|
||||
"attachment-transfer-limits-save-failed": "Nie dało sã zapisac ògrańczeniów transferu przëłączników",
|
||||
@@ -1401,51 +1401,51 @@
|
||||
"error-notAuthorized": "Nié jesteś uprawniony do przeglądania tej strony.",
|
||||
"webhook-title": "Miono webhooka",
|
||||
"webhook-token": "Token (opcjonalny do autoryzacëji)",
|
||||
"webhook-payload-heading": "Webhook data",
|
||||
"webhook-payload-description": "What outgoing webhooks (Rocket.Chat, Slack, ...) send. Default keeps the inherited setting; with nothing set, webhooks send what they always have.",
|
||||
"webhook-payload-text": "Send the message text",
|
||||
"webhook-payload-fields": "Send these fields",
|
||||
"webhook-payload-field-standard": "Standard fields (WEBHOOKS_ATTRIBUTES)",
|
||||
"webhook-payload-field-ids": "IDs",
|
||||
"webhook-payload-field-names": "Names and titles",
|
||||
"webhook-payload-field-links": "Links to the card and board",
|
||||
"webhook-payload-field-actor": "Who did it",
|
||||
"webhook-payload-field-people": "The member or assignee it is about",
|
||||
"webhook-payload-field-details": "Details: comment, description, values and dates",
|
||||
"webhook-hide-identity": "Leave my name out of outgoing webhooks",
|
||||
"webhook-someone": "A member",
|
||||
"webhook-payload-heading": "Pòdôwczi webhooków",
|
||||
"webhook-payload-description": "Co sélają wëchòdzącé webhooczi (Rocket.Chat, Slack, ...). Domëslné zachòwùje dzedziczoné nastôwë; bez nastôwów webhooczi sélają to, co wiedno.",
|
||||
"webhook-payload-text": "Sélôj tekst wiadła",
|
||||
"webhook-payload-fields": "Sélôj te pòla",
|
||||
"webhook-payload-field-standard": "Standardowé pòla (WEBHOOKS_ATTRIBUTES)",
|
||||
"webhook-payload-field-ids": "ID",
|
||||
"webhook-payload-field-names": "Miona i title",
|
||||
"webhook-payload-field-links": "Lënczi do kôrtë i tôflë",
|
||||
"webhook-payload-field-actor": "Chto to zrobił",
|
||||
"webhook-payload-field-people": "Nôleżnik abò òdpòwiedzalny, ò chtërnym to je",
|
||||
"webhook-payload-field-details": "Szczególe: kòmentôrz, òpisënk, wôrtnotë i datumë",
|
||||
"webhook-hide-identity": "Nie dôwôj mòjégò miona do wëchòdzącëch webhooków",
|
||||
"webhook-someone": "Nôleżnik",
|
||||
"notification-delivery-layout": "Ùkłôd",
|
||||
"notification-delivery-layout-classic": "One sentence (classic)",
|
||||
"notification-delivery-layout-clear": "Clearly arranged: board, who, card in bold",
|
||||
"notification-delivery-parts": "Show",
|
||||
"notification-delivery-part-board": "Board name and link",
|
||||
"notification-delivery-part-actor": "Who did it",
|
||||
"notification-delivery-part-card": "Card title and link",
|
||||
"notification-delivery-layout-classic": "Jedno zdanié (klasyczné)",
|
||||
"notification-delivery-layout-clear": "Przezérno: tôfla, chto, kôrta grëbò",
|
||||
"notification-delivery-parts": "Pòkôżë",
|
||||
"notification-delivery-part-board": "Miono i lënk tôflë",
|
||||
"notification-delivery-part-actor": "Chto to zrobił",
|
||||
"notification-delivery-part-card": "Titel i lënk kôrtë",
|
||||
"notification-delivery-part-list": "Lësta",
|
||||
"notification-delivery-part-swimlane": "Stegna",
|
||||
"notification-delivery-part-details": "Comment or changed value",
|
||||
"notification-delivery-part-dates": "Dates",
|
||||
"notification-delivery-part-link": "Link on its own line",
|
||||
"notification-delivery-grouping": "Grouping",
|
||||
"notification-delivery-grouping-all": "Everything due in one message",
|
||||
"notification-delivery-grouping-board": "One per board",
|
||||
"notification-delivery-grouping-card": "One per card",
|
||||
"notification-delivery-grouping-none": "Each on its own",
|
||||
"notification-delivery-schedule": "When to send",
|
||||
"notification-delivery-schedule-immediate": "Immediately",
|
||||
"notification-delivery-schedule-interval": "Every few minutes or hours",
|
||||
"notification-delivery-schedule-daily": "Once a day",
|
||||
"notification-delivery-interval": "Every",
|
||||
"notification-delivery-minutes": "__count__ minutes",
|
||||
"notification-delivery-part-details": "Kòmentôrz abò zmienionô wôrtnota",
|
||||
"notification-delivery-part-dates": "Datumë",
|
||||
"notification-delivery-part-link": "Lënk w swòji régù",
|
||||
"notification-delivery-grouping": "Grëpòwanié",
|
||||
"notification-delivery-grouping-all": "Wszëtkò w jednym wiadle",
|
||||
"notification-delivery-grouping-board": "Jedno na tôflã",
|
||||
"notification-delivery-grouping-card": "Jedno na kôrtã",
|
||||
"notification-delivery-grouping-none": "Kòżdé òsobno",
|
||||
"notification-delivery-schedule": "Czedë sélac",
|
||||
"notification-delivery-schedule-immediate": "Zarô",
|
||||
"notification-delivery-schedule-interval": "Co czile minut abò gòdzin",
|
||||
"notification-delivery-schedule-daily": "Rôz na dzéń",
|
||||
"notification-delivery-interval": "Co",
|
||||
"notification-delivery-minutes": "__count__ minut",
|
||||
"notification-delivery-daily-time": "Ò",
|
||||
"notification-delivery-quiet": "Quiet hours (wait until they end)",
|
||||
"notification-delivery-quiet": "Gòdzënë cëchòtë (żdżë, jaż sã skùńczą)",
|
||||
"notification-delivery-quiet-from": "Òd",
|
||||
"notification-delivery-quiet-to": "Do",
|
||||
"notification-delivery-quiet-clear": "No quiet hours",
|
||||
"notification-delivery-timezone": "Time zone:",
|
||||
"notification-delivery-invalid": "The setting could not be saved.",
|
||||
"notification-group-card": "__count__ notifications on card __card__",
|
||||
"notification-group-board": "__count__ notifications on board __board__",
|
||||
"notification-delivery-quiet-clear": "Bez gòdzënów cëchòtë",
|
||||
"notification-delivery-timezone": "Czasowô strefa:",
|
||||
"notification-delivery-invalid": "Nie szło zapisac nastôwów.",
|
||||
"notification-group-card": "__count__ wiadłów na kôrce __card__",
|
||||
"notification-group-board": "__count__ wiadłów na tôfli __board__",
|
||||
"outgoing-webhooks": "Wëchòdzącé webhooki",
|
||||
"bidirectional-webhooks": "Dwùstronné webhooki",
|
||||
"outgoingWebhooksPopup-title": "Wëchòdzącé webhooki",
|
||||
@@ -1500,7 +1500,7 @@
|
||||
"showLabel-field-on-card": "Pòkôżë etichetã pòla na minikôrce",
|
||||
"showSum-field-on-list": "Pòkôżë sëmã pòl na górze lëstë",
|
||||
"admin-only-field": "Widzewné blós dlô administratorów tôblëcë",
|
||||
"read-only-field": "Read-only: every member sees the value, only board admins change it",
|
||||
"read-only-field": "Blós do czëtaniô: kòżdi nôleżnik widzy wôrtnotã, blós administratorzë tôflë jã zmieniają",
|
||||
"yes": "Jo",
|
||||
"no": "Nié",
|
||||
"accounts": "Kònta",
|
||||
@@ -1692,11 +1692,11 @@
|
||||
"r-format-auto": "Aùtomatné wëkrëwanié",
|
||||
"r-import-workflow-note": "Impòrtëjë wizualny przebieg robòtë wëekspòrtowóny z n8n abò Node-RED. Rozpòznóné wãzłë wëwòłującëch wëdarzeniów i dzejbów są przëpisywóné do reglów WeKan; wãzłë bez przëpisaniô są zgłôszóné.",
|
||||
"r-import-unmapped": "Nie dało sã przëpisac __count__ rézów",
|
||||
"r-wrike-workflow": "Wrike workflow",
|
||||
"r-wrike-workflow-note": "A Wrike workflow is a list of statuses, each in the Active, Completed, Deferred or Cancelled group. Export writes this board's lists as one, named after the board, in the JSON Wrike's API returns for workflows: create that workflow in Wrike before importing the board's Wrike Excel export, so its statuses are applied. Import reads that JSON pasted above, from WeKan or from Wrike's GET /workflows, into the chosen board: a status without a list becomes a list, and a rule marks a card complete when it is moved into a Completed or Cancelled status and incomplete in an Active or Deferred one. Wrike's own automation rules cannot be exported from Wrike.",
|
||||
"r-export-wrike-workflow": "Export Wrike workflow",
|
||||
"r-import-wrike-workflow": "Import Wrike workflow",
|
||||
"r-import-wrike-workflow-done": "Wrike workflow \"__name__\": __lists__ list(s) and __rules__ rule(s) added, __skipped__ part(s) not read",
|
||||
"r-wrike-workflow": "Przebiég robòtë Wrike",
|
||||
"r-wrike-workflow-note": "Przebiég robòtë Wrike to lësta statusów, kòżdi w grëpie Active, Completed, Deferred abò Cancelled. Ekspòrt pisze lëstë ti tôflë jakno jeden przebiég, mionowóny pò tôfli, w JSON, chtëren API Wrike òddôwô dlô przebiégów robòtë: zrobi nen przebiég we Wrike przed impòrtã ekspòrtu Excel ti tôflë do Wrike, żebë jegò statusë bëłë brëkòwóné. Impòrt czëtô nen JSON wklejony wëżi, z WeKan abò z GET /workflows Wrike, do wëbróny tôflë: status bez lëstë stôwô sã lëstą, a prawidło òbznaczô kôrtã jakno skùńczoną, czej je przeniesionô do statusu Completed abò Cancelled, a jakno nieskùńczoną w Active abò Deferred. Włôsnëch prawidłów aùtomatizacje Wrike nie mòżna ekspòrtowac z Wrike.",
|
||||
"r-export-wrike-workflow": "Ekspòrtëjë przebiég robòtë Wrike",
|
||||
"r-import-wrike-workflow": "Impòrtëjë przebiég robòtë Wrike",
|
||||
"r-import-wrike-workflow-done": "Przebiég robòtë Wrike \"__name__\": dodóné __lists__ lëst i __rules__ prawidłów, __skipped__ dzélów nie przeczëtóné",
|
||||
"r-set-scheduled-triggers": "Zaplanowóné wëwòłania",
|
||||
"r-set-button-triggers": "Knąpë",
|
||||
"r-when-scheduled": "Wedle harmonogramù",
|
||||
@@ -1752,8 +1752,8 @@
|
||||
"set-filter": "Nastawi filter",
|
||||
"r-moved-to": "Przeniesóné do",
|
||||
"r-moved-from": "Przeniesóné z",
|
||||
"r-moved-forward": "moved forward to a later list",
|
||||
"r-moved-back": "moved back to an earlier list",
|
||||
"r-moved-forward": "przeniesionô do przódkù na pózniészą lëstã",
|
||||
"r-moved-back": "przeniesionô nazôd na wczasniészą lëstã",
|
||||
"r-archived": "Przeniesóné do archiwùm",
|
||||
"r-unarchived": "przywròcona z archiwùm",
|
||||
"r-commented": "skòmentowónô",
|
||||
@@ -1801,8 +1801,8 @@
|
||||
"r-add-actinguser-member": "Dodôj brëkòwnika, jaczi wëzwòlił nã reglã, jakno nôleżnika",
|
||||
"r-remove-all": "Rëmôj wszëtczich brëkòwnikòw tej kôrtë",
|
||||
"r-assignee": "przëpisóny",
|
||||
"r-add-actinguser-assignee": "Add the user who triggered this rule as an assignee",
|
||||
"r-remove-all-assignees": "Remove all assignees from the card",
|
||||
"r-add-actinguser-assignee": "Dodôj brëkòwnika, chtëren wëzwôł to prawidło, jakno òdpòwiedzalnégò",
|
||||
"r-remove-all-assignees": "Rëmôj wszëtczich òdpòwiedzalnëch z kôrtë",
|
||||
"r-remove-all-labels": "Rëmnąc wszëtczé etiketë z kôrtë",
|
||||
"r-set-color": "Ùstôwi farwã na",
|
||||
"r-checklist": "lëstã kòntrólną",
|
||||
@@ -1876,10 +1876,10 @@
|
||||
"ldap-test-connection": "Sprôbùjë sparłãczenié LDAP",
|
||||
"ldap-test-connection-success": "Sparłãczenié darzëło sã",
|
||||
"ldap-test-connection-error": "Sparłãczenié nie darzëło sã: %s",
|
||||
"ldap-sync-now": "Sync LDAP users now",
|
||||
"ldap-sync-now-done": "LDAP sync finished",
|
||||
"ldap-sync-now-error": "LDAP sync failed: %s",
|
||||
"ldap-sync-now-nothing": "Nothing to sync: turn on LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS or LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED first.",
|
||||
"ldap-sync-now": "Synchronizëjë terô brëkòwników LDAP",
|
||||
"ldap-sync-now-done": "Synchronizacjô LDAP skùńczonô",
|
||||
"ldap-sync-now-error": "Synchronizacjô LDAP sã nie ùdała: %s",
|
||||
"ldap-sync-now-nothing": "Ni ma czegò synchronizowac: nôprzód włączë LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS abò LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED.",
|
||||
"oauth-providers-title": "Dostôwcowie logowaniô OAuth",
|
||||
"oauth-providers-hint": "Kòżdégò dostôwcã mòże włączëc tuwò abò zmienną òkrãżégò OAUTH_*_ENABLED; wôrtnota nastawionô tuwò mô pierszeństwò przed zmienną òkrãżégò. Sekret je trzimóny na serwerze i nigdë nie je pòkazywóny.",
|
||||
"oauth-provider-google": "Google",
|
||||
@@ -1894,7 +1894,7 @@
|
||||
"oauth-provider-secret-set": "Sekret je nastawiony",
|
||||
"oauth-providers-login-style": "Sztél logowaniô",
|
||||
"oauth-providers-merge-existing-users": "Pòłączë z jistniejącym kòntã z tim samim elektronicznym lëstã",
|
||||
"oauth-providers-allowed-email-domains": "Only these email domains may sign in with these providers (comma separated; empty allows all)",
|
||||
"oauth-providers-allowed-email-domains": "Blós te domenë e-mail mògą sã wlogòwac przez tëch dostôwców (òddzeloné przecënkama; pùsté pòzwôlô wszëtczim)",
|
||||
"oauth-account-conflict": "Kònto z tim elektronicznym lëstã ju jistnieje i nie òstało ùsadzoné tą metodą logowaniô",
|
||||
"sign-in-with": "Zalogùj przez %s",
|
||||
"login-origin-mismatch": "Nie mòże skùńczëc logowaniô przez jinszą ùsługã pòd tim adrésã. WeKan je skonfigùrowóny dlô __expected__, ale ta starna òstała òtemkłô pòd __actual__. Òtemkni WeKan pòd __expected__ abò pòproszë administratora, żebë nastôwił ROOT_URL na __actual__.",
|
||||
@@ -2826,8 +2826,8 @@
|
||||
"feature-pilot-users-placeholder": "Miona brëkòwników, rozdzéloné przecënkama",
|
||||
"feature-pilot-users-unknown": "Zapisóné. Nie nalazłë sã ne miona brëkòwników:",
|
||||
"feature-saved": "Zapisóné.",
|
||||
"card-field-visibility": "Card fields",
|
||||
"card-field-visibility-desc": "The fields of Board Settings / Card that boards can show. An unticked field is hidden from every card, minicard and Board Settings / Card of every board. No card data or board setting changes, and ticking it again shows each board as it was set. The order of the fields is each board's own.",
|
||||
"card-field-visibility": "Pòla kôrtë",
|
||||
"card-field-visibility-desc": "Pòla z Nastôwów tôflë / Kôrta, chtërné tôfle mògą pòkazac. Nieòbznaczoné pòle je skrëté na kòżdi kôrce, minikôrce i w Nastôwach tôflë / Kôrta kòżdi tôflë. Niżódné pòdôwczi kôrtë ani nastôwë tôflë sã nie zmieniają, a czej je zôs òbznaczësz, kòżdô tôfla je pòkôzónô tak, jak bëła nastôwionô. Pòrządk pòlów je włôsny dlô kòżdi tôflë.",
|
||||
"features-performance": "Wëdajnosc",
|
||||
"cards-loading": "Ladowanié kôrtów",
|
||||
"cards-loading-auto": "Aùtomatno (lénowò blós dlô wiôldżich tôflów)",
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Połączenié nieudane",
|
||||
"cloud-settings-saved": "Nastôwë składu zapisóné",
|
||||
"cloud-settings-save-failed": "Nie dało sã zapisac nastôwów składu",
|
||||
"cloud": "Chmura",
|
||||
"cloud-settings-desc": "Z WeKan w Weddy, Cloud Panel òbsłëgùje wiele domenów WeKan z jednégò serwera, kòżdi ze swòjima pòdôwkama. Na domenie Cloud Panel SuperAdmin zarządzô domenama ë CloudFlare.",
|
||||
"cloud-panel-domain": "Domena Cloud Panel",
|
||||
"cloud-superadmin-username": "Miono brëkòwnika SuperAdmin",
|
||||
"cloud-saved": "Zapisóné.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel je blós dlô SuperAdmina, na domenie Cloud Panel.",
|
||||
"cloud-customer-name": "Miono klienta",
|
||||
"cloud-customer-email": "Adresa elektroniczny pòcztë klienta",
|
||||
"cloud-billing-email": "Adresa elektroniczny pòcztë do rachunków",
|
||||
"cloud-start-date": "Data zôczątkù",
|
||||
"cloud-end-date": "Data kùńca",
|
||||
"cloudflare-api-key": "Klucz CloudFlare API",
|
||||
"cloudflare-api-key-desc": "Z kluczã CloudFlare API domena, chtërnëj zóna je na twòjim kònce CloudFlare, dostôwô DNS wpis przez proxy ë certifikat CloudFlare Origin. Jinszé domenë dostôwają certifikat Let's Encrypt. Klucz je zapisóny na serwerze ë nigdë wicy nie je pòkôzóny.",
|
||||
"cloudflare-api-key-set": "Klucz CloudFlare API je zapisóny.",
|
||||
"cloudflare-api-key-not-set": "Ni ma zapisónégò klucza CloudFlare API: kòżdô domena dostôwô certifikat Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Przeniesë przëdôwk do Azure Blob Storage",
|
||||
@@ -3276,7 +3291,7 @@
|
||||
"import-report-open-board": "Òtemkni tôflã",
|
||||
"import-parts-instruction": "Importowane są tylko zaznaczone części. Ten sam wybòr jest używany przy eksporcie.",
|
||||
"import-wekan-file": "Impòrt z lopka ekspòrtu .json abò .zip:",
|
||||
"import-file-label": "Choose the export file, or paste its text below:",
|
||||
"import-file-label": "Wëbierzë lopk ekspòrtu abò wklejë jegò tekst niżi:",
|
||||
"sum-of-number-fields": "Sëma numerowëch swòjich pòlów pòkôzónëch na górze lëstë",
|
||||
"date-range-of-fields": "Zakres datów pòlów daty òznaczonëch do pòkazywaniô na górze lëstë",
|
||||
"more-options": "Wicy opcëji",
|
||||
@@ -3303,9 +3318,9 @@
|
||||
"wip-limit-group-select-swimlane": "Wëbierzë stegnã",
|
||||
"wip-limit-group-apply-swimlane": "Zastosëjë do stegnë",
|
||||
"subtask-inherit-parent-labels": "Dostac sztiże starszi kôrtë",
|
||||
"subtask-mark-done": "Mark subtask as done",
|
||||
"subtask-mark-not-done": "Mark subtask as not done",
|
||||
"subtask-done-no-permission": "You cannot change whether this subtask is done",
|
||||
"subtask-mark-done": "Òbznaczë pòdzadanié jakno zrobioné",
|
||||
"subtask-mark-not-done": "Òbznaczë pòdzadanié jakno niezrobioné",
|
||||
"subtask-done-no-permission": "Nie mòżesz zmienic, czë to pòdzadanié je zrobioné",
|
||||
"chart-forecast-none-remaining": "Felë nick òtemkłégò do przewidzeniô - wszëtczé kôrtë są zrobioné.",
|
||||
"chart-forecast-no-velocity": "__remaining__ kôrtów je jesz òtemkłëch; felë skòrim zakùńczeniów do przewidzeniô datë.",
|
||||
"chart-forecast-projected": "Przë slédnym tempie __average__ kôrtów/tidzéń, __remaining__ jesz òtemkłëch kôrtów mô bëc zrobioné do __date__.",
|
||||
@@ -4380,52 +4395,52 @@
|
||||
"rule-email-legacy-attempt-exists": "Nen lëst je ju wësłóny, òdrzucony abò bëła próba jegò wësëłczi.",
|
||||
"rule-email-legacy-not-legacy": "Nen lëst je ju sparłãczony znowa.",
|
||||
"rule-email-legacy-failed": "Nie dało sã skùńczëc ti dzejbë. Òdswiéżë i spróbùjë znowa.",
|
||||
"stuck-sync-operation-heading": "List Sync operations that cannot be replayed",
|
||||
"stuck-sync-operation-description": "Each of these saved List Sync operations stopped part way and can no longer be finished, because its list was removed, recreated or reconfigured, or the user who started it lost write access. While one is pending, that list cannot Sync. Discard it to let the list Sync again: changes already applied stay, the remaining saved changes are never written, and the next Sync compares the list with its source again.",
|
||||
"stuck-sync-operation-list": "Board / list",
|
||||
"stuck-sync-operation-heading": "Òperacje Synchronizacje lëstów, chtërnëch nie mòżna pòwtórzëc",
|
||||
"stuck-sync-operation-description": "Kòżdô z tëch zapisónëch òperacjów Synchronizacje lëstów sã zatrzima w pòłowie i nie mòże ju bëc skùńczonô, bò ji lësta òsta rëmniãtô, zrobionô znowa abò przenastôwionô, abò brëkòwnik, chtëren jã zaczął, stracył prawò do zapisu. Pòczi jedna żdże, ta lësta nie mòże sã synchronizowac. Òdrzucë jã, żebë lësta mògła zôs sã synchronizowac: ju ùżëté zmianë òstôwają, reszta zapisónëch zmianów nigdë nie mdze zapisónô, a pòstãpnô Synchronizacjô zôs pòrównô lëstã z ji zdrzódłã.",
|
||||
"stuck-sync-operation-list": "Tôfla / lësta",
|
||||
"stuck-sync-operation-progress": "Pòstãp",
|
||||
"stuck-sync-operation-reason": "Powòd",
|
||||
"stuck-sync-operation-applied": "__applied__ of __total__ changes applied",
|
||||
"stuck-sync-operation-reason-scope-changed": "The list was removed, recreated or its Sync settings changed",
|
||||
"stuck-sync-operation-reason-access-denied": "The user who started it no longer has write access to the whole list",
|
||||
"stuck-sync-operation-reason-trigger-unknown": "Saved by an earlier WeKan version without what started it",
|
||||
"stuck-sync-operation-reason-intent-missing": "The record of who started it is missing",
|
||||
"stuck-sync-operation-reason-unknown": "Could not be checked now",
|
||||
"stuck-sync-operation-replayable-now": "It can be replayed again now and will finish by itself, so it cannot be discarded.",
|
||||
"stuck-sync-operation-applied": "Ùżëté __applied__ z __total__ zmianów",
|
||||
"stuck-sync-operation-reason-scope-changed": "Lësta òsta rëmniãtô, zrobionô znowa abò ji nastôwë Synchronizacje sã zmieniłë",
|
||||
"stuck-sync-operation-reason-access-denied": "Brëkòwnik, chtëren to zaczął, ni mô ju prawa do zapisu całi lëstë",
|
||||
"stuck-sync-operation-reason-trigger-unknown": "Zapisóné przez starszą wersjã WeKan bez tegò, co to zaczãło",
|
||||
"stuck-sync-operation-reason-intent-missing": "Felëje zapisu ò tim, chto to zaczął",
|
||||
"stuck-sync-operation-reason-unknown": "Nie szło tegò terô sprawdzëc",
|
||||
"stuck-sync-operation-replayable-now": "Mòżna jã terô zôs pòwtórzëc i skùńczi sã sama, tej nie mòżna ji òdrzucëc.",
|
||||
"stuck-sync-operation-discard": "Òdrzucë",
|
||||
"stuck-sync-operation-discard-confirm": "Discard this saved List Sync operation? Changes it already applied stay; the rest are never written. The list can then Sync again.",
|
||||
"stuck-sync-operation-discard-confirm": "Òdrzucëc nã zapisóną òperacjã Synchronizacje lëstë? Zmianë, chtërné ju ùżëła, òstôwają; reszta nigdë nie mdze zapisónô. Lësta pòtemù zôs mòże sã synchronizowac.",
|
||||
"stuck-sync-operation-refresh": "Òdswiéżë",
|
||||
"stuck-sync-operation-empty": "No List Sync operations are waiting for review.",
|
||||
"stuck-sync-operation-truncated": "Only the oldest 50 are shown. Discard some and refresh to see more.",
|
||||
"stuck-sync-operation-unavailable": "Could not load the List Sync operations. Refresh to try again.",
|
||||
"stuck-sync-operation-missing": "This operation is no longer pending.",
|
||||
"stuck-sync-operation-not-stuck": "This operation has not been found stuck, so it cannot be discarded.",
|
||||
"stuck-sync-operation-replayable": "This operation can be replayed again now and will finish by itself, so it was not discarded.",
|
||||
"stuck-sync-operation-busy": "This list is syncing right now. Try again in a moment.",
|
||||
"stuck-sync-operation-empty": "Niżódné òperacje Synchronizacje lëstów nie żdżą na przezéranié.",
|
||||
"stuck-sync-operation-truncated": "Pòkôzóné są blós nôstarszé 50. Òdrzucë czile i òdswiéżë, żebë òbaczëc wiãcy.",
|
||||
"stuck-sync-operation-unavailable": "Nie szło wczëtac òperacjów Synchronizacje lëstów. Òdswiéżë, żebë spróbòwac zôs.",
|
||||
"stuck-sync-operation-missing": "Na òperacjô ju nie żdże.",
|
||||
"stuck-sync-operation-not-stuck": "Na òperacjô nie òsta nalazłô jakno zawieszonô, tej nie mòżna ji òdrzucëc.",
|
||||
"stuck-sync-operation-replayable": "Na òperacjô mòże bëc terô zôs pòwtórzonô i skùńczi sã sama, tej nie òsta òdrzuconô.",
|
||||
"stuck-sync-operation-busy": "Na lësta sã terô synchronizëje. Spróbùjë zôs za chwilã.",
|
||||
"stuck-sync-operation-failed": "Nie dało sã skùńczëc ti dzejbë. Òdswiéżë i spróbùjë znowa.",
|
||||
"interrupted-import-heading": "Board imports that stopped before finishing",
|
||||
"interrupted-import-description": "Each of these board imports stopped part way - the server restarted, or the import failed - and left a partial board. An import cannot be continued, because its source file is not kept. Keep the board as it is, or discard it and import the file again. Discarding removes the board this import created and everything on it, including anything added to it since.",
|
||||
"interrupted-import-heading": "Impòrtë tôflów, chtërné sã zatrzimałë przed kùńcã",
|
||||
"interrupted-import-description": "Kòżdi z tëch impòrtów tôflów zatrzimôł sã w pòłowie - serwer sã zrestartowôł abò impòrt sã nie ùdôł - i òstawił niefùl tôflã. Impòrtu nie mòżna dali prowadzëc, bò jegò zdrzódłowi lopk nie je trzimóny. Òstawi tôflã, jak je, abò jã òdrzucë i zaimpòrtëjë lopk zôs. Òdrzucenié rëmôwô tôflã, chtërną zrobił nen impòrt, i wszëtkò na ni, téż to, co dodóné pòtemù.",
|
||||
"interrupted-import-board": "Tôfla",
|
||||
"interrupted-import-progress": "Where it stopped",
|
||||
"interrupted-import-created": "Created so far",
|
||||
"interrupted-import-source": "Imported from __source__",
|
||||
"interrupted-import-state-stopped": "Stopped without finishing, at __stage__",
|
||||
"interrupted-import-state-failed": "Failed with __error__, at __stage__",
|
||||
"interrupted-import-state-discarding": "Being discarded; discard again to finish",
|
||||
"interrupted-import-progress": "Gdze sã zatrzimôł",
|
||||
"interrupted-import-created": "Zrobioné do terô",
|
||||
"interrupted-import-source": "Zaimpòrtowóné z __source__",
|
||||
"interrupted-import-state-stopped": "Zatrzimôł sã bez skùńczeniô, przë __stage__",
|
||||
"interrupted-import-state-failed": "Nie ùdôł sã z felą __error__, przë __stage__",
|
||||
"interrupted-import-state-discarding": "Je òdrzucóny; òdrzucë zôs, żebë skùńczëc",
|
||||
"interrupted-import-scrum": "Jegò etap Scrum mô swój pùnkt kòntrólny (__state__, krok __next__ z __total__). Jeżlë zachòwôsz tôflã, skùńczë abò òdrzucë ten etap przez òdzëskiwanié impòrtu Scrum tôflë.",
|
||||
"interrupted-import-counts": "__swimlanes__ swimlanes, __lists__ lists, __cards__ cards, __checklists__ checklists, __comments__ comments, __attachments__ attachments",
|
||||
"interrupted-import-no-board": "No board was created",
|
||||
"interrupted-import-keep": "Keep",
|
||||
"interrupted-import-counts": "__swimlanes__ stegnów, __lists__ lëstów, __cards__ kôrtów, __checklists__ lëstów kòntrolë, __comments__ kòmentôrzów, __attachments__ załączników",
|
||||
"interrupted-import-no-board": "Niżódnô tôfla nie òsta zrobionô",
|
||||
"interrupted-import-keep": "Òstawi",
|
||||
"interrupted-import-discard": "Òdrzucë",
|
||||
"interrupted-import-keep-confirm": "Keep this partial board as it is? Nothing is removed, and the import is no longer listed here.",
|
||||
"interrupted-import-discard-confirm": "Discard this import? The board it created and everything on it are removed permanently.",
|
||||
"interrupted-import-keep-confirm": "Òstawic nã niefùl tôflã, jak je? Nick nie je rëmniãté, a impòrt nie mdze ju tuwò wëmieniony.",
|
||||
"interrupted-import-discard-confirm": "Òdrzucëc nen impòrt? Tôfla, chtërną zrobił, i wszëtkò na ni są rëmniãté na wiedno.",
|
||||
"interrupted-import-refresh": "Òdswiéżë",
|
||||
"interrupted-import-empty": "No interrupted board imports are waiting for review.",
|
||||
"interrupted-import-truncated": "Only the oldest 50 are shown. Keep or discard some and refresh to see more.",
|
||||
"interrupted-import-unavailable": "Could not load the interrupted board imports. Refresh to try again.",
|
||||
"interrupted-import-missing": "This import is no longer recorded.",
|
||||
"interrupted-import-not-interrupted": "This import is not waiting for a decision any more.",
|
||||
"interrupted-import-foreign-board": "The board with this id was not created by this import, so it was not touched.",
|
||||
"interrupted-import-empty": "Niżódné przerwóné impòrtë tôflów nie żdżą na przezéranié.",
|
||||
"interrupted-import-truncated": "Pòkôzóné są blós nôstarszé 50. Òstawi abò òdrzucë czile i òdswiéżë, żebë òbaczëc wiãcy.",
|
||||
"interrupted-import-unavailable": "Nie szło wczëtac przerwónëch impòrtów tôflów. Òdswiéżë, żebë spróbòwac zôs.",
|
||||
"interrupted-import-missing": "Nen impòrt ju nie je zapisóny.",
|
||||
"interrupted-import-not-interrupted": "Nen impòrt ju nie żdże na decyzjã.",
|
||||
"interrupted-import-foreign-board": "Tôfla z tim id nie òsta zrobionô przez nen impòrt, tej nie òsta tkniãtô.",
|
||||
"interrupted-import-scrum-busy": "Jegò etap Scrum je jesz zapisywóny abò òdzëskiwóny. Spróbùjë znowa za czile minut.",
|
||||
"interrupted-import-failed": "Nie dało sã skùńczëc ti dzejbë. Òdswiéżë i spróbùjë znowa.",
|
||||
"saml-login-not-started": "Ne logòwanié SAML nie bëło zaczãté w ti kôrce przezérnika. Zalogùjë sã znowa.",
|
||||
@@ -4446,7 +4461,7 @@
|
||||
"header-login": "Logowanié przez nagłówczi HTTP",
|
||||
"login-setting-clear-secret": "Rëmac wôrtnotã zapisaną w panelu administratora",
|
||||
"login-setting-after-restart": "Zacznie dzejac pò zrëszenim WeKan jesz rôz",
|
||||
"login-setting-env-only": "Only the server environment sets these. They are shown here read-only.",
|
||||
"login-setting-env-only": "Te nastôwë ùstôwiô blós strzodowiszcze serwera. Są tuwò pòkôzóné blós do czëtaniô.",
|
||||
"email-to-card": "Elektroniczny lëst do kôrtë",
|
||||
"email-to-card-description": "Elektroniczny lëst wësłóny na adresã twòji tôflë stôwô sã kôrtą na ti tôflë.",
|
||||
"email-to-card-admin-enabled": "Òdbiérac elektroniczné lëstë jakno kôrtë",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Ҫыхӑну ӑнӑҫсӑр",
|
||||
"cloud-settings-saved": "Упранӑв йӗркелӗвӗсене упранӑ",
|
||||
"cloud-settings-save-failed": "Упранӑв йӗркелӗвӗсене упраймарӗ",
|
||||
"cloud": "Пĕлĕт",
|
||||
"cloud-settings-desc": "Weddy-ри WeKan-па Cloud Panel пĕр серверран нумай WeKan доменне ĕçлеттерет, кашнин хăйĕн даннăйĕсем пур. Cloud Panel доменĕнче SuperAdmin доменсене тата CloudFlare-а йĕркелет.",
|
||||
"cloud-panel-domain": "Cloud Panel доменĕ",
|
||||
"cloud-superadmin-username": "SuperAdmin усăҫă ячӗ",
|
||||
"cloud-saved": "Упранӑ.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel SuperAdmin валли кăна, Cloud Panel доменĕнче.",
|
||||
"cloud-customer-name": "Клиент ячӗ",
|
||||
"cloud-customer-email": "Клиентăн электронлă почта адресӗ",
|
||||
"cloud-billing-email": "Счет-фактура валли электронлă почта адресӗ",
|
||||
"cloud-start-date": "Пуҫламӑш кун",
|
||||
"cloud-end-date": "Вӗҫӗмлӗ кун",
|
||||
"cloudflare-api-key": "CloudFlare API уҫҫи",
|
||||
"cloudflare-api-key-desc": "CloudFlare API уҫҫипе зони сирĕн CloudFlare аккаунтӗнче пулнă домен прокси урлă DNS ҫырăвне тата CloudFlare Origin сертификатне илет. Ытти доменсем Let's Encrypt сертификатне илеҫҫӗ. Уҫҫа серверта упранать, ăна урӑх нихҫан та кăтартмаҫҫӗ.",
|
||||
"cloudflare-api-key-set": "CloudFlare API уҫҫи упранӑ.",
|
||||
"cloudflare-api-key-not-set": "Пӗр CloudFlare API уҫҫи те упранман: кашни домен Let's Encrypt сертификатне илет.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Ҫумлӑ файла Azure Blob Storage патне куҫарас",
|
||||
|
||||
+215
-200
@@ -227,20 +227,20 @@
|
||||
"attachment-soft-delete-pop": "Mae'r ymlyniad wedi'i dynnu o'r cerdyn hwn ac mae ei ffeil yn cael ei chadw. Gall unrhyw un sy'n gallu golygu'r cerdyn ei adfer o hanes y cerdyn.",
|
||||
"attachmentDeletePopup-title": "Dileu Atodiad?",
|
||||
"attachments": "Ymlyniadau",
|
||||
"attach-card": "Attach card",
|
||||
"attachCardPopup-title": "Attach card",
|
||||
"attach-card-hint": "Paste a card link (Copy card link on the other card), or type a card title on this board",
|
||||
"attached-card-unavailable": "An attached card you cannot see",
|
||||
"detach-card": "Remove attached card",
|
||||
"attach-card-self": "A card cannot be attached to itself",
|
||||
"attach-card-limit": "This card already has as many attached cards as it can have",
|
||||
"attach-card-not-found": "No card you can see was found for that link",
|
||||
"attach-card": "Atodi cerdyn",
|
||||
"attachCardPopup-title": "Atodi cerdyn",
|
||||
"attach-card-hint": "Gludwch ddolen cerdyn (Copïo dolen y cerdyn ar y cerdyn arall), neu teipiwch deitl cerdyn ar y bwrdd hwn",
|
||||
"attached-card-unavailable": "Cerdyn wedi'i atodi na allwch ei weld",
|
||||
"detach-card": "Tynnu'r cerdyn wedi'i atodi",
|
||||
"attach-card-self": "Ni ellir atodi cerdyn iddo'i hun",
|
||||
"attach-card-limit": "Mae gan y cerdyn hwn eisoes gymaint o gardiau wedi'u hatodi ag y gall eu cael",
|
||||
"attach-card-not-found": "Ni chafwyd hyd i gerdyn y gallwch ei weld ar gyfer y ddolen honno",
|
||||
"auto-watch": "Gwylio byrddau yn awtomatig pan fyddant yn cael eu creu",
|
||||
"avatar-too-big": "Mae'r avatar yn rhy fawr (__size__ max)",
|
||||
"back": "Yn ol",
|
||||
"board-change-color": "Newid lliw",
|
||||
"board-announcement": "Board announcement",
|
||||
"board-announcement-enabled": "Show it to everyone on this board",
|
||||
"board-announcement": "Cyhoeddiad y bwrdd",
|
||||
"board-announcement-enabled": "Ei ddangos i bawb ar y bwrdd hwn",
|
||||
"board-change-background-image": "Newid Delwedd Gefndir",
|
||||
"board-background-image-url": "URL Delwedd Gefndir",
|
||||
"add-background-image": "Ychwanegu Delwedd Gefndir",
|
||||
@@ -284,7 +284,7 @@
|
||||
"zoom-level": "Lefel Chwyddo",
|
||||
"enter-zoom-level": "Rhowch lefel chwyddo (50-300%):",
|
||||
"board-view-cal": "Calendr",
|
||||
"board-view-calendar-mode": "Calendar Mode",
|
||||
"board-view-calendar-mode": "Modd Calendr",
|
||||
"board-view-multiboard-cal": "Calendr (Pob Bwrdd)",
|
||||
"board-view-swimlanes": "Lonydd nofio",
|
||||
"board-view-collapse": "Cwymp",
|
||||
@@ -686,32 +686,32 @@
|
||||
"chooseBoardSourcePopup-title": "Bwrdd mewnforio",
|
||||
"createLabelPopup-title": "Creu Label",
|
||||
"createCustomField": "Creu Maes",
|
||||
"custom-field-links": "Link fields to another card",
|
||||
"custom-field-links-hint": "Custom fields with the same name and type on both cards are linked: when a value changes on one card, it is copied to the other card. Fields that only one card has are left alone.",
|
||||
"custom-field-links-none": "This card's custom fields are not linked to any card.",
|
||||
"custom-field-link-unavailable": "A linked card you cannot see",
|
||||
"custom-field-link-fields": "Linked fields:",
|
||||
"custom-field-link-no-fields": "No fields with the same name and type yet",
|
||||
"custom-field-link-inactive": "Paused: the person who made this link can no longer edit both cards, or a card is archived.",
|
||||
"custom-field-unlink": "Unlink",
|
||||
"custom-field-link-target": "Card link (Copy card link), or search this board's cards",
|
||||
"custom-field-link-direction": "Direction",
|
||||
"custom-field-link-both": "Both ways: changes on either card go to the other",
|
||||
"custom-field-link-send": "One way: changes on this card go to the main card",
|
||||
"custom-field-link-sends": "Changes on this card go to that card",
|
||||
"custom-field-link-receives": "Changes on that card come to this card",
|
||||
"custom-field-link-add": "Link fields",
|
||||
"field-link-self": "A card cannot be linked to itself.",
|
||||
"field-link-limit": "This card, or that card, already has the most linked cards it can have.",
|
||||
"field-link-archived": "An archived card cannot be linked.",
|
||||
"field-link-invalid": "That is not a card link.",
|
||||
"field-link-not-found": "Card not found.",
|
||||
"field-link-not-allowed": "You need to be able to edit both cards.",
|
||||
"cardCustomFieldLinksPopup-title": "Linked custom fields",
|
||||
"custom-field-links": "Cysylltu meysydd â cherdyn arall",
|
||||
"custom-field-links-hint": "Mae meysydd personol sydd â'r un enw a math ar y ddau gerdyn wedi'u cysylltu: pan fydd gwerth yn newid ar un cerdyn, caiff ei gopïo i'r cerdyn arall. Caiff meysydd sydd gan un cerdyn yn unig eu gadael fel y maent.",
|
||||
"custom-field-links-none": "Nid yw meysydd personol y cerdyn hwn wedi'u cysylltu ag unrhyw gerdyn.",
|
||||
"custom-field-link-unavailable": "Cerdyn cysylltiedig na allwch ei weld",
|
||||
"custom-field-link-fields": "Meysydd cysylltiedig:",
|
||||
"custom-field-link-no-fields": "Dim meysydd â'r un enw a math eto",
|
||||
"custom-field-link-inactive": "Wedi'i oedi: ni all y sawl a wnaeth y cysylltiad hwn olygu'r ddau gerdyn mwyach, neu mae cerdyn wedi'i archifo.",
|
||||
"custom-field-unlink": "Datgysylltu",
|
||||
"custom-field-link-target": "Dolen cerdyn (Copïo dolen y cerdyn), neu chwilio cardiau'r bwrdd hwn",
|
||||
"custom-field-link-direction": "Cyfeiriad",
|
||||
"custom-field-link-both": "Y ddwy ffordd: mae newidiadau ar y naill gerdyn yn mynd i'r llall",
|
||||
"custom-field-link-send": "Un ffordd: mae newidiadau ar y cerdyn hwn yn mynd i'r prif gerdyn",
|
||||
"custom-field-link-sends": "Mae newidiadau ar y cerdyn hwn yn mynd i'r cerdyn hwnnw",
|
||||
"custom-field-link-receives": "Mae newidiadau ar y cerdyn hwnnw yn dod i'r cerdyn hwn",
|
||||
"custom-field-link-add": "Cysylltu meysydd",
|
||||
"field-link-self": "Ni ellir cysylltu cerdyn ag ef ei hun.",
|
||||
"field-link-limit": "Mae gan y cerdyn hwn, neu'r cerdyn hwnnw, eisoes y nifer fwyaf o gardiau cysylltiedig y gall eu cael.",
|
||||
"field-link-archived": "Ni ellir cysylltu cerdyn wedi'i archifo.",
|
||||
"field-link-invalid": "Nid dolen cerdyn yw honno.",
|
||||
"field-link-not-found": "Heb ddod o hyd i'r cerdyn.",
|
||||
"field-link-not-allowed": "Mae angen i chi allu golygu'r ddau gerdyn.",
|
||||
"cardCustomFieldLinksPopup-title": "Meysydd personol cysylltiedig",
|
||||
"createCustomFieldPopup-title": "Creu Maes",
|
||||
"current": "presennol",
|
||||
"custom-color": "Lliw personol",
|
||||
"cards-use-list-color": "Cards without a colour of their own use this colour",
|
||||
"cards-use-list-color": "Mae cardiau heb liw eu hunain yn defnyddio'r lliw hwn",
|
||||
"custom-field-delete-pop": "Does dim dadwneud. Bydd hyn yn dileu'r maes arfer hwn o bob cerdyn ac yn dinistrio ei hanes.",
|
||||
"custom-field-checkbox": "Blwch ticio",
|
||||
"custom-field-currency": "Arian cyfred",
|
||||
@@ -909,20 +909,20 @@
|
||||
"import-board-instruction-zenkit": "Gludwch JSON allforio rhestr ZenKit ({ \"title\", \"stages\":[...], \"items\":[...] }). Daw'r camau'n rhestrau a'r eitemau'n gardiau.",
|
||||
"import-board-instruction-markdown": "Gludwch restr dasgau markdown (y confensiwn y mae offer markdown-kanban fel Obsidian Kanban yn ei ddefnyddio): penawdau \"## Enw'r rhestr\", gyda eitemau \"- [ ]\"/\"- [x]\" oddi tanynt. Mae penawdau'n dod yn rhestrau ac eitemau'n dod yn gardiau; mae rhestr bwled blaen heb flychau ticio hefyd yn cael ei mewnforio, fel cardiau agored.",
|
||||
"import-board-instruction-leo": "Gludwch amlinelliad Leo (ffeil .leo). Mae nodau'r lefel uchaf yn troi'n restrau, mae eu plant yn troi'n gardiau gyda chorff y nod fel disgrifiad, ac mae nodau dyfnach yn troi'n restrau gwirio. Mewnforir nodau wedi'u marcio fel rhai wedi gorffen.",
|
||||
"import-board-instruction-opml": "Paste an OPML outline, as Workflowy, Dynalist, OmniOutliner, Logseq and other outliners export it. Top-level items become lists, their children become cards with the note as the description, and deeper items become checklists. Completed items import as done.",
|
||||
"import-board-instruction-orgmode": "Paste an Org mode file, as Emacs, Orgzly or Beorg write it. Top-level headings become lists and second-level headings become cards. TODO and DONE, priorities, tags, SCHEDULED, DEADLINE and CLOSED are kept, and checkboxes and deeper headings become checklists.",
|
||||
"import-board-instruction-opml": "Gludwch amlinelliad OPML, fel y mae Workflowy, Dynalist, OmniOutliner, Logseq ac amlinellwyr eraill yn ei allforio. Daw eitemau'r lefel uchaf yn rhestrau, daw eu plant yn gardiau gyda'r nodyn yn ddisgrifiad, a daw eitemau dyfnach yn rhestrau gwirio. Caiff eitemau wedi'u cwblhau eu mewnforio fel rhai wedi'u gwneud.",
|
||||
"import-board-instruction-orgmode": "Gludwch ffeil Org mode, fel y mae Emacs, Orgzly neu Beorg yn ei hysgrifennu. Daw penawdau'r lefel uchaf yn rhestrau a daw penawdau'r ail lefel yn gardiau. Cedwir TODO a DONE, blaenoriaethau, tagiau, SCHEDULED, DEADLINE a CLOSED, a daw blychau ticio a phenawdau dyfnach yn rhestrau gwirio.",
|
||||
"import-board-instruction-todotxt": "Gludwch ffeil todo.txt: un dasg fesul llinell, fel y mae ap todo.txt ac apiau cydnaws yn ei hysgrifennu. Mae tasgau gorffenedig (\"x\") yn mynd i Wedi gorffen, mae +project a @context yn troi'n labeli, mae (A) yn troi'n label blaenoriaeth, ac mae due: a t: yn troi'n ddyddiadau cau a dechrau.",
|
||||
"import-board-instruction-taskwarrior": "Gludwch allbwn task export: arae JSON neu un dasg fesul llinell. Mae tasgau gorffenedig yn mynd i Done, daw project a priority yn labeli, annotations yn sylwadau, a depends yn ddibyniaethau ar dasgau sy’n eu rhwystro.",
|
||||
"import-board-instruction-focalboard": "Dadsipiwch y ffeil .boardarchive a gludwch gynnwys board.jsonl y bwrdd, gyda llinell y fersiwn neu hebddi. Daw priodwedd grwpio golwg y bwrdd yn restrau, priodweddau dewis eraill yn labeli, blociau testun a blychau ticio yn ddisgrifiad a rhestr wirio, a chedwir y sylwadau. Ni fewnforir delweddau nac atodiadau.",
|
||||
"import-board-instruction-todoist": "In Todoist, open the project menu and choose Export as a template, then paste the CSV file. Sections become lists, tasks become cards, sub-tasks become a checklist, notes become comments, and @labels and priorities p1 to p3 become labels.",
|
||||
"import-board-instruction-planner": "In Microsoft Planner, open the plan's menu and choose Export plan to Excel, then choose that .xlsx file. Buckets become lists and tasks become cards, with their assignees, dates, labels, checklist and description; Progress, Priority and Completed By become custom fields.",
|
||||
"import-board-instruction-meistertask": "In MeisterTask, click the project name, choose Export project and export the CSV, then paste it. Sections become lists and tasks become cards, with their notes, due date, assignee and tags; completed tasks keep their completion date. A CSV written for MeisterTask's own import works too.",
|
||||
"import-board-instruction-obsidian": "In Obsidian, open the Kanban board's file as Markdown (or open the .md file in a text editor), then paste all of it. Lanes become lists with their card limits, cards keep their tags, dates, priority, fields and checklist, and the Archive becomes archived cards.",
|
||||
"import-board-instruction-linear": "In Linear, export the issues as CSV (Settings, Import / Export, Export data, or a view's export), then paste the CSV. Statuses become lists, teams become swimlanes, and issues become cards with their assignee, labels, dates, parent issue, priority, estimate, project and cycle.",
|
||||
"import-board-instruction-ticktick": "In TickTick, open Settings, Account, Backup & Import and generate a backup, then paste the CSV file. Each TickTick list becomes a swimlane and its columns become lists; tasks keep their tags, dates, checklist, priority and subtasks.",
|
||||
"import-board-instruction-clickup": "In ClickUp, export the tasks as CSV (Settings, Imports / Exports, Export Items, or a List or Table view's export with all columns), then paste the CSV. Statuses become lists, ClickUp lists become swimlanes, and tasks become cards with their subtasks, assignees, tags, dates, priority and time.",
|
||||
"import-board-instruction-nullboard": "In Nullboard, open the board, choose Export this board... from the menu, open the .nbx file in a text editor and paste its contents. Lists become lists and notes become cards: a note's first line is the card title and the rest its description. A raw note gets the label raw. A file with several boards imports its first board.",
|
||||
"import-board-instruction-kanri": "In Kanri, open Import & Export and choose Export individual board (or Export all data), then paste the .json file. Columns become lists and cards become cards, with their description, due date, tags and colors; a card's tasks become a checklist. From an all-data export the first board is imported.",
|
||||
"import-board-instruction-meistertask": "Yn MeisterTask, cliciwch enw'r prosiect, dewiswch Export project ac allforiwch y CSV, yna gludwch ef. Daw adrannau yn rhestrau a thasgau yn gardiau, gyda'u nodiadau, dyddiad cwblhau, aseinai a thagiau; mae tasgau wedi'u cwblhau yn cadw eu dyddiad cwblhau. Mae CSV a ysgrifennwyd ar gyfer mewnforio MeisterTask ei hun yn gweithio hefyd.",
|
||||
"import-board-instruction-obsidian": "Yn Obsidian, agorwch ffeil y bwrdd Kanban fel Markdown (neu agorwch y ffeil .md mewn golygydd testun), yna gludwch y cyfan. Daw lonydd yn rhestrau gyda'u terfynau cardiau, mae cardiau'n cadw eu tagiau, dyddiadau, blaenoriaeth, meysydd a rhestr wirio, a daw'r Archive yn gardiau wedi'u harchifo.",
|
||||
"import-board-instruction-linear": "Yn Linear, allforiwch y materion fel CSV (Settings, Import / Export, Export data, neu allforio golwg), yna gludwch y CSV. Daw statwsau yn rhestrau, timau yn lonydd nofio, a materion yn gardiau gyda'u haseinai, labeli, dyddiadau, mater rhiant, blaenoriaeth, amcangyfrif, prosiect a chylch.",
|
||||
"import-board-instruction-ticktick": "Yn TickTick, agorwch Settings, Account, Backup & Import a chynhyrchwch gopi wrth gefn, yna gludwch y ffeil CSV. Daw pob rhestr TickTick yn lôn nofio a'i cholofnau yn rhestrau; mae tasgau'n cadw eu tagiau, dyddiadau, rhestr wirio, blaenoriaeth ac is-dasgau.",
|
||||
"import-board-instruction-clickup": "Yn ClickUp, allforiwch y tasgau fel CSV (Settings, Imports / Exports, Export Items, neu allforio golwg List neu Table gyda phob colofn), yna gludwch y CSV. Daw statwsau yn rhestrau, rhestrau ClickUp yn lonydd nofio, a thasgau yn gardiau gyda'u his-dasgau, aseineion, tagiau, dyddiadau, blaenoriaeth ac amser.",
|
||||
"import-board-instruction-nullboard": "Yn Nullboard, agorwch y bwrdd, dewiswch Export this board... o'r ddewislen, agorwch y ffeil .nbx mewn golygydd testun a gludwch ei chynnwys. Daw rhestrau yn rhestrau a nodiadau yn gardiau: llinell gyntaf nodyn yw teitl y cerdyn a'r gweddill yw ei ddisgrifiad. Mae nodyn crai yn cael y label raw. O ffeil gyda sawl bwrdd, caiff y bwrdd cyntaf ei fewnforio.",
|
||||
"import-board-instruction-kanri": "Yn Kanri, agorwch Import & Export a dewiswch Export individual board (neu Export all data), yna gludwch y ffeil .json. Daw colofnau yn rhestrau a chardiau yn gardiau, gyda'u disgrifiad, dyddiad cwblhau, tagiau a lliwiau; daw tasgau cerdyn yn rhestr wirio. O allforio'r holl ddata, caiff y bwrdd cyntaf ei fewnforio.",
|
||||
"import-board-instruction-planka": "Teipiwch gyfeiriad eich Planka (er enghraifft https://planka.example.com), eich enw defnyddiwr a'ch cyfrinair Planka, yna pwyswch Rhestru. Fe'u defnyddir dim ond i nôl y bwrdd ac ni chânt eu cadw. Dewiswch fwrdd a phwyswch Nôl: mae WeKan yn ei ddarllen o API Planka, gyda'i restrau, cardiau, disgrifiadau, dyddiadau dyledus, labeli, aelodau, rhestrau tasgau a sylwadau, yna parhewch â'r mewnforio fel arfer. Caiff cardiau yn archif Planka eu mewnforio wedi'u harchifo; ni chaiff atodiadau a chardiau yn y sbwriel eu mewnforio.",
|
||||
"import-board-instruction-mstodo": "Yn Microsoft Graph Explorer (developer.microsoft.com/graph/graph-explorer), mewngofnodwch, cydsyniwch i'r caniatâd Tasks.Read a chopïwch y tocyn o'r tab Access token. Gludwch ef yma a chliciwch Rhestru; defnyddir y tocyn ar gyfer y nôl hwn yn unig ac ni chaiff ei gadw. Dewiswch bob rhestr neu un rhestr a chliciwch Nôl. Daw pob rhestr To Do yn rhestr a phob tasg yn gerdyn, gyda'i nodiadau, dyddiadau dyledus a dechrau, camau fel rhestr wirio, categorïau fel labeli, a phwysigrwydd uchel a statws fel labeli; caiff tasgau wedi'u cwblhau eu harchifo. Ni chaiff nodiadau atgoffa ac ailadroddiadau eu mewnforio.",
|
||||
"import-board-instruction-kanbanflow": "Yn KanbanFlow, agorwch ddewislen y bwrdd, dewiswch Settings → API & Webhooks a chopïwch docyn API y bwrdd. Gludwch y tocyn yma, cliciwch Rhestru, dewiswch y bwrdd a chliciwch Nôl; defnyddir y tocyn ar gyfer y nôl hwnnw yn unig ac ni chaiff ei gadw. Daw colofnau'n rhestrau ac mae lonydd nofio'n aros yn lonydd nofio; mae tasgau'n cadw eu disgrifiad, lliw, labeli, dyddiad dyledus, is-dasgau fel rhestr wirio, amser a dreuliwyd, amcangyfrif, pobl a sylwadau.",
|
||||
@@ -941,19 +941,19 @@
|
||||
"api-import-fetched": "Wedi'i nôl. Gwiriwch yr aelodau isod a mewnforiwch.",
|
||||
"api-import-failed": "Methodd y nôl",
|
||||
"api-import-nothing": "Does dim i'w fewnforio gyda'r cyfrif hwn.",
|
||||
"import-board-instruction-pivotal": "In Pivotal Tracker, open the project's MORE tab and choose Export CSV (or select stories and choose Bulk Actions, CSV), then paste the CSV. Each story state becomes a list and stories become cards, with their type and labels, owners, requester, dates, comments, tasks and blockers; Estimate becomes Story points and iterations become sprints.",
|
||||
"import-board-instruction-tasksorg": "In Tasks.org, open Settings, Backups and choose Export tasks, then paste the contents of that .json file. Lists become lists and tasks become cards, with their notes, tags, dates, comments and subtasks; completed tasks keep their completion date, and priority becomes a custom field.",
|
||||
"import-board-instruction-monday": "In monday.com, open the board menu, choose More actions and Export board to Excel (with updates and subitems if you like), then choose that .xlsx file. The Status column becomes lists and groups become swimlanes; people, dates, timeline and tags keep their places, other columns become custom fields and updates become comments.",
|
||||
"import-board-instruction-superproductivity": "In Super Productivity, open Settings, then Sync & Backup, choose Export Data and paste the contents of the sp-backup .json file. Each project becomes a swimlane and tasks become cards in To Do, In Progress, Backlog or Done, with their notes, tags, due date, time spent and sub-tasks; archived tasks become archived cards.",
|
||||
"import-board-instruction-taiga": "In Taiga, open Admin > Project > Export and download the project dump (unpack a .json.gz file first), then paste its JSON. User story statuses become lists, user stories become cards with their tasks as subtasks, epics and issues get swimlanes of their own, and sprints, tags, comments and custom attributes come along. Attachments are not imported.",
|
||||
"import-board-instruction-vikunja": "In Vikunja, open Settings, choose Data Export and download the .zip file it sends you, then choose that file, or paste the data.json inside it. Kanban buckets become lists and tasks become cards, with their labels, assignees, dates, description, task lists as checklists and comments; priority, percent done and done become custom fields. Attachments are not imported.",
|
||||
"import-board-instruction-quire": "In Quire, open the project menu and choose Export CSV, then paste the CSV. Statuses become lists and tasks become cards, with their subtasks as child cards, assignees, tags as labels, start, due and completed dates, description and priority as a custom field. Comments and attachments are not in Quire's CSV.",
|
||||
"import-board-instruction-wrike": "In Wrike, export a project or folder to Excel, or fill in Wrike's Excel import template, then choose that .xlsx file. Folder and project rows become swimlanes, Status becomes lists and tasks become cards, with their Key, parent task, assignees, start and end dates, dependencies and description; Priority, Duration and other columns become custom fields.",
|
||||
"import-board-instruction-teamwork": "In Teamwork.com, fill in the Excel task import template (Tasklist, Task, Description, Assign to, Start date, Due date, Priority, Estimated time, Tags, Status) and save it as .xlsx, then choose that file. Task lists become lists and tasks become cards; a task starting with -, # or > becomes a subtask of the task above it (--, ## or >> one level deeper). Assignees, dates and tags keep their places; Priority, Estimated time and Complete become custom fields.",
|
||||
"import-board-instruction-businessmap": "In Businessmap (formerly Kanbanize), open Advanced Search, choose Configure results to pick the columns, download the Excel file, then choose that .xlsx file. Columns are read by their English names (Title, Column, Lane, Owner, Deadline ...): if your account uses another language, rename the header row to English first. Column becomes lists and Lane swimlanes; owners, tags, color, dates, comments and parent links keep their places, and Priority, Size, Type and other columns become custom fields.",
|
||||
"import-board-instruction-redmine": "In Redmine, open the project's Issues, choose Also available in: CSV at the bottom of the list, pick the columns (All columns, with Description) and export, then paste the CSV. Redmine names the columns in your language, so set your language to English in My account before exporting. Each status becomes a list and issues become cards, with the tracker as a label, assignee, author, dates, parent task and related issues; priority, category, target version, estimated time, % Done and custom fields become custom fields.",
|
||||
"import-board-instruction-notion": "In Notion, open the database as a full page, choose ••• and Export, pick Markdown & CSV, and choose the .zip file it downloads, or one database CSV from it, or paste that CSV. A column named Status becomes the lists (without one, the first column that looks like a select does, and the import report says which); rows become cards with their title, tags as labels, people as members, dates, and the page text as the description; other columns become custom fields. Relations, images and attachments are not imported.",
|
||||
"import-board-instruction-plane": "In Plane, open Workspace Settings, choose Exports, export the projects as JSON, CSV or Excel and download the .zip file, then choose that file, or one file from inside it, or paste the JSON or CSV. States become lists, projects become swimlanes, and issues become cards with their identifier, parent, assignees, labels, dates, links and comments; priority, estimate, cycle and module become custom fields. Plane's export has no descriptions or attachments, so they are not imported.",
|
||||
"import-board-instruction-pivotal": "Yn Pivotal Tracker, agorwch dab MORE y prosiect a dewiswch Export CSV (neu dewiswch straeon a dewiswch Bulk Actions, CSV), yna gludwch y CSV. Daw pob cyflwr stori yn rhestr a straeon yn gardiau, gyda'u math a'u labeli, perchnogion, gofynnwr, dyddiadau, sylwadau, tasgau a rhwystrau; daw Estimate yn Story points a daw iteriadau yn sbrintiau.",
|
||||
"import-board-instruction-tasksorg": "Yn Tasks.org, agorwch Settings, Backups a dewiswch Export tasks, yna gludwch gynnwys y ffeil .json honno. Daw rhestrau yn rhestrau a thasgau yn gardiau, gyda'u nodiadau, tagiau, dyddiadau, sylwadau ac is-dasgau; mae tasgau wedi'u cwblhau yn cadw eu dyddiad cwblhau, a daw blaenoriaeth yn faes personol.",
|
||||
"import-board-instruction-monday": "Yn monday.com, agorwch ddewislen y bwrdd, dewiswch More actions ac Export board to Excel (gyda diweddariadau ac is-eitemau os mynnwch), yna dewiswch y ffeil .xlsx honno. Daw'r golofn Status yn rhestrau a grwpiau yn lonydd nofio; mae pobl, dyddiadau, llinell amser a thagiau yn cadw eu lle, daw colofnau eraill yn feysydd personol a diweddariadau yn sylwadau.",
|
||||
"import-board-instruction-superproductivity": "Yn Super Productivity, agorwch Settings, yna Sync & Backup, dewiswch Export Data a gludwch gynnwys y ffeil sp-backup .json. Daw pob prosiect yn lôn nofio a thasgau yn gardiau yn To Do, In Progress, Backlog neu Done, gyda'u nodiadau, tagiau, dyddiad cwblhau, amser a dreuliwyd ac is-dasgau; daw tasgau wedi'u harchifo yn gardiau wedi'u harchifo.",
|
||||
"import-board-instruction-taiga": "Yn Taiga, agorwch Admin > Project > Export a lawrlwythwch ddymp y prosiect (dadbaciwch ffeil .json.gz yn gyntaf), yna gludwch ei JSON. Daw statwsau straeon defnyddwyr yn rhestrau, straeon defnyddwyr yn gardiau gyda'u tasgau yn is-dasgau, mae epigau a materion yn cael lonydd nofio eu hunain, a daw sbrintiau, tagiau, sylwadau a phriodoleddau personol hefyd. Ni chaiff ymlyniadau eu mewnforio.",
|
||||
"import-board-instruction-vikunja": "Yn Vikunja, agorwch Settings, dewiswch Data Export a lawrlwythwch y ffeil .zip y mae'n ei hanfon atoch, yna dewiswch y ffeil honno, neu gludwch y data.json sydd ynddi. Daw bwcedi Kanban yn rhestrau a thasgau yn gardiau, gyda'u labeli, aseineion, dyddiadau, disgrifiad, rhestrau tasgau fel rhestrau gwirio a sylwadau; daw blaenoriaeth, canran wedi'i gwneud a gwneud yn feysydd personol. Ni chaiff ymlyniadau eu mewnforio.",
|
||||
"import-board-instruction-quire": "Yn Quire, agorwch ddewislen y prosiect a dewiswch Export CSV, yna gludwch y CSV. Daw statwsau yn rhestrau a thasgau yn gardiau, gyda'u his-dasgau yn gardiau plentyn, aseineion, tagiau fel labeli, dyddiadau dechrau, cwblhau a gorffen, disgrifiad a blaenoriaeth fel maes personol. Nid yw sylwadau ac ymlyniadau yn CSV Quire.",
|
||||
"import-board-instruction-wrike": "Yn Wrike, allforiwch brosiect neu ffolder i Excel, neu llenwch dempled mewnforio Excel Wrike, yna dewiswch y ffeil .xlsx honno. Daw rhesi ffolder a phrosiect yn lonydd nofio, Status yn rhestrau a thasgau yn gardiau, gyda'u Key, tasg rhiant, aseineion, dyddiadau dechrau a gorffen, dibyniaethau a disgrifiad; daw Priority, Duration a cholofnau eraill yn feysydd personol.",
|
||||
"import-board-instruction-teamwork": "Yn Teamwork.com, llenwch dempled mewnforio tasgau Excel (Tasklist, Task, Description, Assign to, Start date, Due date, Priority, Estimated time, Tags, Status) a'i gadw fel .xlsx, yna dewiswch y ffeil honno. Daw rhestrau tasgau yn rhestrau a thasgau yn gardiau; daw tasg sy'n dechrau gyda -, # neu > yn is-dasg i'r dasg uwch ei phen (--, ## neu >> un lefel yn ddyfnach). Mae aseineion, dyddiadau a thagiau yn cadw eu lle; daw Priority, Estimated time a Complete yn feysydd personol.",
|
||||
"import-board-instruction-businessmap": "Yn Businessmap (Kanbanize gynt), agorwch Advanced Search, dewiswch Configure results i ddewis y colofnau, lawrlwythwch y ffeil Excel, yna dewiswch y ffeil .xlsx honno. Darllenir colofnau yn ôl eu henwau Saesneg (Title, Column, Lane, Owner, Deadline ...): os yw'ch cyfrif yn defnyddio iaith arall, ailenwch res y pennawd i Saesneg yn gyntaf. Daw Column yn rhestrau a Lane yn lonydd nofio; mae perchnogion, tagiau, lliw, dyddiadau, sylwadau a dolenni rhiant yn cadw eu lle, a daw Priority, Size, Type a cholofnau eraill yn feysydd personol.",
|
||||
"import-board-instruction-redmine": "Yn Redmine, agorwch Issues y prosiect, dewiswch Also available in: CSV ar waelod y rhestr, dewiswch y colofnau (All columns, gyda Description) ac allforiwch, yna gludwch y CSV. Mae Redmine yn enwi'r colofnau yn eich iaith chi, felly gosodwch eich iaith i Saesneg yn My account cyn allforio. Daw pob statws yn rhestr a materion yn gardiau, gyda'r tracker fel label, aseinai, awdur, dyddiadau, tasg rhiant a materion cysylltiedig; daw blaenoriaeth, categori, fersiwn targed, amser amcangyfrifedig, % Done a meysydd personol yn feysydd personol.",
|
||||
"import-board-instruction-notion": "Yn Notion, agorwch y gronfa ddata fel tudalen lawn, dewiswch ••• ac Export, dewiswch Markdown & CSV, a dewiswch y ffeil .zip y mae'n ei lawrlwytho, neu un CSV cronfa ddata ohoni, neu gludwch y CSV hwnnw. Daw colofn o'r enw Status yn rhestrau (hebddi, y golofn gyntaf sy'n edrych fel dewis, ac mae'r adroddiad mewnforio yn dweud pa un); daw rhesi yn gardiau gyda'u teitl, tagiau fel labeli, pobl fel aelodau, dyddiadau, a thestun y dudalen fel disgrifiad; daw colofnau eraill yn feysydd personol. Ni chaiff perthnasoedd, delweddau ac ymlyniadau eu mewnforio.",
|
||||
"import-board-instruction-plane": "Yn Plane, agorwch Workspace Settings, dewiswch Exports, allforiwch y prosiectau fel JSON, CSV neu Excel a lawrlwythwch y ffeil .zip, yna dewiswch y ffeil honno, neu un ffeil o'i mewn, neu gludwch y JSON neu'r CSV. Daw cyflyrau yn rhestrau, prosiectau yn lonydd nofio, a materion yn gardiau gyda'u dynodwr, rhiant, aseineion, labeli, dyddiadau, dolenni a sylwadau; daw blaenoriaeth, amcangyfrif, cylch a modiwl yn feysydd personol. Nid oes disgrifiadau nac ymlyniadau yn allforio Plane, felly ni chânt eu mewnforio.",
|
||||
"import-board-instruction-trello": "Yn eich bwrdd Trello, ewch i 'Dewislen', yna 'Mwy', 'Argraffu ac Allforio', 'Allforio JSON', a chopïwch y testun canlyniadol.",
|
||||
"import-board-instruction-csv": "Gludwch eich Gwerthoedd Wedi'u Gwahanu gan Goma (CSV)/Gwerthoedd Wedi'u Gwahanu â Thabiau (TSV) i mewn .",
|
||||
"import-board-instruction-jira": "Gludwch y JSON o chwiliad materion API REST Jira Cloud (GET /rest/api/3/search/jql), neu wrthrych cyfatebol { \"issues\": [...] }. Daw statwsau Jira'n rhestrau a materion yn gardiau. Yn ddewisol, cynhwyswch arae \"automationRules\" i fewnforio rheolau.",
|
||||
@@ -962,33 +962,33 @@
|
||||
"import-board-instruction-wekan": "Yn eich bwrdd, ewch i 'Dewislen', yna 'Bwrdd Allforio', a chopïwch y testun yn y ffeil wedi'i lawrlwytho.",
|
||||
"import-board-instruction-about-errors": "Os cewch wallau wrth fewnforio bwrdd, weithiau mae mewnforio yn dal i weithio, ac mae bwrdd ar dudalen All Boards.",
|
||||
"import-without-mapping-members": "Mewnforio heb fapio aelodau (mapio'n ddiweddarach)",
|
||||
"import-members-mode-heading": "People in the file:",
|
||||
"import-members-mode-map": "Choose an existing user for each of them",
|
||||
"import-members-mode-placeholder": "Keep them as placeholder users with their own names, to map to real users later",
|
||||
"import-members-mode-me": "Make them all me",
|
||||
"csv-mapping-title": "Which column is which?",
|
||||
"csv-mapping-description": "Choose the column of the file that holds each card field. The choices were made from the column names; change any that are wrong, and choose (not in the file) for a field the file does not have.",
|
||||
"csv-mapping-not-in-file": "(not in the file)",
|
||||
"csv-mapping-column-number": "Column __number__",
|
||||
"csv-mapping-list-missing": "The file has no column for the list. Choose one above, or type the name of the one list every card goes into.",
|
||||
"csv-mapping-list-name": "List for every card",
|
||||
"csv-mapping-custom-fields": "Other columns to import as text custom fields:",
|
||||
"csv-mapping-boards": "Each sheet becomes its own board:",
|
||||
"csv-mapping-skipped-sheets": "Sheets that are not imported:",
|
||||
"csv-mapping-title-required": "Choose the column that holds the card titles.",
|
||||
"csv-mapping-list-required": "Type the name of the list every card goes into, or choose the column that holds the list.",
|
||||
"invalid-import-mapping": "The column mapping does not match the file.",
|
||||
"import-many-boards": "Import many boards",
|
||||
"import-many-boards-hint": "Choose several export files of this tool, or one .zip that holds them: each file becomes its own board, imported without member mapping. A .zip that is itself one export (Vikunja, Notion, Plane, or WeKan with attachments) is one board. When files are chosen here, Import imports them instead of the field above.",
|
||||
"import-one-board-per-project": "One board per project",
|
||||
"import-one-board-per-project-hint": "Each swimlane the import would make (a project, folder or list of the tool) becomes its own board. Parent cards and dependencies between those boards are listed in the import report.",
|
||||
"import-many-progress": "Importing __done__ of __total__: __name__",
|
||||
"import-many-results-heading": "Imported files",
|
||||
"import-many-imported": "imported",
|
||||
"export-all-boards": "Export all boards",
|
||||
"export-selected-boards": "Export selected boards",
|
||||
"export-all-boards-hint": "Every board you can export, in one download: Excel as one workbook with a sheet per board named after it, every other format as a .zip with a file per board.",
|
||||
"exportAllBoardsPopup-title": "Export all boards",
|
||||
"import-members-mode-heading": "Pobl yn y ffeil:",
|
||||
"import-members-mode-map": "Dewis defnyddiwr presennol ar gyfer pob un ohonynt",
|
||||
"import-members-mode-placeholder": "Eu cadw fel defnyddwyr dalfan gyda'u henwau eu hunain, i'w paru â defnyddwyr go iawn yn nes ymlaen",
|
||||
"import-members-mode-me": "Gwneud pob un ohonynt yn fi",
|
||||
"csv-mapping-title": "Pa golofn yw pa un?",
|
||||
"csv-mapping-description": "Dewiswch golofn y ffeil sy'n dal pob maes cerdyn. Gwnaed y dewisiadau o enwau'r colofnau; newidiwch unrhyw rai sy'n anghywir, a dewiswch (nid yn y ffeil) ar gyfer maes nad yw gan y ffeil.",
|
||||
"csv-mapping-not-in-file": "(nid yn y ffeil)",
|
||||
"csv-mapping-column-number": "Colofn __number__",
|
||||
"csv-mapping-list-missing": "Nid oes gan y ffeil golofn ar gyfer y rhestr. Dewiswch un uchod, neu teipiwch enw'r un rhestr y mae pob cerdyn yn mynd iddi.",
|
||||
"csv-mapping-list-name": "Rhestr ar gyfer pob cerdyn",
|
||||
"csv-mapping-custom-fields": "Colofnau eraill i'w mewnforio fel meysydd personol testun:",
|
||||
"csv-mapping-boards": "Daw pob dalen yn fwrdd ei hun:",
|
||||
"csv-mapping-skipped-sheets": "Dalennau nad ydynt yn cael eu mewnforio:",
|
||||
"csv-mapping-title-required": "Dewiswch y golofn sy'n dal teitlau'r cardiau.",
|
||||
"csv-mapping-list-required": "Teipiwch enw'r rhestr y mae pob cerdyn yn mynd iddi, neu dewiswch y golofn sy'n dal y rhestr.",
|
||||
"invalid-import-mapping": "Nid yw'r mapio colofnau yn cyfateb i'r ffeil.",
|
||||
"import-many-boards": "Mewnforio llawer o fyrddau",
|
||||
"import-many-boards-hint": "Dewiswch sawl ffeil allforio o'r offeryn hwn, neu un .zip sy'n eu dal: daw pob ffeil yn fwrdd ei hun, wedi'i fewnforio heb fapio aelodau. Mae .zip sydd ei hun yn un allforiad (Vikunja, Notion, Plane, neu WeKan gydag ymlyniadau) yn un bwrdd. Pan ddewisir ffeiliau yma, mae Mewnforio yn eu mewnforio yn lle'r maes uchod.",
|
||||
"import-one-board-per-project": "Un bwrdd fesul prosiect",
|
||||
"import-one-board-per-project-hint": "Daw pob lôn nofio y byddai'r mewnforio yn ei gwneud (prosiect, ffolder neu restr o'r offeryn) yn fwrdd ei hun. Rhestrir cardiau rhiant a dibyniaethau rhwng y byrddau hynny yn yr adroddiad mewnforio.",
|
||||
"import-many-progress": "Yn mewnforio __done__ o __total__: __name__",
|
||||
"import-many-results-heading": "Ffeiliau wedi'u mewnforio",
|
||||
"import-many-imported": "wedi'i fewnforio",
|
||||
"export-all-boards": "Allforio pob bwrdd",
|
||||
"export-selected-boards": "Allforio'r byrddau a ddewiswyd",
|
||||
"export-all-boards-hint": "Pob bwrdd y gallwch ei allforio, mewn un lawrlwythiad: Excel fel un llyfr gwaith gyda dalen fesul bwrdd wedi'i henwi ar ei ôl, pob fformat arall fel .zip gyda ffeil fesul bwrdd.",
|
||||
"exportAllBoardsPopup-title": "Allforio pob bwrdd",
|
||||
"import-json-placeholder": "Gludwch eich data JSON dilys yma",
|
||||
"import-csv-placeholder": "Gludwch eich data CSV/TSV dilys yma",
|
||||
"import-json-file": "Neu dewiswch ffeil JSON Trello",
|
||||
@@ -1026,17 +1026,17 @@
|
||||
"trello-import-results": "Canlyniadau mewnforio",
|
||||
"trello-api-credentials-required": "Rhowch yr allwedd a'r tocyn API Trello os gwelwch yn dda.",
|
||||
"trello-api-credentials-saved": "Mae'r allwedd a'r tocyn API Trello wedi'u cadw. Gallwch restru gweithleoedd heb eu hail-nodi.",
|
||||
"exportToTrelloPopup-title": "Export to Trello",
|
||||
"trello-export-desc": "Create a copy of this board in Trello through Trello's API, with your Trello API key and token. Swimlanes become labels named \"Swimlane: …\", which WeKan's Trello import turns back into swimlanes.",
|
||||
"trello-export-include-attachments": "Include attachments",
|
||||
"trello-export-start": "Export to Trello",
|
||||
"trello-export-open-board": "Open the board in Trello",
|
||||
"trello-export-warnings": "Not everything could be exported",
|
||||
"sandstorm-trello-access-needed": "A Sandstorm grain has no Internet access of its own. Allow this grain to reach Trello first.",
|
||||
"sandstorm-allow-trello-access": "Allow access to Trello",
|
||||
"sandstorm-trello-access-granted": "This grain may reach Trello.",
|
||||
"sandstorm-forget-access": "Forget",
|
||||
"sandstorm-trello-save-label": "Trello, for importing and exporting boards",
|
||||
"exportToTrelloPopup-title": "Allforio i Trello",
|
||||
"trello-export-desc": "Creu copi o'r bwrdd hwn yn Trello drwy API Trello, gyda'ch allwedd API a'ch tocyn Trello. Daw lonydd nofio yn labeli o'r enw \"Swimlane: …\", y mae mewnforio Trello WeKan yn eu troi'n ôl yn lonydd nofio.",
|
||||
"trello-export-include-attachments": "Cynnwys ymlyniadau",
|
||||
"trello-export-start": "Allforio i Trello",
|
||||
"trello-export-open-board": "Agor y bwrdd yn Trello",
|
||||
"trello-export-warnings": "Nid oedd modd allforio popeth",
|
||||
"sandstorm-trello-access-needed": "Nid oes gan grain Sandstorm fynediad i'r Rhyngrwyd ei hun. Caniatewch i'r grain hwn gyrraedd Trello yn gyntaf.",
|
||||
"sandstorm-allow-trello-access": "Caniatáu mynediad i Trello",
|
||||
"sandstorm-trello-access-granted": "Caiff y grain hwn gyrraedd Trello.",
|
||||
"sandstorm-forget-access": "Anghofio",
|
||||
"sandstorm-trello-save-label": "Trello, ar gyfer mewnforio ac allforio byrddau",
|
||||
"trello-select-boards": "Dewiswch o leiaf un bwrdd i'w fewnforio os gwelwch yn dda.",
|
||||
"select-all": "Dewis popeth",
|
||||
"select-none": "Dewis dim",
|
||||
@@ -1318,9 +1318,9 @@
|
||||
"external-link-pattern-description": "Yn cysylltu'n awtomatig docynnau ar ffurf \"#1234\" a geir yng nghorff y cerdyn â thraciwr materion allanol. Gadewch faes yn wag i'w analluogi.",
|
||||
"external-link-pattern-prefix": "Rhagddodiad y tocyn",
|
||||
"external-link-pattern-url": "Templed URL (defnyddiwch {number})",
|
||||
"external-link-rules": "More link rules",
|
||||
"external-link-rules-description": "One rule per line: the text to find, \" = \", and the address. Use {number} for digits and {identifier} for a word, in both, for example [{identifier}:{number}] = https://tracker.example.com/{identifier}/{number}.",
|
||||
"external-link-identifier-aliases": "Abbreviations for {identifier}, for example TK=Task, IN=Incident",
|
||||
"external-link-rules": "Rhagor o reolau dolenni",
|
||||
"external-link-rules-description": "Un rheol fesul llinell: y testun i'w ganfod, \" = \", a'r cyfeiriad. Defnyddiwch {number} ar gyfer digidau a {identifier} ar gyfer gair, yn y ddau, er enghraifft [{identifier}:{number}] = https://tracker.example.com/{identifier}/{number}.",
|
||||
"external-link-identifier-aliases": "Byrfoddau ar gyfer {identifier}, er enghraifft TK=Task, IN=Incident",
|
||||
"username": "Enw defnyddiwr",
|
||||
"import-usernames": "Mewnforio Enwau Defnyddwyr",
|
||||
"view-it": "Edrychwch arno",
|
||||
@@ -1345,13 +1345,13 @@
|
||||
"attachment-transfer-limits-title": "Terfynau Maint Ffeil Ymlyniadau ac API",
|
||||
"attachment-limits": "Terfynau",
|
||||
"attachment-retention": "Cadw",
|
||||
"attachment-retention-days": "Delete attachments after (days)",
|
||||
"attachment-retention-days-help": "Like Delete on the card: the card history can restore them. Empty or 0: never.",
|
||||
"attachment-retention-mode": "Count the days from",
|
||||
"attachment-retention-purge": "Remove deleted attachments for good after (days)",
|
||||
"attachment-retention-purge-help": "After this they can no longer be restored. Empty or 0: never.",
|
||||
"attachment-retention-dry-run": "Dry run: only count, in the server log, change nothing",
|
||||
"attachment-retention-max-per-run": "At most this many attachments per night",
|
||||
"attachment-retention-days": "Dileu ymlyniadau ar ôl (diwrnodau)",
|
||||
"attachment-retention-days-help": "Fel Dileu ar y cerdyn: gall hanes y cerdyn eu hadfer. Gwag neu 0: byth.",
|
||||
"attachment-retention-mode": "Cyfrif y diwrnodau o",
|
||||
"attachment-retention-purge": "Tynnu ymlyniadau wedi'u dileu am byth ar ôl (diwrnodau)",
|
||||
"attachment-retention-purge-help": "Ar ôl hyn ni ellir eu hadfer mwyach. Gwag neu 0: byth.",
|
||||
"attachment-retention-dry-run": "Rhediad prawf: cyfrif yn unig, yng nghofnod y gweinydd, heb newid dim",
|
||||
"attachment-retention-max-per-run": "Dim mwy na hyn o ymlyniadau bob nos",
|
||||
"attachment-transfer-limits-description": "Ffurfweddu terfynau maint ffeil uchaf ar gyfer llwytho i fyny a lawrlwytho. Mae gan fyffro trosglwyddo API gapiau diogelwch ar wahân ar ochr y gweinydd.",
|
||||
"attachment-transfer-limits-saved": "Cadwyd terfynau trosglwyddo ymlyniadau.",
|
||||
"attachment-transfer-limits-save-failed": "Methwyd â chadw terfynau trosglwyddo ymlyniadau",
|
||||
@@ -1401,51 +1401,51 @@
|
||||
"error-notAuthorized": "Nid oes gennych awdurdod i weld y dudalen hon.",
|
||||
"webhook-title": "Enw Bachyn",
|
||||
"webhook-token": "Tocyn (Dewisol ar gyfer Dilysu)",
|
||||
"webhook-payload-heading": "Webhook data",
|
||||
"webhook-payload-description": "What outgoing webhooks (Rocket.Chat, Slack, ...) send. Default keeps the inherited setting; with nothing set, webhooks send what they always have.",
|
||||
"webhook-payload-text": "Send the message text",
|
||||
"webhook-payload-fields": "Send these fields",
|
||||
"webhook-payload-field-standard": "Standard fields (WEBHOOKS_ATTRIBUTES)",
|
||||
"webhook-payload-field-ids": "IDs",
|
||||
"webhook-payload-field-names": "Names and titles",
|
||||
"webhook-payload-field-links": "Links to the card and board",
|
||||
"webhook-payload-field-actor": "Who did it",
|
||||
"webhook-payload-field-people": "The member or assignee it is about",
|
||||
"webhook-payload-field-details": "Details: comment, description, values and dates",
|
||||
"webhook-hide-identity": "Leave my name out of outgoing webhooks",
|
||||
"webhook-someone": "A member",
|
||||
"webhook-payload-heading": "Data bachyn gwe",
|
||||
"webhook-payload-description": "Beth mae bachau gwe sy'n mynd allan (Rocket.Chat, Slack, ...) yn ei anfon. Mae'r rhagosodiad yn cadw'r gosodiad a etifeddwyd; heb ddim wedi'i osod, mae bachau gwe yn anfon yr hyn y maent wedi'i anfon erioed.",
|
||||
"webhook-payload-text": "Anfon testun y neges",
|
||||
"webhook-payload-fields": "Anfon y meysydd hyn",
|
||||
"webhook-payload-field-standard": "Meysydd safonol (WEBHOOKS_ATTRIBUTES)",
|
||||
"webhook-payload-field-ids": "Dynodwyr (ID)",
|
||||
"webhook-payload-field-names": "Enwau a theitlau",
|
||||
"webhook-payload-field-links": "Dolenni i'r cerdyn a'r bwrdd",
|
||||
"webhook-payload-field-actor": "Pwy a'i gwnaeth",
|
||||
"webhook-payload-field-people": "Yr aelod neu'r aseinai y mae'n ymwneud ag ef",
|
||||
"webhook-payload-field-details": "Manylion: sylw, disgrifiad, gwerthoedd a dyddiadau",
|
||||
"webhook-hide-identity": "Gadael fy enw allan o fachau gwe sy'n mynd allan",
|
||||
"webhook-someone": "Aelod",
|
||||
"notification-delivery-layout": "Gosodiad",
|
||||
"notification-delivery-layout-classic": "One sentence (classic)",
|
||||
"notification-delivery-layout-clear": "Clearly arranged: board, who, card in bold",
|
||||
"notification-delivery-parts": "Show",
|
||||
"notification-delivery-part-board": "Board name and link",
|
||||
"notification-delivery-part-actor": "Who did it",
|
||||
"notification-delivery-part-card": "Card title and link",
|
||||
"notification-delivery-layout-classic": "Un frawddeg (clasurol)",
|
||||
"notification-delivery-layout-clear": "Wedi'i drefnu'n glir: bwrdd, pwy, cerdyn mewn print trwm",
|
||||
"notification-delivery-parts": "Dangos",
|
||||
"notification-delivery-part-board": "Enw a dolen y bwrdd",
|
||||
"notification-delivery-part-actor": "Pwy a'i gwnaeth",
|
||||
"notification-delivery-part-card": "Teitl a dolen y cerdyn",
|
||||
"notification-delivery-part-list": "Rhestr",
|
||||
"notification-delivery-part-swimlane": "Lôn nofio",
|
||||
"notification-delivery-part-details": "Comment or changed value",
|
||||
"notification-delivery-part-dates": "Dates",
|
||||
"notification-delivery-part-link": "Link on its own line",
|
||||
"notification-delivery-grouping": "Grouping",
|
||||
"notification-delivery-grouping-all": "Everything due in one message",
|
||||
"notification-delivery-grouping-board": "One per board",
|
||||
"notification-delivery-grouping-card": "One per card",
|
||||
"notification-delivery-grouping-none": "Each on its own",
|
||||
"notification-delivery-schedule": "When to send",
|
||||
"notification-delivery-schedule-immediate": "Immediately",
|
||||
"notification-delivery-schedule-interval": "Every few minutes or hours",
|
||||
"notification-delivery-schedule-daily": "Once a day",
|
||||
"notification-delivery-interval": "Every",
|
||||
"notification-delivery-minutes": "__count__ minutes",
|
||||
"notification-delivery-part-details": "Sylw neu werth wedi'i newid",
|
||||
"notification-delivery-part-dates": "Dyddiadau",
|
||||
"notification-delivery-part-link": "Dolen ar ei llinell ei hun",
|
||||
"notification-delivery-grouping": "Grwpio",
|
||||
"notification-delivery-grouping-all": "Popeth sy'n ddyledus mewn un neges",
|
||||
"notification-delivery-grouping-board": "Un fesul bwrdd",
|
||||
"notification-delivery-grouping-card": "Un fesul cerdyn",
|
||||
"notification-delivery-grouping-none": "Pob un ar ei ben ei hun",
|
||||
"notification-delivery-schedule": "Pryd i anfon",
|
||||
"notification-delivery-schedule-immediate": "Ar unwaith",
|
||||
"notification-delivery-schedule-interval": "Bob ychydig funudau neu oriau",
|
||||
"notification-delivery-schedule-daily": "Unwaith y dydd",
|
||||
"notification-delivery-interval": "Bob",
|
||||
"notification-delivery-minutes": "__count__ munud",
|
||||
"notification-delivery-daily-time": "Am",
|
||||
"notification-delivery-quiet": "Quiet hours (wait until they end)",
|
||||
"notification-delivery-quiet": "Oriau tawel (aros nes iddynt ddod i ben)",
|
||||
"notification-delivery-quiet-from": "O",
|
||||
"notification-delivery-quiet-to": "Tan",
|
||||
"notification-delivery-quiet-clear": "No quiet hours",
|
||||
"notification-delivery-timezone": "Time zone:",
|
||||
"notification-delivery-invalid": "The setting could not be saved.",
|
||||
"notification-group-card": "__count__ notifications on card __card__",
|
||||
"notification-group-board": "__count__ notifications on board __board__",
|
||||
"notification-delivery-quiet-clear": "Dim oriau tawel",
|
||||
"notification-delivery-timezone": "Cylchfa amser:",
|
||||
"notification-delivery-invalid": "Nid oedd modd cadw'r gosodiad.",
|
||||
"notification-group-card": "__count__ hysbysiad ar gerdyn __card__",
|
||||
"notification-group-board": "__count__ hysbysiad ar fwrdd __board__",
|
||||
"outgoing-webhooks": "Llyfrau Gwe Allan",
|
||||
"bidirectional-webhooks": "Bachogau Gwe Dwyffordd",
|
||||
"outgoingWebhooksPopup-title": "Llyfrau Gwe Allan",
|
||||
@@ -1500,7 +1500,7 @@
|
||||
"showLabel-field-on-card": "Dangos label maes ar minicard",
|
||||
"showSum-field-on-list": "Dangos swm y meysydd ar frig y rhestr",
|
||||
"admin-only-field": "Gweladwy i weinyddwyr y bwrdd yn unig",
|
||||
"read-only-field": "Read-only: every member sees the value, only board admins change it",
|
||||
"read-only-field": "Darllen yn unig: mae pob aelod yn gweld y gwerth, dim ond gweinyddwyr y bwrdd sy'n ei newid",
|
||||
"yes": "Oes",
|
||||
"no": "Nac ydw",
|
||||
"accounts": "Cyfrifon",
|
||||
@@ -1692,11 +1692,11 @@
|
||||
"r-format-auto": "Canfod yn awtomatig",
|
||||
"r-import-workflow-note": "Mewnforio llif gwaith gweledol a allforiwyd o n8n neu Node-RED. Mapir nodau sbardun/gweithred a adnabyddir i reolau WeKan; adroddir am nodau heb eu mapio.",
|
||||
"r-import-unmapped": "Methwyd â mapio __count__ llinell(au)",
|
||||
"r-wrike-workflow": "Wrike workflow",
|
||||
"r-wrike-workflow-note": "A Wrike workflow is a list of statuses, each in the Active, Completed, Deferred or Cancelled group. Export writes this board's lists as one, named after the board, in the JSON Wrike's API returns for workflows: create that workflow in Wrike before importing the board's Wrike Excel export, so its statuses are applied. Import reads that JSON pasted above, from WeKan or from Wrike's GET /workflows, into the chosen board: a status without a list becomes a list, and a rule marks a card complete when it is moved into a Completed or Cancelled status and incomplete in an Active or Deferred one. Wrike's own automation rules cannot be exported from Wrike.",
|
||||
"r-export-wrike-workflow": "Export Wrike workflow",
|
||||
"r-import-wrike-workflow": "Import Wrike workflow",
|
||||
"r-import-wrike-workflow-done": "Wrike workflow \"__name__\": __lists__ list(s) and __rules__ rule(s) added, __skipped__ part(s) not read",
|
||||
"r-wrike-workflow": "Llif gwaith Wrike",
|
||||
"r-wrike-workflow-note": "Rhestr o statwsau yw llif gwaith Wrike, pob un yn y grŵp Active, Completed, Deferred neu Cancelled. Mae allforio yn ysgrifennu rhestrau'r bwrdd hwn fel un llif, wedi'i enwi ar ôl y bwrdd, yn y JSON y mae API Wrike yn ei ddychwelyd ar gyfer llifoedd gwaith: crëwch y llif gwaith hwnnw yn Wrike cyn mewnforio allforiad Excel Wrike y bwrdd, fel bod ei statwsau yn cael eu cymhwyso. Mae mewnforio yn darllen y JSON hwnnw a gludwyd uchod, o WeKan neu o GET /workflows Wrike, i'r bwrdd a ddewiswyd: daw statws heb restr yn rhestr, ac mae rheol yn nodi cerdyn yn gyflawn pan gaiff ei symud i statws Completed neu Cancelled ac yn anghyflawn mewn un Active neu Deferred. Ni ellir allforio rheolau awtomeiddio Wrike ei hun o Wrike.",
|
||||
"r-export-wrike-workflow": "Allforio llif gwaith Wrike",
|
||||
"r-import-wrike-workflow": "Mewnforio llif gwaith Wrike",
|
||||
"r-import-wrike-workflow-done": "Llif gwaith Wrike \"__name__\": ychwanegwyd __lists__ rhestr a __rules__ rheol, ni ddarllenwyd __skipped__ rhan",
|
||||
"r-set-scheduled-triggers": "Sbardunau wedi'u hamserlennu",
|
||||
"r-set-button-triggers": "Botymau",
|
||||
"r-when-scheduled": "Ar amserlen",
|
||||
@@ -1752,8 +1752,8 @@
|
||||
"set-filter": "Gosod Hidlydd",
|
||||
"r-moved-to": "Wedi symud i",
|
||||
"r-moved-from": "Wedi symud o",
|
||||
"r-moved-forward": "moved forward to a later list",
|
||||
"r-moved-back": "moved back to an earlier list",
|
||||
"r-moved-forward": "symudwyd ymlaen i restr ddiweddarach",
|
||||
"r-moved-back": "symudwyd yn ôl i restr gynharach",
|
||||
"r-archived": "Wedi symud i'r Archif",
|
||||
"r-unarchived": "Wedi'i adfer o'r Archif",
|
||||
"r-commented": "wedi cael sylw",
|
||||
@@ -1801,8 +1801,8 @@
|
||||
"r-add-actinguser-member": "Ychwanegu'r defnyddiwr a sbardunodd y rheol hon fel aelod",
|
||||
"r-remove-all": "Tynnwch yr holl aelodau o'r cerdyn",
|
||||
"r-assignee": "aseinai",
|
||||
"r-add-actinguser-assignee": "Add the user who triggered this rule as an assignee",
|
||||
"r-remove-all-assignees": "Remove all assignees from the card",
|
||||
"r-add-actinguser-assignee": "Ychwanegu'r defnyddiwr a sbardunodd y rheol hon fel aseinai",
|
||||
"r-remove-all-assignees": "Tynnu pob aseinai o'r cerdyn",
|
||||
"r-remove-all-labels": "Tynnu'r holl labeli oddi ar y cerdyn",
|
||||
"r-set-color": "Gosod lliw i",
|
||||
"r-checklist": "rhestr wirio",
|
||||
@@ -1876,10 +1876,10 @@
|
||||
"ldap-test-connection": "Profi Cysylltiad LDAP",
|
||||
"ldap-test-connection-success": "Cysylltiad llwyddiannus",
|
||||
"ldap-test-connection-error": "Methodd y cysylltiad: %s",
|
||||
"ldap-sync-now": "Sync LDAP users now",
|
||||
"ldap-sync-now-done": "LDAP sync finished",
|
||||
"ldap-sync-now-error": "LDAP sync failed: %s",
|
||||
"ldap-sync-now-nothing": "Nothing to sync: turn on LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS or LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED first.",
|
||||
"ldap-sync-now": "Cysoni defnyddwyr LDAP nawr",
|
||||
"ldap-sync-now-done": "Mae cysoni LDAP wedi gorffen",
|
||||
"ldap-sync-now-error": "Methodd cysoni LDAP: %s",
|
||||
"ldap-sync-now-nothing": "Dim i'w gysoni: trowch LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS neu LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED ymlaen yn gyntaf.",
|
||||
"oauth-providers-title": "Darparwyr mewngofnodi OAuth",
|
||||
"oauth-providers-hint": "Gellir troi pob darparwr ymlaen yma neu gyda’i newidyn amgylchedd OAUTH_*_ENABLED; mae gwerth a osodir yma yn diystyru’r newidyn amgylchedd. Caiff y gyfrinach ei storio ar y gweinydd ac ni chaiff byth ei dangos.",
|
||||
"oauth-provider-google": "Google",
|
||||
@@ -1894,7 +1894,7 @@
|
||||
"oauth-provider-secret-set": "Mae cyfrinach wedi’i gosod",
|
||||
"oauth-providers-login-style": "Arddull mewngofnodi",
|
||||
"oauth-providers-merge-existing-users": "Uno â chyfrif presennol sydd â’r un ebost",
|
||||
"oauth-providers-allowed-email-domains": "Only these email domains may sign in with these providers (comma separated; empty allows all)",
|
||||
"oauth-providers-allowed-email-domains": "Dim ond y parthau e-bost hyn gaiff fewngofnodi gyda'r darparwyr hyn (wedi'u gwahanu gan goma; mae gwag yn caniatáu pob un)",
|
||||
"oauth-account-conflict": "Mae cyfrif gyda’r ebost hwn yn bodoli eisoes ac ni chafodd ei greu gyda’r dull mewngofnodi hwn",
|
||||
"sign-in-with": "Mewngofnodi gyda %s",
|
||||
"login-origin-mismatch": "Ni ellir cwblhau mewngofnodi drwy wasanaeth arall yn y cyfeiriad hwn. Mae WeKan wedi’i ffurfweddu ar gyfer __expected__, ond agorwyd y dudalen hon yn __actual__. Agorwch WeKan yn __expected__, neu gofynnwch i’r gweinyddwr osod ROOT_URL i __actual__.",
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Methodd y cysylltiad",
|
||||
"cloud-settings-saved": "Cadwyd gosodiadau storfa",
|
||||
"cloud-settings-save-failed": "Methwyd â chadw gosodiadau storfa",
|
||||
"cloud": "Cwmwl",
|
||||
"cloud-settings-desc": "Gyda WeKan yn Weddy, mae'r Cloud Panel yn gwasanaethu llawer o barthau WeKan o un gweinydd, pob un â'i ddata ei hun. Ar Barth y Cloud Panel, mae'r SuperAdmin yn rheoli'r parthau a CloudFlare.",
|
||||
"cloud-panel-domain": "Parth y Cloud Panel",
|
||||
"cloud-superadmin-username": "Enw defnyddiwr y SuperAdmin",
|
||||
"cloud-saved": "Wedi cadw.",
|
||||
"cloud-panel-not-allowed": "Mae'r Cloud Panel ar gyfer y SuperAdmin yn unig, ar Barth y Cloud Panel.",
|
||||
"cloud-customer-name": "Enw'r cwsmer",
|
||||
"cloud-customer-email": "Cyfeiriad e-bost y cwsmer",
|
||||
"cloud-billing-email": "Cyfeiriad e-bost bilio",
|
||||
"cloud-start-date": "Dyddiad Dechrau",
|
||||
"cloud-end-date": "Dyddiad Gorffen",
|
||||
"cloudflare-api-key": "Allwedd API CloudFlare",
|
||||
"cloudflare-api-key-desc": "Gydag allwedd API CloudFlare, mae parth y mae ei gylchfa yn eich cyfrif CloudFlare yn cael cofnod DNS drwy ddirprwy a thystysgrif CloudFlare Origin. Mae parthau eraill yn cael tystysgrif Let's Encrypt. Caiff yr allwedd ei chadw ar y gweinydd ac ni chaiff ei dangos byth eto.",
|
||||
"cloudflare-api-key-set": "Mae allwedd API CloudFlare wedi'i chadw.",
|
||||
"cloudflare-api-key-not-set": "Does dim allwedd API CloudFlare wedi'i chadw: mae pob parth yn cael tystysgrif Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Symud ymlyniad i Azure Blob Storage",
|
||||
@@ -3276,7 +3291,7 @@
|
||||
"import-report-open-board": "Agor y bwrdd",
|
||||
"import-parts-instruction": "Dim ond y rhannau sydd wedi'u ticio sy'n cael eu mewnforio. Defnyddir yr un dewis wrth allforio.",
|
||||
"import-wekan-file": "Mewnforio o ffeil allforio .json neu .zip:",
|
||||
"import-file-label": "Choose the export file, or paste its text below:",
|
||||
"import-file-label": "Dewiswch y ffeil allforio, neu gludwch ei thestun isod:",
|
||||
"sum-of-number-fields": "Cyfanswm y meysydd rhifol addasedig a ddangosir ar frig y rhestr",
|
||||
"date-range-of-fields": "Ystod dyddiadau meysydd dyddiad personol wedi’u nodi i’w dangos ar frig y rhestr",
|
||||
"more-options": "Rhagor o opsiynau",
|
||||
@@ -3303,9 +3318,9 @@
|
||||
"wip-limit-group-select-swimlane": "Dethol Lôn nofio",
|
||||
"wip-limit-group-apply-swimlane": "Ymgeisiwch Lôn nofio",
|
||||
"subtask-inherit-parent-labels": "Etifeddu labelau'r cerdyn rhiant",
|
||||
"subtask-mark-done": "Mark subtask as done",
|
||||
"subtask-mark-not-done": "Mark subtask as not done",
|
||||
"subtask-done-no-permission": "You cannot change whether this subtask is done",
|
||||
"subtask-mark-done": "Nodi'r is-dasg fel wedi'i gwneud",
|
||||
"subtask-mark-not-done": "Nodi'r is-dasg fel heb ei gwneud",
|
||||
"subtask-done-no-permission": "Ni allwch newid a yw'r is-dasg hon wedi'i gwneud",
|
||||
"chart-forecast-none-remaining": "Dim byd ar ôl yn agored i'w ragamcanu - mae pob cerdyn wedi'i orffen.",
|
||||
"chart-forecast-no-velocity": "Mae __remaining__ cerdyn/cardiau yn dal ar agor; dim cwblhau diweddar i ragamcanu dyddiad ohono.",
|
||||
"chart-forecast-projected": "Ar y gyfradd ddiweddar o __average__ cerdyn/cardiau yr wythnos, dylai'r __remaining__ cerdyn/cardiau sy'n dal ar agor gael eu gorffen erbyn __date__.",
|
||||
@@ -4380,52 +4395,52 @@
|
||||
"rule-email-legacy-attempt-exists": "Mae'r ebost hwn eisoes wedi'i anfon, wedi cael ymgais i'w anfon, neu wedi'i waredu.",
|
||||
"rule-email-legacy-not-legacy": "Mae'r ebost hwn eisoes wedi'i ailgysylltu.",
|
||||
"rule-email-legacy-failed": "Methu cwblhau'r weithred hon. Adnewyddwch a rhowch gynnig arall arni.",
|
||||
"stuck-sync-operation-heading": "List Sync operations that cannot be replayed",
|
||||
"stuck-sync-operation-description": "Each of these saved List Sync operations stopped part way and can no longer be finished, because its list was removed, recreated or reconfigured, or the user who started it lost write access. While one is pending, that list cannot Sync. Discard it to let the list Sync again: changes already applied stay, the remaining saved changes are never written, and the next Sync compares the list with its source again.",
|
||||
"stuck-sync-operation-list": "Board / list",
|
||||
"stuck-sync-operation-heading": "Gweithrediadau Cysoni Rhestr na ellir eu hailchwarae",
|
||||
"stuck-sync-operation-description": "Stopiodd pob un o'r gweithrediadau Cysoni Rhestr hyn a gadwyd hanner ffordd ac ni ellir eu gorffen mwyach, oherwydd i'w rhestr gael ei thynnu, ei hail-greu neu ei hailgyflunio, neu i'r defnyddiwr a'i dechreuodd golli mynediad ysgrifennu. Tra bo un yn yr arfaeth, ni all y rhestr honno Gysoni. Gwaredwch ef i adael i'r rhestr Gysoni eto: mae newidiadau a gymhwyswyd eisoes yn aros, ni chaiff y newidiadau sy'n weddill a gadwyd byth eu hysgrifennu, ac mae'r Cysoni nesaf yn cymharu'r rhestr â'i ffynhonnell eto.",
|
||||
"stuck-sync-operation-list": "Bwrdd / rhestr",
|
||||
"stuck-sync-operation-progress": "Cynnydd",
|
||||
"stuck-sync-operation-reason": "Rheswm",
|
||||
"stuck-sync-operation-applied": "__applied__ of __total__ changes applied",
|
||||
"stuck-sync-operation-reason-scope-changed": "The list was removed, recreated or its Sync settings changed",
|
||||
"stuck-sync-operation-reason-access-denied": "The user who started it no longer has write access to the whole list",
|
||||
"stuck-sync-operation-reason-trigger-unknown": "Saved by an earlier WeKan version without what started it",
|
||||
"stuck-sync-operation-reason-intent-missing": "The record of who started it is missing",
|
||||
"stuck-sync-operation-reason-unknown": "Could not be checked now",
|
||||
"stuck-sync-operation-replayable-now": "It can be replayed again now and will finish by itself, so it cannot be discarded.",
|
||||
"stuck-sync-operation-applied": "Cymhwyswyd __applied__ o __total__ newid",
|
||||
"stuck-sync-operation-reason-scope-changed": "Tynnwyd y rhestr, cafodd ei hail-greu neu newidiwyd ei gosodiadau Cysoni",
|
||||
"stuck-sync-operation-reason-access-denied": "Nid oes gan y defnyddiwr a'i dechreuodd fynediad ysgrifennu i'r rhestr gyfan mwyach",
|
||||
"stuck-sync-operation-reason-trigger-unknown": "Wedi'i gadw gan fersiwn gynharach o WeKan heb yr hyn a'i dechreuodd",
|
||||
"stuck-sync-operation-reason-intent-missing": "Mae'r cofnod o bwy a'i dechreuodd ar goll",
|
||||
"stuck-sync-operation-reason-unknown": "Nid oedd modd ei wirio nawr",
|
||||
"stuck-sync-operation-replayable-now": "Gellir ei ailchwarae eto nawr a bydd yn gorffen ar ei ben ei hun, felly ni ellir ei waredu.",
|
||||
"stuck-sync-operation-discard": "Taflwch",
|
||||
"stuck-sync-operation-discard-confirm": "Discard this saved List Sync operation? Changes it already applied stay; the rest are never written. The list can then Sync again.",
|
||||
"stuck-sync-operation-discard-confirm": "Gwaredu'r gweithrediad Cysoni Rhestr hwn a gadwyd? Mae'r newidiadau a gymhwysodd eisoes yn aros; ni chaiff y gweddill byth eu hysgrifennu. Gall y rhestr Gysoni eto wedyn.",
|
||||
"stuck-sync-operation-refresh": "Adnewyddu",
|
||||
"stuck-sync-operation-empty": "No List Sync operations are waiting for review.",
|
||||
"stuck-sync-operation-truncated": "Only the oldest 50 are shown. Discard some and refresh to see more.",
|
||||
"stuck-sync-operation-unavailable": "Could not load the List Sync operations. Refresh to try again.",
|
||||
"stuck-sync-operation-missing": "This operation is no longer pending.",
|
||||
"stuck-sync-operation-not-stuck": "This operation has not been found stuck, so it cannot be discarded.",
|
||||
"stuck-sync-operation-replayable": "This operation can be replayed again now and will finish by itself, so it was not discarded.",
|
||||
"stuck-sync-operation-busy": "This list is syncing right now. Try again in a moment.",
|
||||
"stuck-sync-operation-empty": "Nid oes gweithrediadau Cysoni Rhestr yn aros am adolygiad.",
|
||||
"stuck-sync-operation-truncated": "Dim ond yr 50 hynaf a ddangosir. Gwaredwch rai ac adnewyddwch i weld rhagor.",
|
||||
"stuck-sync-operation-unavailable": "Nid oedd modd llwytho'r gweithrediadau Cysoni Rhestr. Adnewyddwch i geisio eto.",
|
||||
"stuck-sync-operation-missing": "Nid yw'r gweithrediad hwn yn yr arfaeth mwyach.",
|
||||
"stuck-sync-operation-not-stuck": "Nid yw'r gweithrediad hwn wedi'i ganfod yn sownd, felly ni ellir ei waredu.",
|
||||
"stuck-sync-operation-replayable": "Gellir ailchwarae'r gweithrediad hwn eto nawr a bydd yn gorffen ar ei ben ei hun, felly ni chafodd ei waredu.",
|
||||
"stuck-sync-operation-busy": "Mae'r rhestr hon yn cysoni ar hyn o bryd. Ceisiwch eto mewn eiliad.",
|
||||
"stuck-sync-operation-failed": "Methu cwblhau'r weithred hon. Adnewyddwch a rhowch gynnig arall arni.",
|
||||
"interrupted-import-heading": "Board imports that stopped before finishing",
|
||||
"interrupted-import-description": "Each of these board imports stopped part way - the server restarted, or the import failed - and left a partial board. An import cannot be continued, because its source file is not kept. Keep the board as it is, or discard it and import the file again. Discarding removes the board this import created and everything on it, including anything added to it since.",
|
||||
"interrupted-import-heading": "Mewnforion byrddau a stopiodd cyn gorffen",
|
||||
"interrupted-import-description": "Stopiodd pob un o'r mewnforion byrddau hyn hanner ffordd - ailgychwynnodd y gweinydd, neu methodd y mewnforio - a gadael bwrdd rhannol. Ni ellir parhau â mewnforio, oherwydd ni chedwir ei ffeil ffynhonnell. Cadwch y bwrdd fel y mae, neu gwaredwch ef a mewnforiwch y ffeil eto. Mae gwaredu yn tynnu'r bwrdd a greodd y mewnforio hwn a phopeth arno, gan gynnwys unrhyw beth a ychwanegwyd ato ers hynny.",
|
||||
"interrupted-import-board": "Bwrdd",
|
||||
"interrupted-import-progress": "Where it stopped",
|
||||
"interrupted-import-created": "Created so far",
|
||||
"interrupted-import-source": "Imported from __source__",
|
||||
"interrupted-import-state-stopped": "Stopped without finishing, at __stage__",
|
||||
"interrupted-import-state-failed": "Failed with __error__, at __stage__",
|
||||
"interrupted-import-state-discarding": "Being discarded; discard again to finish",
|
||||
"interrupted-import-progress": "Lle stopiodd",
|
||||
"interrupted-import-created": "Wedi'u creu hyd yma",
|
||||
"interrupted-import-source": "Wedi'i fewnforio o __source__",
|
||||
"interrupted-import-state-stopped": "Stopiodd heb orffen, yn __stage__",
|
||||
"interrupted-import-state-failed": "Methodd gyda __error__, yn __stage__",
|
||||
"interrupted-import-state-discarding": "Yn cael ei waredu; gwaredwch eto i orffen",
|
||||
"interrupted-import-scrum": "Mae gan ei gam Scrum ei bwynt gwirio ei hun (__state__, cam __next__ o __total__). Os ydych yn cadw’r bwrdd, cwblhewch neu gwaredwch y cam hwnnw drwy adfer mewnforio Scrum y bwrdd.",
|
||||
"interrupted-import-counts": "__swimlanes__ swimlanes, __lists__ lists, __cards__ cards, __checklists__ checklists, __comments__ comments, __attachments__ attachments",
|
||||
"interrupted-import-no-board": "No board was created",
|
||||
"interrupted-import-keep": "Keep",
|
||||
"interrupted-import-counts": "__swimlanes__ lôn nofio, __lists__ rhestr, __cards__ cerdyn, __checklists__ rhestr wirio, __comments__ sylw, __attachments__ ymlyniad",
|
||||
"interrupted-import-no-board": "Ni chrëwyd bwrdd",
|
||||
"interrupted-import-keep": "Cadw",
|
||||
"interrupted-import-discard": "Taflwch",
|
||||
"interrupted-import-keep-confirm": "Keep this partial board as it is? Nothing is removed, and the import is no longer listed here.",
|
||||
"interrupted-import-discard-confirm": "Discard this import? The board it created and everything on it are removed permanently.",
|
||||
"interrupted-import-keep-confirm": "Cadw'r bwrdd rhannol hwn fel y mae? Ni chaiff dim ei dynnu, ac ni restrir y mewnforio yma mwyach.",
|
||||
"interrupted-import-discard-confirm": "Gwaredu'r mewnforio hwn? Caiff y bwrdd a greodd a phopeth arno eu tynnu'n barhaol.",
|
||||
"interrupted-import-refresh": "Adnewyddu",
|
||||
"interrupted-import-empty": "No interrupted board imports are waiting for review.",
|
||||
"interrupted-import-truncated": "Only the oldest 50 are shown. Keep or discard some and refresh to see more.",
|
||||
"interrupted-import-unavailable": "Could not load the interrupted board imports. Refresh to try again.",
|
||||
"interrupted-import-missing": "This import is no longer recorded.",
|
||||
"interrupted-import-not-interrupted": "This import is not waiting for a decision any more.",
|
||||
"interrupted-import-foreign-board": "The board with this id was not created by this import, so it was not touched.",
|
||||
"interrupted-import-empty": "Nid oes mewnforion byrddau a dorrwyd ar draws yn aros am adolygiad.",
|
||||
"interrupted-import-truncated": "Dim ond yr 50 hynaf a ddangosir. Cadwch neu gwaredwch rai ac adnewyddwch i weld rhagor.",
|
||||
"interrupted-import-unavailable": "Nid oedd modd llwytho'r mewnforion byrddau a dorrwyd ar draws. Adnewyddwch i geisio eto.",
|
||||
"interrupted-import-missing": "Nid yw'r mewnforio hwn wedi'i gofnodi mwyach.",
|
||||
"interrupted-import-not-interrupted": "Nid yw'r mewnforio hwn yn aros am benderfyniad mwyach.",
|
||||
"interrupted-import-foreign-board": "Ni chrëwyd y bwrdd gyda'r id hwn gan y mewnforio hwn, felly ni chyffyrddwyd ag ef.",
|
||||
"interrupted-import-scrum-busy": "Mae ei gam Scrum yn dal i gael ei ysgrifennu neu ei adfer. Rhowch gynnig arall arni mewn ychydig funudau.",
|
||||
"interrupted-import-failed": "Methu cwblhau'r weithred hon. Adnewyddwch a rhowch gynnig arall arni.",
|
||||
"saml-login-not-started": "Ni ddechreuwyd y mewngofnodi SAML hwn yn y tab porwr hwn. Mewngofnodwch eto.",
|
||||
@@ -4446,7 +4461,7 @@
|
||||
"header-login": "Mewngofnodi drwy benawdau HTTP",
|
||||
"login-setting-clear-secret": "Dileu’r gwerth sydd wedi’i gadw yn y panel gweinyddol",
|
||||
"login-setting-after-restart": "Daw i rym ar ôl ailgychwyn WeKan",
|
||||
"login-setting-env-only": "Only the server environment sets these. They are shown here read-only.",
|
||||
"login-setting-env-only": "Dim ond amgylchedd y gweinydd sy'n gosod y rhain. Fe'u dangosir yma i'w darllen yn unig.",
|
||||
"email-to-card": "Ebost i gerdyn",
|
||||
"email-to-card-description": "Mae ebost a anfonir i gyfeiriad eich bwrdd yn troi'n gerdyn ar y bwrdd hwnnw.",
|
||||
"email-to-card-admin-enabled": "Derbyn ebost i gardiau",
|
||||
|
||||
+215
-200
@@ -227,20 +227,20 @@
|
||||
"attachment-soft-delete-pop": "Mae'r ymlyniad wedi'i dynnu o'r cerdyn hwn ac mae ei ffeil yn cael ei chadw. Gall unrhyw un sy'n gallu golygu'r cerdyn ei adfer o hanes y cerdyn.",
|
||||
"attachmentDeletePopup-title": "Dileu Atodiad?",
|
||||
"attachments": "Ymlyniadau",
|
||||
"attach-card": "Attach card",
|
||||
"attachCardPopup-title": "Attach card",
|
||||
"attach-card-hint": "Paste a card link (Copy card link on the other card), or type a card title on this board",
|
||||
"attached-card-unavailable": "An attached card you cannot see",
|
||||
"detach-card": "Remove attached card",
|
||||
"attach-card-self": "A card cannot be attached to itself",
|
||||
"attach-card-limit": "This card already has as many attached cards as it can have",
|
||||
"attach-card-not-found": "No card you can see was found for that link",
|
||||
"attach-card": "Atodi cerdyn",
|
||||
"attachCardPopup-title": "Atodi cerdyn",
|
||||
"attach-card-hint": "Gludwch ddolen cerdyn (Copïo dolen y cerdyn ar y cerdyn arall), neu teipiwch deitl cerdyn ar y bwrdd hwn",
|
||||
"attached-card-unavailable": "Cerdyn atodedig na allwch ei weld",
|
||||
"detach-card": "Tynnu'r cerdyn atodedig",
|
||||
"attach-card-self": "Ni ellir atodi cerdyn iddo'i hun",
|
||||
"attach-card-limit": "Mae gan y cerdyn hwn eisoes gymaint o gardiau atodedig ag y gall eu cael",
|
||||
"attach-card-not-found": "Ni chafwyd hyd i gerdyn y gallwch ei weld ar gyfer y ddolen honno",
|
||||
"auto-watch": "Gwylio byrddau yn awtomatig pan fyddant yn cael eu creu",
|
||||
"avatar-too-big": "Mae'r avatar yn rhy fawr (__size__ max)",
|
||||
"back": "Yn ol",
|
||||
"board-change-color": "Newid lliw",
|
||||
"board-announcement": "Board announcement",
|
||||
"board-announcement-enabled": "Show it to everyone on this board",
|
||||
"board-announcement": "Cyhoeddiad y bwrdd",
|
||||
"board-announcement-enabled": "Ei ddangos i bawb ar y bwrdd hwn",
|
||||
"board-change-background-image": "Newid Delwedd Gefndir",
|
||||
"board-background-image-url": "URL Delwedd Gefndir",
|
||||
"add-background-image": "Ychwanegu Delwedd Gefndir",
|
||||
@@ -284,7 +284,7 @@
|
||||
"zoom-level": "Lefel Chwyddo",
|
||||
"enter-zoom-level": "Rhowch lefel chwyddo (50-300%):",
|
||||
"board-view-cal": "Calendr",
|
||||
"board-view-calendar-mode": "Calendar Mode",
|
||||
"board-view-calendar-mode": "Modd calendr",
|
||||
"board-view-multiboard-cal": "Calendr (Pob Bwrdd)",
|
||||
"board-view-swimlanes": "Lonydd nofio",
|
||||
"board-view-collapse": "Cwymp",
|
||||
@@ -686,32 +686,32 @@
|
||||
"chooseBoardSourcePopup-title": "Bwrdd mewnforio",
|
||||
"createLabelPopup-title": "Creu Label",
|
||||
"createCustomField": "Creu Maes",
|
||||
"custom-field-links": "Link fields to another card",
|
||||
"custom-field-links-hint": "Custom fields with the same name and type on both cards are linked: when a value changes on one card, it is copied to the other card. Fields that only one card has are left alone.",
|
||||
"custom-field-links-none": "This card's custom fields are not linked to any card.",
|
||||
"custom-field-link-unavailable": "A linked card you cannot see",
|
||||
"custom-field-link-fields": "Linked fields:",
|
||||
"custom-field-link-no-fields": "No fields with the same name and type yet",
|
||||
"custom-field-link-inactive": "Paused: the person who made this link can no longer edit both cards, or a card is archived.",
|
||||
"custom-field-unlink": "Unlink",
|
||||
"custom-field-link-target": "Card link (Copy card link), or search this board's cards",
|
||||
"custom-field-link-direction": "Direction",
|
||||
"custom-field-link-both": "Both ways: changes on either card go to the other",
|
||||
"custom-field-link-send": "One way: changes on this card go to the main card",
|
||||
"custom-field-link-sends": "Changes on this card go to that card",
|
||||
"custom-field-link-receives": "Changes on that card come to this card",
|
||||
"custom-field-link-add": "Link fields",
|
||||
"field-link-self": "A card cannot be linked to itself.",
|
||||
"field-link-limit": "This card, or that card, already has the most linked cards it can have.",
|
||||
"field-link-archived": "An archived card cannot be linked.",
|
||||
"field-link-invalid": "That is not a card link.",
|
||||
"field-link-not-found": "Card not found.",
|
||||
"field-link-not-allowed": "You need to be able to edit both cards.",
|
||||
"cardCustomFieldLinksPopup-title": "Linked custom fields",
|
||||
"custom-field-links": "Cysylltu meysydd â cherdyn arall",
|
||||
"custom-field-links-hint": "Mae meysydd personol sydd â'r un enw a math ar y ddau gerdyn wedi'u cysylltu: pan fydd gwerth yn newid ar un cerdyn, caiff ei gopïo i'r cerdyn arall. Gadewir llonydd i feysydd sydd gan un cerdyn yn unig.",
|
||||
"custom-field-links-none": "Nid yw meysydd personol y cerdyn hwn wedi'u cysylltu ag unrhyw gerdyn.",
|
||||
"custom-field-link-unavailable": "Cerdyn cysylltiedig na allwch ei weld",
|
||||
"custom-field-link-fields": "Meysydd cysylltiedig:",
|
||||
"custom-field-link-no-fields": "Dim meysydd â'r un enw a math eto",
|
||||
"custom-field-link-inactive": "Wedi'i oedi: ni all y person a wnaeth y cysylltiad hwn olygu'r ddau gerdyn mwyach, neu mae cerdyn wedi'i archifo.",
|
||||
"custom-field-unlink": "Datgysylltu",
|
||||
"custom-field-link-target": "Dolen cerdyn (Copïo dolen y cerdyn), neu chwiliwch gardiau'r bwrdd hwn",
|
||||
"custom-field-link-direction": "Cyfeiriad",
|
||||
"custom-field-link-both": "Y ddwy ffordd: mae newidiadau ar y naill gerdyn neu'r llall yn mynd i'r llall",
|
||||
"custom-field-link-send": "Un ffordd: mae newidiadau ar y cerdyn hwn yn mynd i'r prif gerdyn",
|
||||
"custom-field-link-sends": "Mae newidiadau ar y cerdyn hwn yn mynd i'r cerdyn hwnnw",
|
||||
"custom-field-link-receives": "Mae newidiadau ar y cerdyn hwnnw yn dod i'r cerdyn hwn",
|
||||
"custom-field-link-add": "Cysylltu meysydd",
|
||||
"field-link-self": "Ni ellir cysylltu cerdyn ag ef ei hun.",
|
||||
"field-link-limit": "Mae gan y cerdyn hwn, neu'r cerdyn hwnnw, eisoes y nifer mwyaf o gardiau cysylltiedig y gall eu cael.",
|
||||
"field-link-archived": "Ni ellir cysylltu cerdyn sydd wedi'i archifo.",
|
||||
"field-link-invalid": "Nid dolen cerdyn yw honno.",
|
||||
"field-link-not-found": "Heb ddod o hyd i'r cerdyn.",
|
||||
"field-link-not-allowed": "Mae angen i chi allu golygu'r ddau gerdyn.",
|
||||
"cardCustomFieldLinksPopup-title": "Meysydd personol cysylltiedig",
|
||||
"createCustomFieldPopup-title": "Creu Maes",
|
||||
"current": "presennol",
|
||||
"custom-color": "Lliw personol",
|
||||
"cards-use-list-color": "Cards without a colour of their own use this colour",
|
||||
"cards-use-list-color": "Mae cardiau heb liw eu hunain yn defnyddio'r lliw hwn",
|
||||
"custom-field-delete-pop": "Does dim dadwneud. Bydd hyn yn dileu'r maes arfer hwn o bob cerdyn ac yn dinistrio ei hanes.",
|
||||
"custom-field-checkbox": "Blwch ticio",
|
||||
"custom-field-currency": "Arian cyfred",
|
||||
@@ -909,20 +909,20 @@
|
||||
"import-board-instruction-zenkit": "Gludwch JSON allforio rhestr ZenKit ({ \"title\", \"stages\":[...], \"items\":[...] }). Daw'r camau'n rhestrau a'r eitemau'n gardiau.",
|
||||
"import-board-instruction-markdown": "Gludwch restr dasgau markdown (y confensiwn y mae offer markdown-kanban fel Obsidian Kanban yn ei ddefnyddio): penawdau \"## Enw'r rhestr\", gyda eitemau \"- [ ]\"/\"- [x]\" oddi tanynt. Mae penawdau'n dod yn rhestrau ac eitemau'n dod yn gardiau; mae rhestr bwled blaen heb flychau ticio hefyd yn cael ei mewnforio, fel cardiau agored.",
|
||||
"import-board-instruction-leo": "Gludwch amlinelliad Leo (ffeil .leo). Mae nodau'r lefel uchaf yn troi'n restrau, mae eu plant yn troi'n gardiau gyda chorff y nod fel disgrifiad, ac mae nodau dyfnach yn troi'n restrau gwirio. Mewnforir nodau wedi'u marcio fel rhai wedi gorffen.",
|
||||
"import-board-instruction-opml": "Paste an OPML outline, as Workflowy, Dynalist, OmniOutliner, Logseq and other outliners export it. Top-level items become lists, their children become cards with the note as the description, and deeper items become checklists. Completed items import as done.",
|
||||
"import-board-instruction-orgmode": "Paste an Org mode file, as Emacs, Orgzly or Beorg write it. Top-level headings become lists and second-level headings become cards. TODO and DONE, priorities, tags, SCHEDULED, DEADLINE and CLOSED are kept, and checkboxes and deeper headings become checklists.",
|
||||
"import-board-instruction-opml": "Gludwch amlinelliad OPML, fel y mae Workflowy, Dynalist, OmniOutliner, Logseq ac amlinellwyr eraill yn ei allforio. Daw eitemau'r lefel uchaf yn rhestrau, daw eu plant yn gardiau gyda'r nodyn yn ddisgrifiad, a daw eitemau dyfnach yn rhestrau gwirio. Caiff eitemau wedi'u cwblhau eu mewnforio fel rhai wedi'u gwneud.",
|
||||
"import-board-instruction-orgmode": "Gludwch ffeil Org mode, fel y mae Emacs, Orgzly neu Beorg yn ei hysgrifennu. Daw penawdau'r lefel gyntaf yn rhestrau a daw penawdau'r ail lefel yn gardiau. Cedwir TODO a DONE, blaenoriaethau, tagiau, SCHEDULED, DEADLINE a CLOSED, a daw blychau ticio a phenawdau dyfnach yn rhestrau gwirio.",
|
||||
"import-board-instruction-todotxt": "Gludwch ffeil todo.txt: un dasg fesul llinell, fel y mae ap todo.txt ac apiau cydnaws yn ei hysgrifennu. Mae tasgau gorffenedig (\"x\") yn mynd i Wedi gorffen, mae +project a @context yn troi'n labeli, mae (A) yn troi'n label blaenoriaeth, ac mae due: a t: yn troi'n ddyddiadau cau a dechrau.",
|
||||
"import-board-instruction-taskwarrior": "Gludwch allbwn task export: arae JSON neu un dasg fesul llinell. Mae tasgau gorffenedig yn mynd i Done, daw project a priority yn labeli, annotations yn sylwadau, a depends yn ddibyniaethau ar dasgau sy’n eu rhwystro.",
|
||||
"import-board-instruction-focalboard": "Dadsipiwch y ffeil .boardarchive a gludwch gynnwys board.jsonl y bwrdd, gyda llinell y fersiwn neu hebddi. Daw priodwedd grwpio golwg y bwrdd yn restrau, priodweddau dewis eraill yn labeli, blociau testun a blychau ticio yn ddisgrifiad a rhestr wirio, a chedwir y sylwadau. Ni fewnforir delweddau nac atodiadau.",
|
||||
"import-board-instruction-todoist": "In Todoist, open the project menu and choose Export as a template, then paste the CSV file. Sections become lists, tasks become cards, sub-tasks become a checklist, notes become comments, and @labels and priorities p1 to p3 become labels.",
|
||||
"import-board-instruction-planner": "In Microsoft Planner, open the plan's menu and choose Export plan to Excel, then choose that .xlsx file. Buckets become lists and tasks become cards, with their assignees, dates, labels, checklist and description; Progress, Priority and Completed By become custom fields.",
|
||||
"import-board-instruction-meistertask": "In MeisterTask, click the project name, choose Export project and export the CSV, then paste it. Sections become lists and tasks become cards, with their notes, due date, assignee and tags; completed tasks keep their completion date. A CSV written for MeisterTask's own import works too.",
|
||||
"import-board-instruction-obsidian": "In Obsidian, open the Kanban board's file as Markdown (or open the .md file in a text editor), then paste all of it. Lanes become lists with their card limits, cards keep their tags, dates, priority, fields and checklist, and the Archive becomes archived cards.",
|
||||
"import-board-instruction-linear": "In Linear, export the issues as CSV (Settings, Import / Export, Export data, or a view's export), then paste the CSV. Statuses become lists, teams become swimlanes, and issues become cards with their assignee, labels, dates, parent issue, priority, estimate, project and cycle.",
|
||||
"import-board-instruction-ticktick": "In TickTick, open Settings, Account, Backup & Import and generate a backup, then paste the CSV file. Each TickTick list becomes a swimlane and its columns become lists; tasks keep their tags, dates, checklist, priority and subtasks.",
|
||||
"import-board-instruction-clickup": "In ClickUp, export the tasks as CSV (Settings, Imports / Exports, Export Items, or a List or Table view's export with all columns), then paste the CSV. Statuses become lists, ClickUp lists become swimlanes, and tasks become cards with their subtasks, assignees, tags, dates, priority and time.",
|
||||
"import-board-instruction-nullboard": "In Nullboard, open the board, choose Export this board... from the menu, open the .nbx file in a text editor and paste its contents. Lists become lists and notes become cards: a note's first line is the card title and the rest its description. A raw note gets the label raw. A file with several boards imports its first board.",
|
||||
"import-board-instruction-kanri": "In Kanri, open Import & Export and choose Export individual board (or Export all data), then paste the .json file. Columns become lists and cards become cards, with their description, due date, tags and colors; a card's tasks become a checklist. From an all-data export the first board is imported.",
|
||||
"import-board-instruction-meistertask": "Yn MeisterTask, cliciwch enw'r prosiect, dewiswch Export project ac allforiwch y CSV, yna gludwch ef. Daw adrannau yn rhestrau a thasgau yn gardiau, gyda'u nodiadau, dyddiad cwblhau, aseinai a thagiau; mae tasgau wedi'u cwblhau yn cadw eu dyddiad cwblhau. Mae CSV a ysgrifennwyd ar gyfer mewnforio MeisterTask ei hun yn gweithio hefyd.",
|
||||
"import-board-instruction-obsidian": "Yn Obsidian, agorwch ffeil y bwrdd Kanban fel Markdown (neu agorwch y ffeil .md mewn golygydd testun), yna gludwch y cyfan. Daw lonydd yn rhestrau gyda'u terfynau cardiau, mae cardiau'n cadw eu tagiau, dyddiadau, blaenoriaeth, meysydd a rhestr wirio, a daw'r Archif yn gardiau wedi'u harchifo.",
|
||||
"import-board-instruction-linear": "Yn Linear, allforiwch y materion fel CSV (Settings, Import / Export, Export data, neu allforio golwg), yna gludwch y CSV. Daw statwsau yn rhestrau, timau yn lonydd nofio, a materion yn gardiau gyda'u haseinai, labeli, dyddiadau, mater rhiant, blaenoriaeth, amcangyfrif, prosiect a chylch.",
|
||||
"import-board-instruction-ticktick": "Yn TickTick, agorwch Settings, Account, Backup & Import a chreu copi wrth gefn, yna gludwch y ffeil CSV. Daw pob rhestr TickTick yn lôn nofio a daw ei cholofnau yn rhestrau; mae tasgau'n cadw eu tagiau, dyddiadau, rhestr wirio, blaenoriaeth ac is-dasgau.",
|
||||
"import-board-instruction-clickup": "Yn ClickUp, allforiwch y tasgau fel CSV (Settings, Imports / Exports, Export Items, neu allforio golwg List neu Table gyda phob colofn), yna gludwch y CSV. Daw statwsau yn rhestrau, rhestrau ClickUp yn lonydd nofio, a thasgau yn gardiau gyda'u his-dasgau, aseineion, tagiau, dyddiadau, blaenoriaeth ac amser.",
|
||||
"import-board-instruction-nullboard": "Yn Nullboard, agorwch y bwrdd, dewiswch Export this board... o'r ddewislen, agorwch y ffeil .nbx mewn golygydd testun a gludwch ei chynnwys. Daw rhestrau yn rhestrau a nodiadau yn gardiau: llinell gyntaf nodyn yw teitl y cerdyn a'r gweddill yw ei ddisgrifiad. Mae nodyn crai yn cael y label raw. O ffeil â sawl bwrdd, mewnforir y bwrdd cyntaf.",
|
||||
"import-board-instruction-kanri": "Yn Kanri, agorwch Import & Export a dewiswch Export individual board (neu Export all data), yna gludwch y ffeil .json. Daw colofnau yn rhestrau a chardiau yn gardiau, gyda'u disgrifiad, dyddiad cwblhau, tagiau a lliwiau; daw tasgau cerdyn yn rhestr wirio. O allforio'r holl ddata, mewnforir y bwrdd cyntaf.",
|
||||
"import-board-instruction-planka": "Teipiwch gyfeiriad eich Planka (er enghraifft https://planka.example.com), eich enw defnyddiwr a'ch cyfrinair Planka, yna pwyswch Rhestru. Fe'u defnyddir dim ond i nôl y bwrdd ac ni chânt eu cadw. Dewiswch fwrdd a phwyswch Nôl: mae WeKan yn ei ddarllen o API Planka, gyda'i restrau, cardiau, disgrifiadau, dyddiadau dyledus, labeli, aelodau, rhestrau tasgau a sylwadau, yna parhewch â'r mewnforio fel arfer. Caiff cardiau yn archif Planka eu mewnforio wedi'u harchifo; ni chaiff atodiadau a chardiau yn y sbwriel eu mewnforio.",
|
||||
"import-board-instruction-mstodo": "Yn Microsoft Graph Explorer (developer.microsoft.com/graph/graph-explorer), mewngofnodwch, cydsyniwch i'r caniatâd Tasks.Read a chopïwch y tocyn o'r tab Access token. Gludwch ef yma a chliciwch Rhestru; defnyddir y tocyn ar gyfer y nôl hwn yn unig ac ni chaiff ei gadw. Dewiswch bob rhestr neu un rhestr a chliciwch Nôl. Daw pob rhestr To Do yn rhestr a phob tasg yn gerdyn, gyda'i nodiadau, dyddiadau dyledus a dechrau, camau fel rhestr wirio, categorïau fel labeli, a phwysigrwydd uchel a statws fel labeli; caiff tasgau wedi'u cwblhau eu harchifo. Ni chaiff nodiadau atgoffa ac ailadroddiadau eu mewnforio.",
|
||||
"import-board-instruction-kanbanflow": "Yn KanbanFlow, agorwch ddewislen y bwrdd, dewiswch Settings → API & Webhooks a chopïwch docyn API y bwrdd. Gludwch y tocyn yma, cliciwch Rhestru, dewiswch y bwrdd a chliciwch Nôl; defnyddir y tocyn ar gyfer y nôl hwnnw yn unig ac ni chaiff ei gadw. Daw colofnau'n rhestrau ac mae lonydd nofio'n aros yn lonydd nofio; mae tasgau'n cadw eu disgrifiad, lliw, labeli, dyddiad dyledus, is-dasgau fel rhestr wirio, amser a dreuliwyd, amcangyfrif, pobl a sylwadau.",
|
||||
@@ -941,19 +941,19 @@
|
||||
"api-import-fetched": "Wedi'i nôl. Gwiriwch yr aelodau isod a mewnforiwch.",
|
||||
"api-import-failed": "Methodd y nôl",
|
||||
"api-import-nothing": "Does dim i'w fewnforio gyda'r cyfrif hwn.",
|
||||
"import-board-instruction-pivotal": "In Pivotal Tracker, open the project's MORE tab and choose Export CSV (or select stories and choose Bulk Actions, CSV), then paste the CSV. Each story state becomes a list and stories become cards, with their type and labels, owners, requester, dates, comments, tasks and blockers; Estimate becomes Story points and iterations become sprints.",
|
||||
"import-board-instruction-tasksorg": "In Tasks.org, open Settings, Backups and choose Export tasks, then paste the contents of that .json file. Lists become lists and tasks become cards, with their notes, tags, dates, comments and subtasks; completed tasks keep their completion date, and priority becomes a custom field.",
|
||||
"import-board-instruction-monday": "In monday.com, open the board menu, choose More actions and Export board to Excel (with updates and subitems if you like), then choose that .xlsx file. The Status column becomes lists and groups become swimlanes; people, dates, timeline and tags keep their places, other columns become custom fields and updates become comments.",
|
||||
"import-board-instruction-superproductivity": "In Super Productivity, open Settings, then Sync & Backup, choose Export Data and paste the contents of the sp-backup .json file. Each project becomes a swimlane and tasks become cards in To Do, In Progress, Backlog or Done, with their notes, tags, due date, time spent and sub-tasks; archived tasks become archived cards.",
|
||||
"import-board-instruction-taiga": "In Taiga, open Admin > Project > Export and download the project dump (unpack a .json.gz file first), then paste its JSON. User story statuses become lists, user stories become cards with their tasks as subtasks, epics and issues get swimlanes of their own, and sprints, tags, comments and custom attributes come along. Attachments are not imported.",
|
||||
"import-board-instruction-vikunja": "In Vikunja, open Settings, choose Data Export and download the .zip file it sends you, then choose that file, or paste the data.json inside it. Kanban buckets become lists and tasks become cards, with their labels, assignees, dates, description, task lists as checklists and comments; priority, percent done and done become custom fields. Attachments are not imported.",
|
||||
"import-board-instruction-quire": "In Quire, open the project menu and choose Export CSV, then paste the CSV. Statuses become lists and tasks become cards, with their subtasks as child cards, assignees, tags as labels, start, due and completed dates, description and priority as a custom field. Comments and attachments are not in Quire's CSV.",
|
||||
"import-board-instruction-wrike": "In Wrike, export a project or folder to Excel, or fill in Wrike's Excel import template, then choose that .xlsx file. Folder and project rows become swimlanes, Status becomes lists and tasks become cards, with their Key, parent task, assignees, start and end dates, dependencies and description; Priority, Duration and other columns become custom fields.",
|
||||
"import-board-instruction-teamwork": "In Teamwork.com, fill in the Excel task import template (Tasklist, Task, Description, Assign to, Start date, Due date, Priority, Estimated time, Tags, Status) and save it as .xlsx, then choose that file. Task lists become lists and tasks become cards; a task starting with -, # or > becomes a subtask of the task above it (--, ## or >> one level deeper). Assignees, dates and tags keep their places; Priority, Estimated time and Complete become custom fields.",
|
||||
"import-board-instruction-businessmap": "In Businessmap (formerly Kanbanize), open Advanced Search, choose Configure results to pick the columns, download the Excel file, then choose that .xlsx file. Columns are read by their English names (Title, Column, Lane, Owner, Deadline ...): if your account uses another language, rename the header row to English first. Column becomes lists and Lane swimlanes; owners, tags, color, dates, comments and parent links keep their places, and Priority, Size, Type and other columns become custom fields.",
|
||||
"import-board-instruction-redmine": "In Redmine, open the project's Issues, choose Also available in: CSV at the bottom of the list, pick the columns (All columns, with Description) and export, then paste the CSV. Redmine names the columns in your language, so set your language to English in My account before exporting. Each status becomes a list and issues become cards, with the tracker as a label, assignee, author, dates, parent task and related issues; priority, category, target version, estimated time, % Done and custom fields become custom fields.",
|
||||
"import-board-instruction-notion": "In Notion, open the database as a full page, choose ••• and Export, pick Markdown & CSV, and choose the .zip file it downloads, or one database CSV from it, or paste that CSV. A column named Status becomes the lists (without one, the first column that looks like a select does, and the import report says which); rows become cards with their title, tags as labels, people as members, dates, and the page text as the description; other columns become custom fields. Relations, images and attachments are not imported.",
|
||||
"import-board-instruction-plane": "In Plane, open Workspace Settings, choose Exports, export the projects as JSON, CSV or Excel and download the .zip file, then choose that file, or one file from inside it, or paste the JSON or CSV. States become lists, projects become swimlanes, and issues become cards with their identifier, parent, assignees, labels, dates, links and comments; priority, estimate, cycle and module become custom fields. Plane's export has no descriptions or attachments, so they are not imported.",
|
||||
"import-board-instruction-pivotal": "Yn Pivotal Tracker, agorwch dab MORE y prosiect a dewiswch Export CSV (neu dewiswch straeon a dewis Bulk Actions, CSV), yna gludwch y CSV. Daw pob cyflwr stori yn rhestr a straeon yn gardiau, gyda'u math a'u labeli, perchnogion, ceisydd, dyddiadau, sylwadau, tasgau a rhwystrau; daw Estimate yn Story points a daw iteriadau yn sbrintiau.",
|
||||
"import-board-instruction-tasksorg": "Yn Tasks.org, agorwch Settings, Backups a dewiswch Export tasks, yna gludwch gynnwys y ffeil .json honno. Daw rhestrau yn rhestrau a thasgau yn gardiau, gyda'u nodiadau, tagiau, dyddiadau, sylwadau ac is-dasgau; mae tasgau wedi'u cwblhau yn cadw eu dyddiad cwblhau, a daw blaenoriaeth yn faes personol.",
|
||||
"import-board-instruction-monday": "Yn monday.com, agorwch ddewislen y bwrdd, dewiswch More actions ac Export board to Excel (gyda diweddariadau ac is-eitemau os mynnwch), yna dewiswch y ffeil .xlsx honno. Daw'r golofn Status yn rhestrau a grwpiau yn lonydd nofio; mae pobl, dyddiadau, llinell amser a thagiau yn aros yn eu lle, daw colofnau eraill yn feysydd personol a diweddariadau yn sylwadau.",
|
||||
"import-board-instruction-superproductivity": "Yn Super Productivity, agorwch Settings, yna Sync & Backup, dewiswch Export Data a gludwch gynnwys y ffeil .json sp-backup. Daw pob prosiect yn lôn nofio a thasgau yn gardiau yn To Do, In Progress, Backlog neu Done, gyda'u nodiadau, tagiau, dyddiad cwblhau, amser a dreuliwyd ac is-dasgau; daw tasgau wedi'u harchifo yn gardiau wedi'u harchifo.",
|
||||
"import-board-instruction-taiga": "Yn Taiga, agorwch Admin > Project > Export a lawrlwythwch ddymp y prosiect (dadbaciwch ffeil .json.gz yn gyntaf), yna gludwch ei JSON. Daw statwsau straeon defnyddiwr yn rhestrau, daw straeon defnyddiwr yn gardiau gyda'u tasgau fel is-dasgau, mae epigau a materion yn cael lonydd nofio eu hunain, a daw sbrintiau, tagiau, sylwadau a phriodoleddau personol hefyd. Nid yw ymlyniadau'n cael eu mewnforio.",
|
||||
"import-board-instruction-vikunja": "Yn Vikunja, agorwch Settings, dewiswch Data Export a lawrlwythwch y ffeil .zip y mae'n ei hanfon atoch, yna dewiswch y ffeil honno, neu gludwch y data.json sydd y tu mewn iddi. Daw bwcedi Kanban yn rhestrau a thasgau yn gardiau, gyda'u labeli, aseineion, dyddiadau, disgrifiad, rhestrau tasgau fel rhestrau gwirio a sylwadau; daw blaenoriaeth, canran wedi'i gwneud a wedi'i gwneud yn feysydd personol. Nid yw ymlyniadau'n cael eu mewnforio.",
|
||||
"import-board-instruction-quire": "Yn Quire, agorwch ddewislen y prosiect a dewiswch Export CSV, yna gludwch y CSV. Daw statwsau yn rhestrau a thasgau yn gardiau, gyda'u his-dasgau fel cardiau plentyn, aseineion, tagiau fel labeli, dyddiadau dechrau, cwblhau a gorffen, disgrifiad a blaenoriaeth fel maes personol. Nid yw sylwadau ac ymlyniadau yn CSV Quire.",
|
||||
"import-board-instruction-wrike": "Yn Wrike, allforiwch brosiect neu ffolder i Excel, neu llenwch dempled mewnforio Excel Wrike, yna dewiswch y ffeil .xlsx honno. Daw rhesi ffolder a phrosiect yn lonydd nofio, daw Status yn rhestrau a thasgau yn gardiau, gyda'u Key, tasg rhiant, aseineion, dyddiadau dechrau a gorffen, dibyniaethau a disgrifiad; daw Priority, Duration a cholofnau eraill yn feysydd personol.",
|
||||
"import-board-instruction-teamwork": "Yn Teamwork.com, llenwch dempled Excel mewnforio tasgau (Tasklist, Task, Description, Assign to, Start date, Due date, Priority, Estimated time, Tags, Status) a'i gadw fel .xlsx, yna dewiswch y ffeil honno. Daw rhestrau tasgau yn rhestrau a thasgau yn gardiau; daw tasg sy'n dechrau gyda -, # neu > yn is-dasg i'r dasg uwch ei phen (--, ## neu >> un lefel yn ddyfnach). Mae aseineion, dyddiadau a thagiau yn aros yn eu lle; daw Priority, Estimated time a Complete yn feysydd personol.",
|
||||
"import-board-instruction-businessmap": "Yn Businessmap (Kanbanize gynt), agorwch Advanced Search, dewiswch Configure results i ddewis y colofnau, lawrlwythwch y ffeil Excel, yna dewiswch y ffeil .xlsx honno. Darllenir colofnau yn ôl eu henwau Saesneg (Title, Column, Lane, Owner, Deadline ...): os yw'ch cyfrif yn defnyddio iaith arall, ailenwch y rhes pennawd i Saesneg yn gyntaf. Daw Column yn rhestrau a Lane yn lonydd nofio; mae perchnogion, tagiau, lliw, dyddiadau, sylwadau a dolenni rhiant yn aros yn eu lle, a daw Priority, Size, Type a cholofnau eraill yn feysydd personol.",
|
||||
"import-board-instruction-redmine": "Yn Redmine, agorwch Issues y prosiect, dewiswch Also available in: CSV ar waelod y rhestr, dewiswch y colofnau (All columns, gyda Description) ac allforiwch, yna gludwch y CSV. Mae Redmine yn enwi'r colofnau yn eich iaith chi, felly gosodwch eich iaith i Saesneg yn My account cyn allforio. Daw pob statws yn rhestr a materion yn gardiau, gyda'r tracker fel label, aseinai, awdur, dyddiadau, tasg rhiant a materion cysylltiedig; daw blaenoriaeth, categori, fersiwn targed, amser amcangyfrifedig, % Done a meysydd personol yn feysydd personol.",
|
||||
"import-board-instruction-notion": "Yn Notion, agorwch y gronfa ddata fel tudalen lawn, dewiswch ••• ac Export, dewiswch Markdown & CSV, a dewiswch y ffeil .zip y mae'n ei lawrlwytho, neu un CSV cronfa ddata ohoni, neu gludwch y CSV hwnnw. Daw colofn o'r enw Status yn rhestrau (hebddi, y golofn gyntaf sy'n edrych fel dewis sy'n gwneud hynny, ac mae'r adroddiad mewnforio yn dweud pa un); daw rhesi yn gardiau gyda'u teitl, tagiau fel labeli, pobl fel aelodau, dyddiadau, a thestun y dudalen fel disgrifiad; daw colofnau eraill yn feysydd personol. Nid yw perthnasoedd, delweddau ac ymlyniadau'n cael eu mewnforio.",
|
||||
"import-board-instruction-plane": "Yn Plane, agorwch Workspace Settings, dewiswch Exports, allforiwch y prosiectau fel JSON, CSV neu Excel a lawrlwythwch y ffeil .zip, yna dewiswch y ffeil honno, neu un ffeil o'i mewn, neu gludwch y JSON neu'r CSV. Daw cyflyrau yn rhestrau, prosiectau yn lonydd nofio, a materion yn gardiau gyda'u dynodwr, rhiant, aseineion, labeli, dyddiadau, dolenni a sylwadau; daw blaenoriaeth, amcangyfrif, cylch a modiwl yn feysydd personol. Nid oes disgrifiadau nac ymlyniadau yn allforio Plane, felly nid ydynt yn cael eu mewnforio.",
|
||||
"import-board-instruction-trello": "Yn eich bwrdd Trello, ewch i 'Dewislen', yna 'Mwy', 'Argraffu ac Allforio', 'Allforio JSON', a chopïwch y testun canlyniadol.",
|
||||
"import-board-instruction-csv": "Gludwch eich Gwerthoedd Wedi'u Gwahanu gan Goma (CSV)/Gwerthoedd Wedi'u Gwahanu â Thabiau (TSV) i mewn .",
|
||||
"import-board-instruction-jira": "Gludwch y JSON o chwiliad materion API REST Jira Cloud (GET /rest/api/3/search/jql), neu wrthrych cyfatebol { \"issues\": [...] }. Daw statwsau Jira'n rhestrau a materion yn gardiau. Yn ddewisol, cynhwyswch arae \"automationRules\" i fewnforio rheolau.",
|
||||
@@ -962,33 +962,33 @@
|
||||
"import-board-instruction-wekan": "Yn eich bwrdd, ewch i 'Dewislen', yna 'Bwrdd Allforio', a chopïwch y testun yn y ffeil wedi'i lawrlwytho.",
|
||||
"import-board-instruction-about-errors": "Os cewch wallau wrth fewnforio bwrdd, weithiau mae mewnforio yn dal i weithio, ac mae bwrdd ar dudalen All Boards.",
|
||||
"import-without-mapping-members": "Mewnforio heb fapio aelodau (mapio'n ddiweddarach)",
|
||||
"import-members-mode-heading": "People in the file:",
|
||||
"import-members-mode-map": "Choose an existing user for each of them",
|
||||
"import-members-mode-placeholder": "Keep them as placeholder users with their own names, to map to real users later",
|
||||
"import-members-mode-me": "Make them all me",
|
||||
"csv-mapping-title": "Which column is which?",
|
||||
"csv-mapping-description": "Choose the column of the file that holds each card field. The choices were made from the column names; change any that are wrong, and choose (not in the file) for a field the file does not have.",
|
||||
"csv-mapping-not-in-file": "(not in the file)",
|
||||
"csv-mapping-column-number": "Column __number__",
|
||||
"csv-mapping-list-missing": "The file has no column for the list. Choose one above, or type the name of the one list every card goes into.",
|
||||
"csv-mapping-list-name": "List for every card",
|
||||
"csv-mapping-custom-fields": "Other columns to import as text custom fields:",
|
||||
"csv-mapping-boards": "Each sheet becomes its own board:",
|
||||
"csv-mapping-skipped-sheets": "Sheets that are not imported:",
|
||||
"csv-mapping-title-required": "Choose the column that holds the card titles.",
|
||||
"csv-mapping-list-required": "Type the name of the list every card goes into, or choose the column that holds the list.",
|
||||
"invalid-import-mapping": "The column mapping does not match the file.",
|
||||
"import-many-boards": "Import many boards",
|
||||
"import-many-boards-hint": "Choose several export files of this tool, or one .zip that holds them: each file becomes its own board, imported without member mapping. A .zip that is itself one export (Vikunja, Notion, Plane, or WeKan with attachments) is one board. When files are chosen here, Import imports them instead of the field above.",
|
||||
"import-one-board-per-project": "One board per project",
|
||||
"import-one-board-per-project-hint": "Each swimlane the import would make (a project, folder or list of the tool) becomes its own board. Parent cards and dependencies between those boards are listed in the import report.",
|
||||
"import-many-progress": "Importing __done__ of __total__: __name__",
|
||||
"import-many-results-heading": "Imported files",
|
||||
"import-many-imported": "imported",
|
||||
"export-all-boards": "Export all boards",
|
||||
"export-selected-boards": "Export selected boards",
|
||||
"export-all-boards-hint": "Every board you can export, in one download: Excel as one workbook with a sheet per board named after it, every other format as a .zip with a file per board.",
|
||||
"exportAllBoardsPopup-title": "Export all boards",
|
||||
"import-members-mode-heading": "Pobl yn y ffeil:",
|
||||
"import-members-mode-map": "Dewiswch ddefnyddiwr sy'n bodoli ar gyfer pob un ohonynt",
|
||||
"import-members-mode-placeholder": "Eu cadw fel defnyddwyr dalfan gyda'u henwau eu hunain, i'w paru â defnyddwyr go iawn yn nes ymlaen",
|
||||
"import-members-mode-me": "Gwneud pob un ohonynt yn fi",
|
||||
"csv-mapping-title": "Pa golofn yw pa un?",
|
||||
"csv-mapping-description": "Dewiswch golofn y ffeil sy'n dal pob maes cerdyn. Gwnaed y dewisiadau o enwau'r colofnau; newidiwch unrhyw rai sy'n anghywir, a dewiswch (nid yn y ffeil) ar gyfer maes nad oes gan y ffeil.",
|
||||
"csv-mapping-not-in-file": "(nid yn y ffeil)",
|
||||
"csv-mapping-column-number": "Colofn __number__",
|
||||
"csv-mapping-list-missing": "Nid oes gan y ffeil golofn ar gyfer y rhestr. Dewiswch un uchod, neu teipiwch enw'r un rhestr y mae pob cerdyn yn mynd iddi.",
|
||||
"csv-mapping-list-name": "Rhestr ar gyfer pob cerdyn",
|
||||
"csv-mapping-custom-fields": "Colofnau eraill i'w mewnforio fel meysydd personol testun:",
|
||||
"csv-mapping-boards": "Daw pob dalen yn fwrdd ei hun:",
|
||||
"csv-mapping-skipped-sheets": "Dalennau nad ydynt yn cael eu mewnforio:",
|
||||
"csv-mapping-title-required": "Dewiswch y golofn sy'n dal teitlau'r cardiau.",
|
||||
"csv-mapping-list-required": "Teipiwch enw'r rhestr y mae pob cerdyn yn mynd iddi, neu dewiswch y golofn sy'n dal y rhestr.",
|
||||
"invalid-import-mapping": "Nid yw paru'r colofnau yn cyfateb i'r ffeil.",
|
||||
"import-many-boards": "Mewnforio llawer o fyrddau",
|
||||
"import-many-boards-hint": "Dewiswch sawl ffeil allforio o'r offeryn hwn, neu un .zip sy'n eu dal: daw pob ffeil yn fwrdd ei hun, wedi'i fewnforio heb baru aelodau. Mae .zip sydd ei hun yn un allforio (Vikunja, Notion, Plane, neu WeKan gydag ymlyniadau) yn un bwrdd. Pan ddewisir ffeiliau yma, mae Mewnforio yn eu mewnforio nhw yn lle'r maes uchod.",
|
||||
"import-one-board-per-project": "Un bwrdd i bob prosiect",
|
||||
"import-one-board-per-project-hint": "Daw pob lôn nofio y byddai'r mewnforio yn ei gwneud (prosiect, ffolder neu restr o'r offeryn) yn fwrdd ei hun. Rhestrir cardiau rhiant a dibyniaethau rhwng y byrddau hynny yn yr adroddiad mewnforio.",
|
||||
"import-many-progress": "Yn mewnforio __done__ o __total__: __name__",
|
||||
"import-many-results-heading": "Ffeiliau wedi'u mewnforio",
|
||||
"import-many-imported": "wedi'i fewnforio",
|
||||
"export-all-boards": "Allforio pob bwrdd",
|
||||
"export-selected-boards": "Allforio'r byrddau a ddewiswyd",
|
||||
"export-all-boards-hint": "Pob bwrdd y gallwch ei allforio, mewn un lawrlwythiad: Excel fel un llyfr gwaith gyda dalen i bob bwrdd wedi'i henwi ar ei ôl, pob fformat arall fel .zip gyda ffeil i bob bwrdd.",
|
||||
"exportAllBoardsPopup-title": "Allforio pob bwrdd",
|
||||
"import-json-placeholder": "Gludwch eich data JSON dilys yma",
|
||||
"import-csv-placeholder": "Gludwch eich data CSV/TSV dilys yma",
|
||||
"import-json-file": "Neu dewiswch ffeil JSON Trello",
|
||||
@@ -1026,17 +1026,17 @@
|
||||
"trello-import-results": "Canlyniadau mewnforio",
|
||||
"trello-api-credentials-required": "Rhowch yr allwedd a'r tocyn API Trello os gwelwch yn dda.",
|
||||
"trello-api-credentials-saved": "Mae'r allwedd a'r tocyn API Trello wedi'u cadw. Gallwch restru gweithleoedd heb eu hail-nodi.",
|
||||
"exportToTrelloPopup-title": "Export to Trello",
|
||||
"trello-export-desc": "Create a copy of this board in Trello through Trello's API, with your Trello API key and token. Swimlanes become labels named \"Swimlane: …\", which WeKan's Trello import turns back into swimlanes.",
|
||||
"trello-export-include-attachments": "Include attachments",
|
||||
"trello-export-start": "Export to Trello",
|
||||
"trello-export-open-board": "Open the board in Trello",
|
||||
"trello-export-warnings": "Not everything could be exported",
|
||||
"sandstorm-trello-access-needed": "A Sandstorm grain has no Internet access of its own. Allow this grain to reach Trello first.",
|
||||
"sandstorm-allow-trello-access": "Allow access to Trello",
|
||||
"sandstorm-trello-access-granted": "This grain may reach Trello.",
|
||||
"sandstorm-forget-access": "Forget",
|
||||
"sandstorm-trello-save-label": "Trello, for importing and exporting boards",
|
||||
"exportToTrelloPopup-title": "Allforio i Trello",
|
||||
"trello-export-desc": "Creu copi o'r bwrdd hwn yn Trello drwy API Trello, gyda'ch allwedd API a'ch tocyn Trello. Daw lonydd nofio yn labeli o'r enw \"Swimlane: …\", y mae mewnforio Trello WeKan yn eu troi'n ôl yn lonydd nofio.",
|
||||
"trello-export-include-attachments": "Cynnwys ymlyniadau",
|
||||
"trello-export-start": "Allforio i Trello",
|
||||
"trello-export-open-board": "Agor y bwrdd yn Trello",
|
||||
"trello-export-warnings": "Nid oedd modd allforio popeth",
|
||||
"sandstorm-trello-access-needed": "Nid oes gan grain Sandstorm fynediad i'r Rhyngrwyd ei hun. Caniatewch i'r grain hwn gyrraedd Trello yn gyntaf.",
|
||||
"sandstorm-allow-trello-access": "Caniatáu mynediad i Trello",
|
||||
"sandstorm-trello-access-granted": "Caiff y grain hwn gyrraedd Trello.",
|
||||
"sandstorm-forget-access": "Anghofio",
|
||||
"sandstorm-trello-save-label": "Trello, ar gyfer mewnforio ac allforio byrddau",
|
||||
"trello-select-boards": "Dewiswch o leiaf un bwrdd i'w fewnforio os gwelwch yn dda.",
|
||||
"select-all": "Dewis popeth",
|
||||
"select-none": "Dewis dim",
|
||||
@@ -1318,9 +1318,9 @@
|
||||
"external-link-pattern-description": "Yn cysylltu'n awtomatig docynnau ar ffurf \"#1234\" a geir yng nghorff y cerdyn â thraciwr materion allanol. Gadewch faes yn wag i'w analluogi.",
|
||||
"external-link-pattern-prefix": "Rhagddodiad y tocyn",
|
||||
"external-link-pattern-url": "Templed URL (defnyddiwch {number})",
|
||||
"external-link-rules": "More link rules",
|
||||
"external-link-rules-description": "One rule per line: the text to find, \" = \", and the address. Use {number} for digits and {identifier} for a word, in both, for example [{identifier}:{number}] = https://tracker.example.com/{identifier}/{number}.",
|
||||
"external-link-identifier-aliases": "Abbreviations for {identifier}, for example TK=Task, IN=Incident",
|
||||
"external-link-rules": "Rhagor o reolau dolenni",
|
||||
"external-link-rules-description": "Un rheol i bob llinell: y testun i'w ganfod, \" = \", a'r cyfeiriad. Defnyddiwch {number} ar gyfer digidau a {identifier} ar gyfer gair, yn y ddau, er enghraifft [{identifier}:{number}] = https://tracker.example.com/{identifier}/{number}.",
|
||||
"external-link-identifier-aliases": "Byrfoddau ar gyfer {identifier}, er enghraifft TK=Task, IN=Incident",
|
||||
"username": "Enw defnyddiwr",
|
||||
"import-usernames": "Mewnforio Enwau Defnyddwyr",
|
||||
"view-it": "Edrychwch arno",
|
||||
@@ -1345,13 +1345,13 @@
|
||||
"attachment-transfer-limits-title": "Terfynau Maint Ffeil Ymlyniadau ac API",
|
||||
"attachment-limits": "Terfynau",
|
||||
"attachment-retention": "Cadw",
|
||||
"attachment-retention-days": "Delete attachments after (days)",
|
||||
"attachment-retention-days-help": "Like Delete on the card: the card history can restore them. Empty or 0: never.",
|
||||
"attachment-retention-mode": "Count the days from",
|
||||
"attachment-retention-purge": "Remove deleted attachments for good after (days)",
|
||||
"attachment-retention-purge-help": "After this they can no longer be restored. Empty or 0: never.",
|
||||
"attachment-retention-dry-run": "Dry run: only count, in the server log, change nothing",
|
||||
"attachment-retention-max-per-run": "At most this many attachments per night",
|
||||
"attachment-retention-days": "Dileu ymlyniadau ar ôl (diwrnodau)",
|
||||
"attachment-retention-days-help": "Fel Dileu ar y cerdyn: gall hanes y cerdyn eu hadfer. Gwag neu 0: byth.",
|
||||
"attachment-retention-mode": "Cyfrif y diwrnodau o",
|
||||
"attachment-retention-purge": "Tynnu ymlyniadau wedi'u dileu am byth ar ôl (diwrnodau)",
|
||||
"attachment-retention-purge-help": "Ar ôl hyn ni ellir eu hadfer mwyach. Gwag neu 0: byth.",
|
||||
"attachment-retention-dry-run": "Rhediad prawf: cyfrif yn unig, yn log y gweinydd, heb newid dim",
|
||||
"attachment-retention-max-per-run": "Dim mwy na hyn o ymlyniadau bob nos",
|
||||
"attachment-transfer-limits-description": "Ffurfweddu terfynau maint ffeil uchaf ar gyfer llwytho i fyny a lawrlwytho. Mae gan fyffro trosglwyddo API gapiau diogelwch ar wahân ar ochr y gweinydd.",
|
||||
"attachment-transfer-limits-saved": "Cadwyd terfynau trosglwyddo ymlyniadau.",
|
||||
"attachment-transfer-limits-save-failed": "Methwyd â chadw terfynau trosglwyddo ymlyniadau",
|
||||
@@ -1401,51 +1401,51 @@
|
||||
"error-notAuthorized": "Nid oes gennych awdurdod i weld y dudalen hon.",
|
||||
"webhook-title": "Enw Bachyn",
|
||||
"webhook-token": "Tocyn (Dewisol ar gyfer Dilysu)",
|
||||
"webhook-payload-heading": "Webhook data",
|
||||
"webhook-payload-description": "What outgoing webhooks (Rocket.Chat, Slack, ...) send. Default keeps the inherited setting; with nothing set, webhooks send what they always have.",
|
||||
"webhook-payload-text": "Send the message text",
|
||||
"webhook-payload-fields": "Send these fields",
|
||||
"webhook-payload-field-standard": "Standard fields (WEBHOOKS_ATTRIBUTES)",
|
||||
"webhook-payload-field-ids": "IDs",
|
||||
"webhook-payload-field-names": "Names and titles",
|
||||
"webhook-payload-field-links": "Links to the card and board",
|
||||
"webhook-payload-field-actor": "Who did it",
|
||||
"webhook-payload-field-people": "The member or assignee it is about",
|
||||
"webhook-payload-field-details": "Details: comment, description, values and dates",
|
||||
"webhook-hide-identity": "Leave my name out of outgoing webhooks",
|
||||
"webhook-someone": "A member",
|
||||
"webhook-payload-heading": "Data'r bachyn gwe",
|
||||
"webhook-payload-description": "Yr hyn y mae bachau gwe sy'n mynd allan (Rocket.Chat, Slack, ...) yn ei anfon. Mae Rhagosodiad yn cadw'r gosodiad a etifeddwyd; heb ddim wedi'i osod, mae bachau gwe yn anfon yr hyn y maent wedi'i anfon erioed.",
|
||||
"webhook-payload-text": "Anfon testun y neges",
|
||||
"webhook-payload-fields": "Anfon y meysydd hyn",
|
||||
"webhook-payload-field-standard": "Meysydd safonol (WEBHOOKS_ATTRIBUTES)",
|
||||
"webhook-payload-field-ids": "Dynodwyr (ID)",
|
||||
"webhook-payload-field-names": "Enwau a theitlau",
|
||||
"webhook-payload-field-links": "Dolenni i'r cerdyn a'r bwrdd",
|
||||
"webhook-payload-field-actor": "Pwy a'i gwnaeth",
|
||||
"webhook-payload-field-people": "Yr aelod neu'r aseinai y mae'n ymwneud ag ef",
|
||||
"webhook-payload-field-details": "Manylion: sylw, disgrifiad, gwerthoedd a dyddiadau",
|
||||
"webhook-hide-identity": "Gadael fy enw allan o fachau gwe sy'n mynd allan",
|
||||
"webhook-someone": "Aelod",
|
||||
"notification-delivery-layout": "Gosodiad",
|
||||
"notification-delivery-layout-classic": "One sentence (classic)",
|
||||
"notification-delivery-layout-clear": "Clearly arranged: board, who, card in bold",
|
||||
"notification-delivery-parts": "Show",
|
||||
"notification-delivery-part-board": "Board name and link",
|
||||
"notification-delivery-part-actor": "Who did it",
|
||||
"notification-delivery-part-card": "Card title and link",
|
||||
"notification-delivery-layout-classic": "Un frawddeg (clasurol)",
|
||||
"notification-delivery-layout-clear": "Wedi'i drefnu'n glir: bwrdd, pwy, cerdyn mewn print trwm",
|
||||
"notification-delivery-parts": "Dangos",
|
||||
"notification-delivery-part-board": "Enw a dolen y bwrdd",
|
||||
"notification-delivery-part-actor": "Pwy a'i gwnaeth",
|
||||
"notification-delivery-part-card": "Teitl a dolen y cerdyn",
|
||||
"notification-delivery-part-list": "Rhestr",
|
||||
"notification-delivery-part-swimlane": "Lôn nofio",
|
||||
"notification-delivery-part-details": "Comment or changed value",
|
||||
"notification-delivery-part-dates": "Dates",
|
||||
"notification-delivery-part-link": "Link on its own line",
|
||||
"notification-delivery-grouping": "Grouping",
|
||||
"notification-delivery-grouping-all": "Everything due in one message",
|
||||
"notification-delivery-grouping-board": "One per board",
|
||||
"notification-delivery-grouping-card": "One per card",
|
||||
"notification-delivery-grouping-none": "Each on its own",
|
||||
"notification-delivery-schedule": "When to send",
|
||||
"notification-delivery-schedule-immediate": "Immediately",
|
||||
"notification-delivery-schedule-interval": "Every few minutes or hours",
|
||||
"notification-delivery-schedule-daily": "Once a day",
|
||||
"notification-delivery-interval": "Every",
|
||||
"notification-delivery-minutes": "__count__ minutes",
|
||||
"notification-delivery-part-details": "Sylw neu werth wedi'i newid",
|
||||
"notification-delivery-part-dates": "Dyddiadau",
|
||||
"notification-delivery-part-link": "Dolen ar ei llinell ei hun",
|
||||
"notification-delivery-grouping": "Grwpio",
|
||||
"notification-delivery-grouping-all": "Popeth sy'n ddyledus mewn un neges",
|
||||
"notification-delivery-grouping-board": "Un i bob bwrdd",
|
||||
"notification-delivery-grouping-card": "Un i bob cerdyn",
|
||||
"notification-delivery-grouping-none": "Pob un ar ei ben ei hun",
|
||||
"notification-delivery-schedule": "Pryd i anfon",
|
||||
"notification-delivery-schedule-immediate": "Ar unwaith",
|
||||
"notification-delivery-schedule-interval": "Bob ychydig funudau neu oriau",
|
||||
"notification-delivery-schedule-daily": "Unwaith y dydd",
|
||||
"notification-delivery-interval": "Bob",
|
||||
"notification-delivery-minutes": "__count__ munud",
|
||||
"notification-delivery-daily-time": "Am",
|
||||
"notification-delivery-quiet": "Quiet hours (wait until they end)",
|
||||
"notification-delivery-quiet": "Oriau tawel (aros nes iddynt ddod i ben)",
|
||||
"notification-delivery-quiet-from": "O",
|
||||
"notification-delivery-quiet-to": "Tan",
|
||||
"notification-delivery-quiet-clear": "No quiet hours",
|
||||
"notification-delivery-timezone": "Time zone:",
|
||||
"notification-delivery-invalid": "The setting could not be saved.",
|
||||
"notification-group-card": "__count__ notifications on card __card__",
|
||||
"notification-group-board": "__count__ notifications on board __board__",
|
||||
"notification-delivery-quiet-clear": "Dim oriau tawel",
|
||||
"notification-delivery-timezone": "Cylchfa amser:",
|
||||
"notification-delivery-invalid": "Nid oedd modd cadw'r gosodiad.",
|
||||
"notification-group-card": "__count__ hysbysiad ar y cerdyn __card__",
|
||||
"notification-group-board": "__count__ hysbysiad ar y bwrdd __board__",
|
||||
"outgoing-webhooks": "Llyfrau Gwe Allan",
|
||||
"bidirectional-webhooks": "Bachogau Gwe Dwyffordd",
|
||||
"outgoingWebhooksPopup-title": "Llyfrau Gwe Allan",
|
||||
@@ -1500,7 +1500,7 @@
|
||||
"showLabel-field-on-card": "Dangos label maes ar minicard",
|
||||
"showSum-field-on-list": "Dangos swm y meysydd ar frig y rhestr",
|
||||
"admin-only-field": "Gweladwy i weinyddwyr y bwrdd yn unig",
|
||||
"read-only-field": "Read-only: every member sees the value, only board admins change it",
|
||||
"read-only-field": "Darllen yn unig: mae pob aelod yn gweld y gwerth, dim ond gweinyddwyr y bwrdd sy'n ei newid",
|
||||
"yes": "Oes",
|
||||
"no": "Nac ydw",
|
||||
"accounts": "Cyfrifon",
|
||||
@@ -1692,11 +1692,11 @@
|
||||
"r-format-auto": "Canfod yn awtomatig",
|
||||
"r-import-workflow-note": "Mewnforio llif gwaith gweledol a allforiwyd o n8n neu Node-RED. Mapir nodau sbardun/gweithred a adnabyddir i reolau WeKan; adroddir am nodau heb eu mapio.",
|
||||
"r-import-unmapped": "Methwyd â mapio __count__ llinell(au)",
|
||||
"r-wrike-workflow": "Wrike workflow",
|
||||
"r-wrike-workflow-note": "A Wrike workflow is a list of statuses, each in the Active, Completed, Deferred or Cancelled group. Export writes this board's lists as one, named after the board, in the JSON Wrike's API returns for workflows: create that workflow in Wrike before importing the board's Wrike Excel export, so its statuses are applied. Import reads that JSON pasted above, from WeKan or from Wrike's GET /workflows, into the chosen board: a status without a list becomes a list, and a rule marks a card complete when it is moved into a Completed or Cancelled status and incomplete in an Active or Deferred one. Wrike's own automation rules cannot be exported from Wrike.",
|
||||
"r-export-wrike-workflow": "Export Wrike workflow",
|
||||
"r-import-wrike-workflow": "Import Wrike workflow",
|
||||
"r-import-wrike-workflow-done": "Wrike workflow \"__name__\": __lists__ list(s) and __rules__ rule(s) added, __skipped__ part(s) not read",
|
||||
"r-wrike-workflow": "Llif gwaith Wrike",
|
||||
"r-wrike-workflow-note": "Rhestr o statwsau yw llif gwaith Wrike, pob un yn y grŵp Active, Completed, Deferred neu Cancelled. Mae allforio yn ysgrifennu rhestrau'r bwrdd hwn fel un, wedi'i enwi ar ôl y bwrdd, yn y JSON y mae API Wrike yn ei ddychwelyd ar gyfer llifoedd gwaith: crëwch y llif gwaith hwnnw yn Wrike cyn mewnforio allforio Excel Wrike y bwrdd, fel bod ei statwsau'n cael eu cymhwyso. Mae mewnforio yn darllen y JSON hwnnw a gludwyd uchod, o WeKan neu o GET /workflows Wrike, i'r bwrdd a ddewiswyd: daw statws heb restr yn rhestr, ac mae rheol yn nodi cerdyn yn gyflawn pan gaiff ei symud i statws Completed neu Cancelled ac yn anghyflawn mewn un Active neu Deferred. Ni ellir allforio rheolau awtomeiddio Wrike ei hun o Wrike.",
|
||||
"r-export-wrike-workflow": "Allforio llif gwaith Wrike",
|
||||
"r-import-wrike-workflow": "Mewnforio llif gwaith Wrike",
|
||||
"r-import-wrike-workflow-done": "Llif gwaith Wrike \"__name__\": ychwanegwyd __lists__ rhestr a __rules__ rheol, __skipped__ rhan heb eu darllen",
|
||||
"r-set-scheduled-triggers": "Sbardunau wedi'u hamserlennu",
|
||||
"r-set-button-triggers": "Botymau",
|
||||
"r-when-scheduled": "Ar amserlen",
|
||||
@@ -1752,8 +1752,8 @@
|
||||
"set-filter": "Gosod Hidlydd",
|
||||
"r-moved-to": "Wedi symud i",
|
||||
"r-moved-from": "Wedi symud o",
|
||||
"r-moved-forward": "moved forward to a later list",
|
||||
"r-moved-back": "moved back to an earlier list",
|
||||
"r-moved-forward": "wedi'i symud ymlaen i restr ddiweddarach",
|
||||
"r-moved-back": "wedi'i symud yn ôl i restr gynharach",
|
||||
"r-archived": "Wedi symud i'r Archif",
|
||||
"r-unarchived": "Wedi'i adfer o'r Archif",
|
||||
"r-commented": "wedi cael sylw",
|
||||
@@ -1801,8 +1801,8 @@
|
||||
"r-add-actinguser-member": "Ychwanegu'r defnyddiwr a sbardunodd y rheol hon fel aelod",
|
||||
"r-remove-all": "Tynnwch yr holl aelodau o'r cerdyn",
|
||||
"r-assignee": "aseinai",
|
||||
"r-add-actinguser-assignee": "Add the user who triggered this rule as an assignee",
|
||||
"r-remove-all-assignees": "Remove all assignees from the card",
|
||||
"r-add-actinguser-assignee": "Ychwanegu'r defnyddiwr a sbardunodd y rheol hon fel aseinai",
|
||||
"r-remove-all-assignees": "Tynnu pob aseinai o'r cerdyn",
|
||||
"r-remove-all-labels": "Tynnu'r holl labeli oddi ar y cerdyn",
|
||||
"r-set-color": "Gosod lliw i",
|
||||
"r-checklist": "rhestr wirio",
|
||||
@@ -1876,10 +1876,10 @@
|
||||
"ldap-test-connection": "Profi Cysylltiad LDAP",
|
||||
"ldap-test-connection-success": "Cysylltiad llwyddiannus",
|
||||
"ldap-test-connection-error": "Methodd y cysylltiad: %s",
|
||||
"ldap-sync-now": "Sync LDAP users now",
|
||||
"ldap-sync-now-done": "LDAP sync finished",
|
||||
"ldap-sync-now-error": "LDAP sync failed: %s",
|
||||
"ldap-sync-now-nothing": "Nothing to sync: turn on LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS or LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED first.",
|
||||
"ldap-sync-now": "Cysoni defnyddwyr LDAP nawr",
|
||||
"ldap-sync-now-done": "Mae'r cysoni LDAP wedi gorffen",
|
||||
"ldap-sync-now-error": "Methodd y cysoni LDAP: %s",
|
||||
"ldap-sync-now-nothing": "Dim i'w gysoni: trowch LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS neu LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED ymlaen yn gyntaf.",
|
||||
"oauth-providers-title": "Darparwyr mewngofnodi OAuth",
|
||||
"oauth-providers-hint": "Gellir troi pob darparwr ymlaen yma neu gyda’i newidyn amgylchedd OAUTH_*_ENABLED; mae gwerth a osodir yma yn diystyru’r newidyn amgylchedd. Caiff y gyfrinach ei storio ar y gweinydd ac ni chaiff byth ei dangos.",
|
||||
"oauth-provider-google": "Google",
|
||||
@@ -1894,7 +1894,7 @@
|
||||
"oauth-provider-secret-set": "Mae cyfrinach wedi’i gosod",
|
||||
"oauth-providers-login-style": "Arddull mewngofnodi",
|
||||
"oauth-providers-merge-existing-users": "Uno â chyfrif presennol sydd â’r un ebost",
|
||||
"oauth-providers-allowed-email-domains": "Only these email domains may sign in with these providers (comma separated; empty allows all)",
|
||||
"oauth-providers-allowed-email-domains": "Dim ond y parthau e-bost hyn a gaiff fewngofnodi gyda'r darparwyr hyn (wedi'u gwahanu gan goma; mae gwag yn caniatáu pob un)",
|
||||
"oauth-account-conflict": "Mae cyfrif gyda’r ebost hwn yn bodoli eisoes ac ni chafodd ei greu gyda’r dull mewngofnodi hwn",
|
||||
"sign-in-with": "Mewngofnodi gyda %s",
|
||||
"login-origin-mismatch": "Ni ellir cwblhau mewngofnodi drwy wasanaeth arall yn y cyfeiriad hwn. Mae WeKan wedi’i ffurfweddu ar gyfer __expected__, ond agorwyd y dudalen hon yn __actual__. Agorwch WeKan yn __expected__, neu gofynnwch i’r gweinyddwr osod ROOT_URL i __actual__.",
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Methodd y cysylltiad",
|
||||
"cloud-settings-saved": "Cadwyd gosodiadau storfa",
|
||||
"cloud-settings-save-failed": "Methwyd â chadw gosodiadau storfa",
|
||||
"cloud": "Cwmwl",
|
||||
"cloud-settings-desc": "Gyda WeKan yn Weddy, mae'r Cloud Panel yn gwasanaethu llawer o barthau WeKan o un gweinydd, pob un â'i ddata ei hun. Ym Mharth y Cloud Panel, mae'r SuperAdmin yn rheoli'r parthau a CloudFlare.",
|
||||
"cloud-panel-domain": "Parth y Cloud Panel",
|
||||
"cloud-superadmin-username": "Enw defnyddiwr SuperAdmin",
|
||||
"cloud-saved": "Wedi cadw.",
|
||||
"cloud-panel-not-allowed": "Mae'r Cloud Panel ar gyfer y SuperAdmin yn unig, ym Mharth y Cloud Panel.",
|
||||
"cloud-customer-name": "Enw'r cwsmer",
|
||||
"cloud-customer-email": "Cyfeiriad e-bost y cwsmer",
|
||||
"cloud-billing-email": "Cyfeiriad e-bost bilio",
|
||||
"cloud-start-date": "Dyddiad dechrau",
|
||||
"cloud-end-date": "Dyddiad gorffen",
|
||||
"cloudflare-api-key": "Allwedd API CloudFlare",
|
||||
"cloudflare-api-key-desc": "Gydag allwedd API CloudFlare, mae parth y mae ei gylchfa yn eich cyfrif CloudFlare yn cael cofnod DNS dirprwyedig a thystysgrif CloudFlare Origin. Mae parthau eraill yn cael tystysgrif Let's Encrypt. Caiff yr allwedd ei chadw ar y gweinydd ac ni chaiff ei dangos eto.",
|
||||
"cloudflare-api-key-set": "Mae allwedd API CloudFlare wedi'i chadw.",
|
||||
"cloudflare-api-key-not-set": "Nid oes allwedd API CloudFlare wedi'i chadw: mae pob parth yn cael tystysgrif Let's Encrypt.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Symud ymlyniad i Azure Blob Storage",
|
||||
@@ -3276,7 +3291,7 @@
|
||||
"import-report-open-board": "Agor y bwrdd",
|
||||
"import-parts-instruction": "Dim ond y rhannau sydd wedi'u ticio sy'n cael eu mewnforio. Defnyddir yr un dewis wrth allforio.",
|
||||
"import-wekan-file": "Mewnforio o ffeil allforio .json neu .zip:",
|
||||
"import-file-label": "Choose the export file, or paste its text below:",
|
||||
"import-file-label": "Dewiswch y ffeil allforio, neu gludwch ei thestun isod:",
|
||||
"sum-of-number-fields": "Cyfanswm y meysydd rhifol addasedig a ddangosir ar frig y rhestr",
|
||||
"date-range-of-fields": "Ystod dyddiadau meysydd dyddiad personol wedi’u nodi i’w dangos ar frig y rhestr",
|
||||
"more-options": "Rhagor o opsiynau",
|
||||
@@ -3303,9 +3318,9 @@
|
||||
"wip-limit-group-select-swimlane": "Dethol Lôn nofio",
|
||||
"wip-limit-group-apply-swimlane": "Ymgeisiwch Lôn nofio",
|
||||
"subtask-inherit-parent-labels": "Etifeddu labelau'r cerdyn rhiant",
|
||||
"subtask-mark-done": "Mark subtask as done",
|
||||
"subtask-mark-not-done": "Mark subtask as not done",
|
||||
"subtask-done-no-permission": "You cannot change whether this subtask is done",
|
||||
"subtask-mark-done": "Nodi'r is-dasg fel un wedi'i gwneud",
|
||||
"subtask-mark-not-done": "Nodi'r is-dasg fel un heb ei gwneud",
|
||||
"subtask-done-no-permission": "Ni allwch newid a yw'r is-dasg hon wedi'i gwneud",
|
||||
"chart-forecast-none-remaining": "Dim byd ar ôl yn agored i'w ragamcanu - mae pob cerdyn wedi'i orffen.",
|
||||
"chart-forecast-no-velocity": "Mae __remaining__ cerdyn/cardiau yn dal ar agor; dim cwblhau diweddar i ragamcanu dyddiad ohono.",
|
||||
"chart-forecast-projected": "Ar y gyfradd ddiweddar o __average__ cerdyn/cardiau yr wythnos, dylai'r __remaining__ cerdyn/cardiau sy'n dal ar agor gael eu gorffen erbyn __date__.",
|
||||
@@ -4380,52 +4395,52 @@
|
||||
"rule-email-legacy-attempt-exists": "Mae'r ebost hwn eisoes wedi'i anfon, wedi cael ymgais i'w anfon, neu wedi'i waredu.",
|
||||
"rule-email-legacy-not-legacy": "Mae'r ebost hwn eisoes wedi'i ailgysylltu.",
|
||||
"rule-email-legacy-failed": "Methu cwblhau'r weithred hon. Adnewyddwch a rhowch gynnig arall arni.",
|
||||
"stuck-sync-operation-heading": "List Sync operations that cannot be replayed",
|
||||
"stuck-sync-operation-description": "Each of these saved List Sync operations stopped part way and can no longer be finished, because its list was removed, recreated or reconfigured, or the user who started it lost write access. While one is pending, that list cannot Sync. Discard it to let the list Sync again: changes already applied stay, the remaining saved changes are never written, and the next Sync compares the list with its source again.",
|
||||
"stuck-sync-operation-list": "Board / list",
|
||||
"stuck-sync-operation-heading": "Gweithrediadau Cysoni Rhestr na ellir eu hailchwarae",
|
||||
"stuck-sync-operation-description": "Stopiodd pob un o'r gweithrediadau Cysoni Rhestr hyn a gadwyd hanner ffordd ac ni ellir eu gorffen mwyach, oherwydd i'w rhestr gael ei thynnu, ei hail-greu neu ei hailffurfweddu, neu i'r defnyddiwr a'i dechreuodd golli mynediad ysgrifennu. Tra bo un yn aros, ni all y rhestr honno Gysoni. Taflwch ef i adael i'r rhestr Gysoni eto: mae newidiadau a gymhwyswyd eisoes yn aros, nid yw'r newidiadau sy'n weddill byth yn cael eu hysgrifennu, ac mae'r Cysoni nesaf yn cymharu'r rhestr â'i ffynhonnell eto.",
|
||||
"stuck-sync-operation-list": "Bwrdd / rhestr",
|
||||
"stuck-sync-operation-progress": "Cynnydd",
|
||||
"stuck-sync-operation-reason": "Rheswm",
|
||||
"stuck-sync-operation-applied": "__applied__ of __total__ changes applied",
|
||||
"stuck-sync-operation-reason-scope-changed": "The list was removed, recreated or its Sync settings changed",
|
||||
"stuck-sync-operation-reason-access-denied": "The user who started it no longer has write access to the whole list",
|
||||
"stuck-sync-operation-reason-trigger-unknown": "Saved by an earlier WeKan version without what started it",
|
||||
"stuck-sync-operation-reason-intent-missing": "The record of who started it is missing",
|
||||
"stuck-sync-operation-reason-unknown": "Could not be checked now",
|
||||
"stuck-sync-operation-replayable-now": "It can be replayed again now and will finish by itself, so it cannot be discarded.",
|
||||
"stuck-sync-operation-applied": "__applied__ o __total__ newid wedi'u cymhwyso",
|
||||
"stuck-sync-operation-reason-scope-changed": "Cafodd y rhestr ei thynnu, ei hail-greu neu newidiwyd ei gosodiadau Cysoni",
|
||||
"stuck-sync-operation-reason-access-denied": "Nid oes gan y defnyddiwr a'i dechreuodd fynediad ysgrifennu i'r rhestr gyfan mwyach",
|
||||
"stuck-sync-operation-reason-trigger-unknown": "Wedi'i gadw gan fersiwn gynharach o WeKan heb yr hyn a'i dechreuodd",
|
||||
"stuck-sync-operation-reason-intent-missing": "Mae'r cofnod o bwy a'i dechreuodd ar goll",
|
||||
"stuck-sync-operation-reason-unknown": "Nid oedd modd ei wirio nawr",
|
||||
"stuck-sync-operation-replayable-now": "Gellir ei ailchwarae nawr a bydd yn gorffen ar ei ben ei hun, felly ni ellir ei daflu.",
|
||||
"stuck-sync-operation-discard": "Taflwch",
|
||||
"stuck-sync-operation-discard-confirm": "Discard this saved List Sync operation? Changes it already applied stay; the rest are never written. The list can then Sync again.",
|
||||
"stuck-sync-operation-discard-confirm": "Taflu'r gweithrediad Cysoni Rhestr hwn a gadwyd? Mae newidiadau a gymhwysodd eisoes yn aros; nid yw'r gweddill byth yn cael eu hysgrifennu. Yna gall y rhestr Gysoni eto.",
|
||||
"stuck-sync-operation-refresh": "Adnewyddu",
|
||||
"stuck-sync-operation-empty": "No List Sync operations are waiting for review.",
|
||||
"stuck-sync-operation-truncated": "Only the oldest 50 are shown. Discard some and refresh to see more.",
|
||||
"stuck-sync-operation-unavailable": "Could not load the List Sync operations. Refresh to try again.",
|
||||
"stuck-sync-operation-missing": "This operation is no longer pending.",
|
||||
"stuck-sync-operation-not-stuck": "This operation has not been found stuck, so it cannot be discarded.",
|
||||
"stuck-sync-operation-replayable": "This operation can be replayed again now and will finish by itself, so it was not discarded.",
|
||||
"stuck-sync-operation-busy": "This list is syncing right now. Try again in a moment.",
|
||||
"stuck-sync-operation-empty": "Nid oes unrhyw weithrediadau Cysoni Rhestr yn aros am adolygiad.",
|
||||
"stuck-sync-operation-truncated": "Dim ond yr 50 hynaf a ddangosir. Taflwch rai ac adnewyddwch i weld rhagor.",
|
||||
"stuck-sync-operation-unavailable": "Nid oedd modd llwytho'r gweithrediadau Cysoni Rhestr. Adnewyddwch i geisio eto.",
|
||||
"stuck-sync-operation-missing": "Nid yw'r gweithrediad hwn yn aros mwyach.",
|
||||
"stuck-sync-operation-not-stuck": "Ni chafwyd bod y gweithrediad hwn yn sownd, felly ni ellir ei daflu.",
|
||||
"stuck-sync-operation-replayable": "Gellir ailchwarae'r gweithrediad hwn nawr a bydd yn gorffen ar ei ben ei hun, felly ni chafodd ei daflu.",
|
||||
"stuck-sync-operation-busy": "Mae'r rhestr hon yn cysoni ar hyn o bryd. Ceisiwch eto mewn munud.",
|
||||
"stuck-sync-operation-failed": "Methu cwblhau'r weithred hon. Adnewyddwch a rhowch gynnig arall arni.",
|
||||
"interrupted-import-heading": "Board imports that stopped before finishing",
|
||||
"interrupted-import-description": "Each of these board imports stopped part way - the server restarted, or the import failed - and left a partial board. An import cannot be continued, because its source file is not kept. Keep the board as it is, or discard it and import the file again. Discarding removes the board this import created and everything on it, including anything added to it since.",
|
||||
"interrupted-import-heading": "Mewnforion byrddau a stopiodd cyn gorffen",
|
||||
"interrupted-import-description": "Stopiodd pob un o'r mewnforion byrddau hyn hanner ffordd - ailgychwynnodd y gweinydd, neu methodd y mewnforio - a gadael bwrdd rhannol. Ni ellir parhau â mewnforio, oherwydd nid yw ei ffeil ffynhonnell yn cael ei chadw. Cadwch y bwrdd fel y mae, neu taflwch ef a mewnforiwch y ffeil eto. Mae taflu yn tynnu'r bwrdd a greodd y mewnforio hwn a phopeth arno, gan gynnwys unrhyw beth a ychwanegwyd ato ers hynny.",
|
||||
"interrupted-import-board": "Bwrdd",
|
||||
"interrupted-import-progress": "Where it stopped",
|
||||
"interrupted-import-created": "Created so far",
|
||||
"interrupted-import-source": "Imported from __source__",
|
||||
"interrupted-import-state-stopped": "Stopped without finishing, at __stage__",
|
||||
"interrupted-import-state-failed": "Failed with __error__, at __stage__",
|
||||
"interrupted-import-state-discarding": "Being discarded; discard again to finish",
|
||||
"interrupted-import-progress": "Lle y stopiodd",
|
||||
"interrupted-import-created": "Wedi'u creu hyd yma",
|
||||
"interrupted-import-source": "Wedi'i fewnforio o __source__",
|
||||
"interrupted-import-state-stopped": "Stopiodd heb orffen, yn __stage__",
|
||||
"interrupted-import-state-failed": "Methodd gyda __error__, yn __stage__",
|
||||
"interrupted-import-state-discarding": "Yn cael ei daflu; taflwch eto i orffen",
|
||||
"interrupted-import-scrum": "Mae gan ei gam Scrum ei bwynt gwirio ei hun (__state__, cam __next__ o __total__). Os ydych yn cadw’r bwrdd, cwblhewch neu gwaredwch y cam hwnnw drwy adfer mewnforio Scrum y bwrdd.",
|
||||
"interrupted-import-counts": "__swimlanes__ swimlanes, __lists__ lists, __cards__ cards, __checklists__ checklists, __comments__ comments, __attachments__ attachments",
|
||||
"interrupted-import-no-board": "No board was created",
|
||||
"interrupted-import-keep": "Keep",
|
||||
"interrupted-import-counts": "__swimlanes__ lôn nofio, __lists__ rhestr, __cards__ cerdyn, __checklists__ rhestr wirio, __comments__ sylw, __attachments__ ymlyniad",
|
||||
"interrupted-import-no-board": "Ni chrëwyd bwrdd",
|
||||
"interrupted-import-keep": "Cadw",
|
||||
"interrupted-import-discard": "Taflwch",
|
||||
"interrupted-import-keep-confirm": "Keep this partial board as it is? Nothing is removed, and the import is no longer listed here.",
|
||||
"interrupted-import-discard-confirm": "Discard this import? The board it created and everything on it are removed permanently.",
|
||||
"interrupted-import-keep-confirm": "Cadw'r bwrdd rhannol hwn fel y mae? Ni thynnir dim, ac ni fydd y mewnforio'n cael ei restru yma mwyach.",
|
||||
"interrupted-import-discard-confirm": "Taflu'r mewnforio hwn? Caiff y bwrdd a greodd a phopeth arno eu tynnu'n barhaol.",
|
||||
"interrupted-import-refresh": "Adnewyddu",
|
||||
"interrupted-import-empty": "No interrupted board imports are waiting for review.",
|
||||
"interrupted-import-truncated": "Only the oldest 50 are shown. Keep or discard some and refresh to see more.",
|
||||
"interrupted-import-unavailable": "Could not load the interrupted board imports. Refresh to try again.",
|
||||
"interrupted-import-missing": "This import is no longer recorded.",
|
||||
"interrupted-import-not-interrupted": "This import is not waiting for a decision any more.",
|
||||
"interrupted-import-foreign-board": "The board with this id was not created by this import, so it was not touched.",
|
||||
"interrupted-import-empty": "Nid oes unrhyw fewnforion byrddau a dorrwyd ar eu traws yn aros am adolygiad.",
|
||||
"interrupted-import-truncated": "Dim ond yr 50 hynaf a ddangosir. Cadwch neu taflwch rai ac adnewyddwch i weld rhagor.",
|
||||
"interrupted-import-unavailable": "Nid oedd modd llwytho'r mewnforion byrddau a dorrwyd ar eu traws. Adnewyddwch i geisio eto.",
|
||||
"interrupted-import-missing": "Nid yw'r mewnforio hwn wedi'i gofnodi mwyach.",
|
||||
"interrupted-import-not-interrupted": "Nid yw'r mewnforio hwn yn aros am benderfyniad mwyach.",
|
||||
"interrupted-import-foreign-board": "Ni chrëwyd y bwrdd â'r id hwn gan y mewnforio hwn, felly ni chyffyrddwyd ag ef.",
|
||||
"interrupted-import-scrum-busy": "Mae ei gam Scrum yn dal i gael ei ysgrifennu neu ei adfer. Rhowch gynnig arall arni mewn ychydig funudau.",
|
||||
"interrupted-import-failed": "Methu cwblhau'r weithred hon. Adnewyddwch a rhowch gynnig arall arni.",
|
||||
"saml-login-not-started": "Ni ddechreuwyd y mewngofnodi SAML hwn yn y tab porwr hwn. Mewngofnodwch eto.",
|
||||
@@ -4446,7 +4461,7 @@
|
||||
"header-login": "Mewngofnodi drwy benawdau HTTP",
|
||||
"login-setting-clear-secret": "Dileu’r gwerth sydd wedi’i gadw yn y panel gweinyddol",
|
||||
"login-setting-after-restart": "Daw i rym ar ôl ailgychwyn WeKan",
|
||||
"login-setting-env-only": "Only the server environment sets these. They are shown here read-only.",
|
||||
"login-setting-env-only": "Dim ond amgylchedd y gweinydd sy'n gosod y rhain. Fe'u dangosir yma fel darllen yn unig.",
|
||||
"email-to-card": "Ebost i gerdyn",
|
||||
"email-to-card-description": "Mae ebost a anfonir i gyfeiriad eich bwrdd yn troi'n gerdyn ar y bwrdd hwnnw.",
|
||||
"email-to-card-admin-enabled": "Derbyn ebost i gardiau",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Forbindelse kunne ikke oprettes",
|
||||
"cloud-settings-saved": "Lagerindstillingerne blev gemt",
|
||||
"cloud-settings-save-failed": "Kunne ikke gemme lagerindstillinger",
|
||||
"cloud": "Sky",
|
||||
"cloud-settings-desc": "Med WeKan i Weddy betjener Cloud Panel mange WeKan-domæner fra én server, hver med sine egne data. På Cloud Panel-domænet administrerer SuperAdmin domænerne og CloudFlare.",
|
||||
"cloud-panel-domain": "Cloud Panel-domæne",
|
||||
"cloud-superadmin-username": "SuperAdmin-brugernavn",
|
||||
"cloud-saved": "Gemt.",
|
||||
"cloud-panel-not-allowed": "Cloud Panel er kun for SuperAdmin, på Cloud Panel-domænet.",
|
||||
"cloud-customer-name": "Kundens navn",
|
||||
"cloud-customer-email": "Kundens e-mailadresse",
|
||||
"cloud-billing-email": "E-mailadresse til fakturering",
|
||||
"cloud-start-date": "Startdato",
|
||||
"cloud-end-date": "Slutdato",
|
||||
"cloudflare-api-key": "CloudFlare API-nøgle",
|
||||
"cloudflare-api-key-desc": "Med en CloudFlare API-nøgle får et domæne, hvis zone er i din CloudFlare-konto, en proxied DNS-post og et CloudFlare Origin-certifikat. Andre domæner får et Let's Encrypt-certifikat. Nøglen gemmes på serveren og vises aldrig igen.",
|
||||
"cloudflare-api-key-set": "En CloudFlare API-nøgle er gemt.",
|
||||
"cloudflare-api-key-not-set": "Ingen CloudFlare API-nøgle er gemt: alle domæner får et Let's Encrypt-certifikat.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Flyt vedhæftninger til Azure Blob Storage",
|
||||
|
||||
@@ -2965,6 +2965,21 @@
|
||||
"cloud-connection-failed": "Verbindung fehlgeschlagen",
|
||||
"cloud-settings-saved": "Speichereinstellungen gespeichert",
|
||||
"cloud-settings-save-failed": "Speichern der Speichereinstellungen fehlgeschlagen",
|
||||
"cloud": "Wolke",
|
||||
"cloud-settings-desc": "Mit WeKan in Weddy bedient das Cloud Panel viele WeKan-Domains von einem Server aus, jede mit ihren eigenen Daten. Auf der Cloud Panel-Domain verwaltet der SuperAdmin die Domains und CloudFlare.",
|
||||
"cloud-panel-domain": "Cloud Panel-Domain",
|
||||
"cloud-superadmin-username": "SuperAdmin-Benutzername",
|
||||
"cloud-saved": "Gespeichert.",
|
||||
"cloud-panel-not-allowed": "Das Cloud Panel ist nur für den SuperAdmin, auf der Cloud Panel-Domain.",
|
||||
"cloud-customer-name": "Name des Kunden",
|
||||
"cloud-customer-email": "E-Mail-Adresse des Kunden",
|
||||
"cloud-billing-email": "E-Mail-Adresse für die Rechnung",
|
||||
"cloud-start-date": "Startdatum",
|
||||
"cloud-end-date": "Enddatum",
|
||||
"cloudflare-api-key": "CloudFlare API-Schlüssel",
|
||||
"cloudflare-api-key-desc": "Mit einem CloudFlare API-Schlüssel erhält eine Domain, deren Zone in Ihrem CloudFlare-Konto liegt, einen über den Proxy geleiteten DNS-Eintrag und ein CloudFlare Origin-Zertifikat. Andere Domains erhalten ein Let's Encrypt-Zertifikat. Der Schlüssel wird auf dem Server gespeichert und nie wieder angezeigt.",
|
||||
"cloudflare-api-key-set": "Ein CloudFlare API-Schlüssel ist gespeichert.",
|
||||
"cloudflare-api-key-not-set": "Kein CloudFlare API-Schlüssel ist gespeichert: Jede Domain erhält ein Let's Encrypt-Zertifikat.",
|
||||
"move-storage-azure": "Azure Blob",
|
||||
"move-storage-gcs": "Google Cloud Storage",
|
||||
"attachment-move-storage-azure": "Anhang nach Azure Blob Storage verschieben",
|
||||
|
||||
Loaded 100 of 526 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user