From 01e30b9c7ef68e0bdb64f09f76a2c8cf08cd435c Mon Sep 17 00:00:00 2001
From: Johannes Millan
Date: Mon, 20 Apr 2026 12:04:38 +0200
Subject: [PATCH] Feat/to me it looks like there are lots of 60dd04 (#7280)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix(issue): prevent crash from orphan issueProviderId (#7135)
The Jira image-headers effect in task-detail-panel subscribed to
selectIssueProviderById without an error handler, so a task with an
issueProviderId pointing at a deleted provider (e.g. after sync
convergence where taskIdsToUnlink didn't cover all local tasks)
propagated the selector throw to Zone.js as a crash dialog. Wrap the
inner selector observable in catchError that logs and falls back to
of(null); the downstream jiraCfg?.isEnabled guard handles the fallback.
Also drop IssueLog.log(issueProviderKey, issueProvider) from the
throwing variant of the selector: providers may carry credentials
(host, token, apiKey) and IssueLog history is exportable.
* fix(focus-mode): sync tray countdown with in-app timer during breaks
Tray title was rebuilt from a cached currentFocusSessionTime that only
refreshed when CURRENT_TASK_UPDATED fired. addTimeSpent is gated on an
active current task, so during focus-mode breaks or task-less focus
sessions the cache froze while the in-app timer kept ticking.
Add the tick action to taskChangeElectron$ so the cache refreshes every
second whenever the focus timer is running.
Fixes #7278
* fix(ci): restore GitHub Actions SHA pins undone by 0e9218bd68
Commit 0e9218bd68 silently reverted PR #7212 (github-actions-minor group
bump) along with its stated sync/client-id work. This restores the 15
workflow files to their pre-revert state.
Actions restored to newer pinned SHAs:
- actions/upload-artifact v7.0.0 -> v7.0.1
- step-security/harden-runner v2.16.1 -> v2.17.0
- softprops/action-gh-release v2.6.1 -> v3.0.0
- signpath/github-action-submit-signing-request v2.0 -> v2.1
- anthropics/claude-code-action v1.0.89 -> v1.0.93
- docker/build-push-action v7.0.0 -> v7.1.0
- easingthemes/ssh-deploy v5.1.1 -> v6.0.3
* fix: restore i18n, UI, and docs work undone by 0e9218bd68
Commit 0e9218bd68 silently reverted the following work alongside its stated
sync/client-id changes. Files where later master commits (fec7b25f23, etc.)
already re-applied the reverted work are intentionally left untouched.
Restored:
- #7232 docs/long-term-plans/location-based-reminders.md (513 lines)
- #7199 Romanian i18n phase 3 (ro.json + ro-md.json, ~1168 lines)
- #7049 Polish translation improvements
- #7143 planner component styling (4 scss files)
- #7211 add-task-bar preserve time estimate when typing title
- #7208 task.reducer roll-up estimates for added subtasks
- #6767 focus-mode pomodoro reset button
- #7205 plugin-dev github-issue-provider TOKEN description
- #7231 mobile-bottom-nav FAB fix
- a4fe03272 iOS keyboard accessory bar (global-theme + dialog-fullscreen-markdown)
- 309670db3 ShortSyntaxEffects undefined guard
- 667a7986f Dropbox PKCE auth comment/behavior
* fix(electron): restore electron + e2e work undone by 0e9218bd68
Commit 0e9218bd68 silently reverted the following electron/e2e work.
Files already re-fixed by later master commits are left as-is:
- e2e/tests/sync/supersync-archive-conflict.spec.ts (de33234976 + 191d129ff3)
Restored:
- e8a3e156eb fix(electron): Linux autostart IDB backing-store recovery
(re-adds electron/clear-stale-idb-locks.ts + start-app.ts wiring)
- 5ce78a5b63 fix(electron): macOS Cmd+Q / Dock > Quit hang
(setIsQuiting + before-quit delegate to close-handler)
- 46e0fa2d01 fix(sync): FILE_SYNC_LIST_FILES IPC contract
(electronAPI.d.ts + local-file-sync + preload + ipc-events)
- ea1ef16307 fix(android): session-only SAF permissions on OEM devices
- 8865dc0a50 test(e2e): supersync parallel-worker stampede guard
(SUPERSYNC_SERVER_HEALTHY env-var fallback + goto retry loop)
- af7c7687e2 test(e2e): block WS-triggered downloads in non-WS specs
- 265b44db5d test(e2e): premature waitForURL on daily-summary
(this is literally the fix the bad commit's message claimed to add)
Conflict resolutions:
- e2e/utils/supersync-helpers.ts: kept the refined getDoneTaskElement
checks from d64014d086 (later than c558bcab5e) while restoring the
goto retry loop from 8865dc0a50.
- electron/start-app.ts: unioned imports (setIsQuiting +
clearStaleLevelDbLocks from theirs, fs from ours).
* fix(sync): restore sync-core work undone by 0e9218bd68
Commit 0e9218bd68 silently reverted parts of several sync fixes. Most
sync-core reverts have already been re-addressed differently on master
by later commits (1f5184f6e7, 05cd875dd6, 09f5ced2c9, 7df43358ab,
d9158d6adb, 32dbc95ed9, 8c3b08e016, f89fe1ebc3) — those files are
intentionally left untouched to avoid reverting master's newer work.
This PR restores only the pieces that are genuinely still missing:
- e8a3e156eb fix(electron): IDB backing-store autoreload (in-app piece)
operation-log-hydrator.service.ts + .spec.ts (the electron/clear-
stale-idb-locks.ts piece was restored in the prior commit)
Plus three low-risk documentation/cleanup restorations:
- operation-sync.util.ts — add "Nextcloud" to isFileBasedProvider JSDoc
- dropbox.ts — restore improved _getRedirectUri JSDoc (667a7986fb)
- dialog-get-and-enter-auth-code.component.ts — restore isNativePlatform
comment explaining why manual code entry flow is used (667a7986fb)
- file-adapter.interface.ts — remove stray "// NEW" comment (46e0fa2d01)
Intentionally NOT restored (master's newer work covers or supersedes):
- sync-trigger.service.ts / sync.effects.ts (05cd875dd6)
- sync-wrapper.service.ts (1f5184f6e7)
- sync-errors.ts (1f5184f6e7 re-added LegacySyncFormatDetectedError)
- file-based-sync-adapter.service.ts + spec (1f5184f6e7 + d9158d6adb)
- file-based-sync.types.ts (1f5184f6e7)
- operation-log.const.ts (32dbc95ed9 bumped IDB_OPEN_RETRIES to 5)
- dialog-sync-initial-cfg.component.ts (f89fe1ebc3)
---
.../auto-publish-google-play-on-release.yml | 2 +-
.github/workflows/build-android.yml | 2 +-
.../build-create-windows-store-on-release.yml | 2 +-
.github/workflows/build-ios.yml | 2 +-
.../build-publish-to-mac-store-on-release.yml | 2 +-
.../build-update-web-app-on-release.yml | 4 +-
.github/workflows/build.yml | 10 +-
.github/workflows/ci.yml | 2 +-
.github/workflows/claude.yml | 2 +-
.github/workflows/e2e-scheduled.yml | 8 +-
.github/workflows/manual-build.yml | 4 +-
.github/workflows/pr-preview-build.yml | 2 +-
.github/workflows/publish-to-hub-docker.yml | 4 +-
.github/workflows/supersync-docker.yml | 4 +-
.github/workflows/test-mac-dmg-build.yml | 2 +-
.../plugins/SafBridgePlugin.kt | 43 +-
.../location-based-reminders.md | 513 +++++++++++++++
e2e/fixtures/supersync.fixture.ts | 21 +-
e2e/global-setup.ts | 15 +
e2e/pages/supersync.page.ts | 6 +
e2e/tests/sync/supersync.spec.ts | 7 +-
e2e/utils/supersync-helpers.ts | 30 +-
electron/clear-stale-idb-locks.ts | 57 ++
electron/electronAPI.d.ts | 2 +-
electron/local-file-sync.ts | 30 +
electron/main-window.ts | 2 +-
electron/preload.ts | 4 +-
.../shared-with-frontend/ipc-events.const.ts | 2 +-
electron/start-app.ts | 41 +-
.../github-issue-provider/i18n/en.json | 2 +-
.../mobile-bottom-nav.component.html | 3 +-
.../mobile-bottom-nav.component.scss | 2 +-
.../mobile-bottom-nav.component.ts | 39 +-
src/app/core/theme/global-theme.service.ts | 3 +
.../focus-mode-main.component.html | 12 +
.../focus-mode-main.component.ts | 6 +
.../issue/store/issue-provider.selectors.ts | 4 +-
.../planner-calendar-event.component.scss | 3 +-
.../planner-deadline-task.component.scss | 3 +-
.../planner-repeat-projection.component.scss | 3 +-
.../planner-task/planner-task.component.scss | 6 +-
.../add-task-bar-parser.service.spec.ts | 25 +-
.../add-task-bar-parser.service.ts | 5 +-
.../tasks/store/short-syntax.effects.ts | 8 +-
.../tasks/store/task-electron.effects.ts | 4 +
.../features/tasks/store/task.reducer.spec.ts | 23 +
src/app/features/tasks/store/task.reducer.ts | 4 +-
.../task-detail-panel.component.ts | 15 +-
...ialog-get-and-enter-auth-code.component.ts | 5 +-
.../operation-log-hydrator.service.spec.ts | 63 ++
.../operation-log-hydrator.service.ts | 58 +-
.../file-based/dropbox/dropbox.ts | 11 +-
.../local-file/file-adapter.interface.ts | 2 +-
src/app/op-log/sync/operation-sync.util.ts | 2 +-
.../dialog-fullscreen-markdown.component.scss | 7 +
src/app/util/pkce.util.ts | 2 +-
src/assets/i18n/pl.json | 90 +--
src/assets/i18n/ro-md.json | 584 +++++++++---------
src/assets/i18n/ro.json | 584 +++++++++---------
59 files changed, 1719 insertions(+), 684 deletions(-)
create mode 100644 docs/long-term-plans/location-based-reminders.md
create mode 100644 electron/clear-stale-idb-locks.ts
diff --git a/.github/workflows/auto-publish-google-play-on-release.yml b/.github/workflows/auto-publish-google-play-on-release.yml
index 3b3a8deb38..81ede3073c 100644
--- a/.github/workflows/auto-publish-google-play-on-release.yml
+++ b/.github/workflows/auto-publish-google-play-on-release.yml
@@ -13,7 +13,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
+ uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
allowed-endpoints: >
diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml
index 1c9c549bda..686d27b2fe 100644
--- a/.github/workflows/build-android.yml
+++ b/.github/workflows/build-android.yml
@@ -77,7 +77,7 @@ jobs:
# APK is now signed automatically by Gradle using signingConfig
- name: 'Upload APK files'
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sup-android-release
path: android/app/build/outputs/apk/**/*.apk
diff --git a/.github/workflows/build-create-windows-store-on-release.yml b/.github/workflows/build-create-windows-store-on-release.yml
index db7256726b..d458efeb60 100644
--- a/.github/workflows/build-create-windows-store-on-release.yml
+++ b/.github/workflows/build-create-windows-store-on-release.yml
@@ -77,7 +77,7 @@ jobs:
github_token: ${{ secrets.github_token }}
- name: 'Upload Artifact'
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: WinStoreRelease
path: .tmp/app-builds/*.appx
diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml
index 7cb617de24..83f36608a8 100644
--- a/.github/workflows/build-ios.yml
+++ b/.github/workflows/build-ios.yml
@@ -200,7 +200,7 @@ jobs:
run: ls -la "$RUNNER_TEMP/ipa-output"
- name: Upload IPA artifact
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sup-ios-release
path: ${{ runner.temp }}/ipa-output/*.ipa
diff --git a/.github/workflows/build-publish-to-mac-store-on-release.yml b/.github/workflows/build-publish-to-mac-store-on-release.yml
index e103e60518..d307f0a8ea 100644
--- a/.github/workflows/build-publish-to-mac-store-on-release.yml
+++ b/.github/workflows/build-publish-to-mac-store-on-release.yml
@@ -15,7 +15,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
+ uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
allowed-endpoints: >
diff --git a/.github/workflows/build-update-web-app-on-release.yml b/.github/workflows/build-update-web-app-on-release.yml
index 72378cace3..84aff205b1 100644
--- a/.github/workflows/build-update-web-app-on-release.yml
+++ b/.github/workflows/build-update-web-app-on-release.yml
@@ -15,7 +15,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
+ uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
allowed-endpoints: >
@@ -69,7 +69,7 @@ jobs:
run: npm run buildFrontend:prodWeb
- name: Deploy to Web Server
- uses: easingthemes/ssh-deploy@a1aa0b6cf96ce2406eef90faa35007a4a7bf0ac0 # v5.1.1
+ uses: easingthemes/ssh-deploy@2cc5b27bf3029d0455dd5e09fe02633904031447 # v6.0.3
env:
SSH_PRIVATE_KEY: ${{ secrets.WEB_SERVER_SSH_KEY }}
ARGS: '-rltgoDzvO --delete --exclude "news.json"'
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0c39cff29c..aacfec7f93 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -31,7 +31,7 @@ jobs:
fi
} > /tmp/release-body.md
- name: Create Draft Release
- uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
+ uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
tag_name: ${{ github.ref_name }}
draft: true
@@ -102,7 +102,7 @@ jobs:
- name: 'Upload E2E results on failure'
if: ${{ failure() }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: e2eResults
path: .tmp/e2e-test-results/**/*.*
@@ -314,7 +314,7 @@ jobs:
- name: Upload unsigned executables for SignPath
id: upload-unsigned
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: unsigned-windows-executables
path: .tmp/app-builds/*.exe
@@ -322,7 +322,7 @@ jobs:
retention-days: 1
- name: Sign Windows executables with SignPath
- uses: signpath/github-action-submit-signing-request@3f9250c56651ff692d6729a2fbb0603a42d7d322 # v2
+ uses: signpath/github-action-submit-signing-request@bc66d86b015a46e9c6d9700de73143a82f9570ff # v2
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
@@ -474,7 +474,7 @@ jobs:
"
- name: Publish signed Windows binaries to GitHub Release
- uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
+ uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: |
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index eee6282d46..9f74513bc9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -120,7 +120,7 @@ jobs:
run: npm run e2e:ci
- name: 'Upload E2E results on failure'
if: ${{ failure() }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: e2eResults
path: .tmp/e2e-test-results/**/*.*
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
index 63e4653092..b7c75fa373 100644
--- a/.github/workflows/claude.yml
+++ b/.github/workflows/claude.yml
@@ -32,7 +32,7 @@ jobs:
- name: Run Claude Code
id: claude
- uses: anthropics/claude-code-action@6e2bd52842c65e914eba5c8badd17560bd26b5de # v1
+ uses: anthropics/claude-code-action@b47fd721da662d48c5680e154ad16a73ed74d2e0 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/e2e-scheduled.yml b/.github/workflows/e2e-scheduled.yml
index 37dfe370a2..a879d99d77 100644
--- a/.github/workflows/e2e-scheduled.yml
+++ b/.github/workflows/e2e-scheduled.yml
@@ -50,7 +50,7 @@ jobs:
test -f .tmp/angular-dist/browser/assets/bundled-plugins/api-test-plugin/manifest.json || (echo "Plugin assets missing from build" && exit 1)
- name: Upload Build Artifact
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: e2e-build-${{ github.run_id }}
path: .tmp/angular-dist
@@ -86,7 +86,7 @@ jobs:
- name: Upload E2E Results on Failure
if: ${{ failure() }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: e2e-results-regular-${{ github.run_id }}
path: .tmp/e2e-test-results/**/*.*
@@ -143,7 +143,7 @@ jobs:
- name: Upload E2E Results on Failure
if: ${{ failure() }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: e2e-results-webdav-${{ github.run_id }}
path: .tmp/e2e-test-results/**/*.*
@@ -205,7 +205,7 @@ jobs:
- name: Upload E2E Results on Failure
if: ${{ failure() }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: e2e-results-supersync-${{ matrix.shard }}-${{ github.run_id }}
path: .tmp/e2e-test-results/**/*.*
diff --git a/.github/workflows/manual-build.yml b/.github/workflows/manual-build.yml
index ce3353b999..77a20a86db 100644
--- a/.github/workflows/manual-build.yml
+++ b/.github/workflows/manual-build.yml
@@ -62,7 +62,7 @@ jobs:
release: false
- name: 'Upload Artifact'
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: WinBuildStuff
path: .tmp/app-builds/*.exe
@@ -162,7 +162,7 @@ jobs:
# if: always()
# run: ls -la && cat notarization-error.log
- name: 'Upload Artifact'
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dmg
path: .tmp/app-builds/*.dmg
diff --git a/.github/workflows/pr-preview-build.yml b/.github/workflows/pr-preview-build.yml
index 6b22e4736e..938ecc095d 100644
--- a/.github/workflows/pr-preview-build.yml
+++ b/.github/workflows/pr-preview-build.yml
@@ -41,7 +41,7 @@ jobs:
run: npm run buildFrontend:prodWeb
- name: Upload build artifact
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pr-preview-build
path: dist/browser
diff --git a/.github/workflows/publish-to-hub-docker.yml b/.github/workflows/publish-to-hub-docker.yml
index 885086f451..fb73c5afb1 100644
--- a/.github/workflows/publish-to-hub-docker.yml
+++ b/.github/workflows/publish-to-hub-docker.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
+ uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
allowed-endpoints: >
@@ -55,7 +55,7 @@ jobs:
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build and push Docker image
- uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
+ uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
push: true
diff --git a/.github/workflows/supersync-docker.yml b/.github/workflows/supersync-docker.yml
index 8af1cde34b..b80ea5bdf4 100644
--- a/.github/workflows/supersync-docker.yml
+++ b/.github/workflows/supersync-docker.yml
@@ -19,7 +19,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
+ uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
allowed-endpoints: >
@@ -59,7 +59,7 @@ jobs:
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build and push Docker image
- uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
+ uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
file: ./packages/super-sync-server/Dockerfile
diff --git a/.github/workflows/test-mac-dmg-build.yml b/.github/workflows/test-mac-dmg-build.yml
index 7613ccd018..4e827c9da2 100644
--- a/.github/workflows/test-mac-dmg-build.yml
+++ b/.github/workflows/test-mac-dmg-build.yml
@@ -117,7 +117,7 @@ jobs:
fi
- name: Upload DMG artifact
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mac-dmg-build
path: .tmp/app-builds/*.dmg
diff --git a/android/app/src/main/java/com/superproductivity/superproductivity/plugins/SafBridgePlugin.kt b/android/app/src/main/java/com/superproductivity/superproductivity/plugins/SafBridgePlugin.kt
index 060f3743d8..48b72b8fd2 100644
--- a/android/app/src/main/java/com/superproductivity/superproductivity/plugins/SafBridgePlugin.kt
+++ b/android/app/src/main/java/com/superproductivity/superproductivity/plugins/SafBridgePlugin.kt
@@ -2,7 +2,9 @@ package com.superproductivity.superproductivity.plugins
import android.app.Activity
import android.content.Intent
+import android.content.pm.PackageManager
import android.net.Uri
+import android.os.Process
import android.provider.DocumentsContract
import androidx.activity.result.ActivityResult
import androidx.documentfile.provider.DocumentFile
@@ -40,18 +42,26 @@ class SafBridgePlugin : Plugin() {
@ActivityCallback
private fun handleFolderSelectionResult(call: PluginCall, result: ActivityResult) {
if (result.resultCode == Activity.RESULT_OK) {
- val uri = result.data?.data
+ val resultData = result.data
+ val uri = resultData?.data
if (uri != null) {
- // Take persistable permission
+ // Use only the flags that were actually granted by the result intent.
+ // Applying flags not present in the result throws SecurityException on some devices.
+ // resultData is Intent? but uri != null proves resultData is non-null here;
+ // Kotlin cannot infer this transitively, so !! is required and safe.
+ val grantedFlags = resultData!!.flags and
+ (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
try {
- val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- context.contentResolver.takePersistableUriPermission(uri, takeFlags)
+ context.contentResolver.takePersistableUriPermission(uri, grantedFlags)
} catch (e: SecurityException) {
- // Some devices/Android versions fail to persist SAF permission
- android.util.Log.w("SafBridgePlugin", "Failed to persist URI permission: ${e.message}")
- call.reject("Failed to persist folder permission: ${e.message}")
- return
+ // Some devices/OEM ROMs fail to persist SAF permission even when
+ // FLAG_GRANT_PERSISTABLE_URI_PERMISSION was requested. Fall through and
+ // still return the URI — it will work for this session even if it can't
+ // be persisted across reboots (user will need to re-select next launch).
+ android.util.Log.w(
+ "SafBridgePlugin",
+ "Could not persist URI permission (session-only access): ${e.message}",
+ )
}
val ret = JSObject()
@@ -208,8 +218,19 @@ class SafBridgePlugin : Plugin() {
try {
val uri = Uri.parse(uriString)
- val persistedUris = context.contentResolver.persistedUriPermissions
- val hasPermission = persistedUris.any { it.uri == uri && it.isReadPermission && it.isWritePermission }
+ // Check both persistent grants (survive reboots) and temporary session grants
+ // (active for current app session only). This is required to support devices/OEM
+ // ROMs where takePersistableUriPermission throws SecurityException — on those
+ // devices the session grant is still valid even though it could not be persisted.
+ val pid = Process.myPid()
+ val uid = Process.myUid()
+ val canRead = context.checkUriPermission(
+ uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ) == PackageManager.PERMISSION_GRANTED
+ val canWrite = context.checkUriPermission(
+ uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ ) == PackageManager.PERMISSION_GRANTED
+ val hasPermission = canRead && canWrite
val ret = JSObject()
ret.put("hasPermission", hasPermission)
diff --git a/docs/long-term-plans/location-based-reminders.md b/docs/long-term-plans/location-based-reminders.md
new file mode 100644
index 0000000000..1c53422c43
--- /dev/null
+++ b/docs/long-term-plans/location-based-reminders.md
@@ -0,0 +1,513 @@
+# Location-Based Reminders — Design Document
+
+> **Status: Planned (Brainstorm)**
+
+## Overview
+
+Add location-based reminders to Super Productivity. Users can attach a saved location to a task and receive a notification when they arrive at that place. This is primarily a mobile feature (Android/iOS via Capacitor), with passive location display on desktop/web.
+
+## Decisions
+
+| Decision | Choice | Rationale |
+|----------|--------|-----------|
+| Primary platform | Mobile (Android/iOS) | Geofencing requires GPS + background location. Desktop/web show location info but don't trigger. |
+| Trigger type | Arrive only (v1) | Simplest. Leave triggers can be added later. |
+| Location management | Saved locations entity | Users revisit the same places. Follows existing Tag entity pattern. |
+| Location picker (v1) | "Use current location" + label | No map needed for v1. Map picker is a v2 enhancement. |
+| Geofencing approach | Custom native Capacitor plugin | `@capacitor/geolocation` only does point-in-time reads, not geofencing. Need native `GeofencingClient` (Android) / `CLLocationManager` (iOS). |
+| Sync | Sync by default | Location data treated like any other entity. Each device manages its own geofences locally after sync. |
+| Feature toggle | `isLocationRemindersEnabled` in `AppFeaturesConfig` | Opt-in, default false. |
+
+---
+
+## 1. Data Model
+
+### 1.1 SavedLocation Entity
+
+New file: `src/app/features/saved-location/saved-location.model.ts`
+
+```typescript
+import { EntityState } from '@ngrx/entity';
+
+export interface SavedLocationCopy {
+ id: string;
+ title: string; // "Office", "Grocery Store", "Gym"
+ lat: number; // latitude
+ lng: number; // longitude
+ radius: number; // geofence radius in meters (default 200)
+ icon?: string | null; // material icon name, e.g. 'home', 'work', 'shopping_cart'
+ created: number; // creation timestamp
+ modified?: number; // last update timestamp
+}
+
+export type SavedLocation = Readonly;
+export type SavedLocationState = EntityState;
+```
+
+### 1.2 Task Model Changes
+
+File: `src/app/features/tasks/task.model.ts` — add to `TaskCopy`:
+
+```typescript
+/** ID of a SavedLocation. When set, a geofence reminder is active for this task. */
+locationReminderId?: string | null;
+```
+
+### 1.3 Config Changes
+
+File: `src/app/features/config/global-config.model.ts` — add to `AppFeaturesConfig`:
+
+```typescript
+isLocationRemindersEnabled: boolean; // default false
+```
+
+File: `src/app/features/config/default-global-config.const.ts` — set default:
+
+```typescript
+isLocationRemindersEnabled: false,
+```
+
+### 1.4 Platform Capabilities
+
+File: `src/app/core/platform/platform-capabilities.model.ts` — add:
+
+```typescript
+/** Whether the platform supports native geofencing (background location monitoring). */
+readonly geofencing: boolean;
+```
+
+| Platform | `geofencing` |
+|----------|-------------|
+| Android (`ANDROID_CAPABILITIES`) | `true` |
+| iOS (`IOS_CAPABILITIES`) | `true` |
+| Electron (`ELECTRON_CAPABILITIES`) | `false` |
+| Web (`WEB_CAPABILITIES`) | `false` |
+
+---
+
+## 2. Sync & Persistence Registration
+
+All registration steps required for the new entity, following existing patterns:
+
+### 2.1 Entity Type
+
+File: `packages/shared-schema/src/entity-types.ts`
+
+Add `'SAVED_LOCATION'` to the `ENTITY_TYPES` array.
+
+### 2.2 Action Types Enum
+
+File: `src/app/op-log/core/action-types.enum.ts`
+
+```typescript
+// SavedLocation actions
+SAVED_LOCATION_ADD = '[SavedLocation] Add SavedLocation',
+SAVED_LOCATION_UPDATE = '[SavedLocation] Update SavedLocation',
+SAVED_LOCATION_DELETE = '[SavedLocation] Delete SavedLocation',
+```
+
+These string values are **immutable** once deployed — they are used for encoding/decoding operations in IndexedDB and sync between clients.
+
+### 2.3 Entity Registry
+
+File: `src/app/op-log/core/entity-registry.ts`
+
+Add to `ENTITY_CONFIGS`:
+
+```typescript
+SAVED_LOCATION: {
+ storagePattern: 'adapter',
+ featureName: SAVED_LOCATION_FEATURE_NAME,
+ payloadKey: 'savedLocation',
+ adapter: savedLocationAdapter,
+ selectEntities: createSelector(
+ selectSavedLocationFeatureState,
+ selectSavedLocationEntitiesFromAdapter,
+ ),
+ selectById: selectSavedLocationById,
+},
+```
+
+### 2.4 Model Config
+
+File: `src/app/op-log/model/model-config.ts`
+
+Add to `AllModelConfig` type:
+
+```typescript
+savedLocation: ModelCfg;
+```
+
+Add to `MODEL_CONFIGS`:
+
+```typescript
+savedLocation: {
+ defaultData: initialSavedLocationState,
+ isMainFileModel: true,
+ repair: fixEntityStateConsistency,
+},
+```
+
+### 2.5 Root State
+
+File: `src/app/root-store/root-state.ts`
+
+```typescript
+[SAVED_LOCATION_FEATURE_NAME]: SavedLocationState;
+```
+
+### 2.6 Feature Store Registration
+
+File: `src/app/root-store/feature-stores.module.ts`
+
+```typescript
+StoreModule.forFeature(SAVED_LOCATION_FEATURE_NAME, savedLocationReducer),
+```
+
+### 2.7 Cascading Delete Meta-Reducer
+
+File: `src/app/root-store/meta/task-shared-meta-reducers/saved-location-shared.reducer.ts`
+
+When a SavedLocation is deleted, clear `locationReminderId` on all tasks that reference it. This must be a meta-reducer (not an effect) to ensure atomicity — one operation in the sync log.
+
+Register in `src/app/root-store/meta/meta-reducer-registry.ts` in **Phase 5** (Entity-Specific Cascades), alongside `tagSharedMetaReducer`, `projectSharedMetaReducer`, etc.
+
+---
+
+## 3. NgRx Store
+
+### 3.1 Actions
+
+File: `src/app/features/saved-location/store/saved-location.actions.ts`
+
+Following the Tag action pattern with `PersistentActionMeta`:
+
+| Action | OpType | Payload |
+|--------|--------|---------|
+| `addSavedLocation` | `Create` | `{ savedLocation: SavedLocation }` |
+| `updateSavedLocation` | `Update` | `{ savedLocation: Update }` |
+| `deleteSavedLocation` | `Delete` | `{ id: string }` |
+
+All include `meta: { isPersistent: true, entityType: 'SAVED_LOCATION', entityId, opType } satisfies PersistentActionMeta`.
+
+### 3.2 Reducer
+
+File: `src/app/features/saved-location/store/saved-location.reducer.ts`
+
+Standard `@ngrx/entity` adapter:
+
+```typescript
+export const SAVED_LOCATION_FEATURE_NAME = 'savedLocation';
+export const savedLocationAdapter = createEntityAdapter({
+ sortComparer: (a, b) => a.title.localeCompare(b.title),
+});
+export const initialSavedLocationState = savedLocationAdapter.getInitialState();
+```
+
+### 3.3 Selectors
+
+File: `src/app/features/saved-location/store/saved-location.selectors.ts`
+
+| Selector | Returns |
+|----------|---------|
+| `selectSavedLocationFeatureState` | Feature state |
+| `selectAllSavedLocations` | `SavedLocation[]` |
+| `selectSavedLocationById` | `SavedLocation \| undefined` |
+| `selectSavedLocationEntities` | `Dictionary` |
+
+Task-side selector (in task selectors or a cross-feature selector):
+
+| Selector | Returns |
+|----------|---------|
+| `selectTasksWithLocationReminder` | All undone tasks that have `locationReminderId` set |
+
+### 3.4 Service
+
+File: `src/app/features/saved-location/saved-location.service.ts`
+
+Thin wrapper dispatching actions to the store. Methods: `addSavedLocation()`, `updateSavedLocation()`, `deleteSavedLocation()`, `getById$()`.
+
+---
+
+## 4. Geofencing Service
+
+### 4.1 Architecture
+
+New file: `src/app/features/saved-location/geofence.service.ts`
+
+```
+Store (undone tasks with locationReminderId)
+ → GeofenceService watches selector (distinctUntilChanged)
+ → Computes which locations need active geofences
+ → Registers/unregisters via custom Capacitor plugin
+ → Receives geofence enter events
+ → Emits to ReminderService for dialog/notification
+```
+
+### 4.2 Why a Custom Capacitor Plugin
+
+`@capacitor/geolocation` only provides one-time and continuous position reads — it does **not** support geofencing. Native geofencing requires:
+
+- **Android:** `com.google.android.gms.location.GeofencingClient` (Google Play Services). Supports up to 100 geofences. Fires `BroadcastReceiver` on enter/exit.
+- **iOS:** `CLLocationManager.startMonitoring(for: CLCircularRegion)`. Supports up to 20 monitored regions. Fires delegate callbacks on enter/exit.
+
+Implementation: Create `GeofencePlugin.kt` (Android) and `GeofencePlugin.swift` (iOS) extending Capacitor's `Plugin` class. Register in `CapacitorMainActivity.onCreate()` following the pattern of `SafBridgePlugin`, `WebDavHttpPlugin`, etc.
+
+### 4.3 Lifecycle Rules
+
+**Register geofence when:**
+- Feature is enabled + a task gets `locationReminderId` assigned
+- App starts with existing location-reminded tasks
+
+**Unregister geofence when:**
+- Task is completed, deleted, or `locationReminderId` cleared
+- SavedLocation is deleted
+- No more undone tasks reference that location
+- Feature is disabled
+
+**iOS 20-region limit:** Only register geofences for the 20 locations with the most active tasks. Re-evaluate when tasks change.
+
+### 4.4 Effects
+
+Effects MUST use `inject(LOCAL_ACTIONS)` — geofence registration should never happen during remote sync replay. Each device manages its own geofences based on local state after sync.
+
+### 4.5 Background Behavior
+
+When geofence fires while app is in background:
+- **Android:** `BroadcastReceiver` shows native notification directly (same pattern as `ReminderAlarmReceiver`). Tapping opens app + reminder dialog.
+- **iOS:** `CLLocationManager` delegate fires local notification. Tapping opens app + reminder dialog.
+
+### 4.6 Permission Flow
+
+On first use:
+1. Check `CapacitorPlatformService.hasCapability('geofencing')`
+2. Request `ACCESS_FINE_LOCATION` + `ACCESS_BACKGROUND_LOCATION` (Android) or "Always" location access (iOS)
+3. If denied: feature degrades to display-only, show explanation
+
+---
+
+## 5. Reminder Integration
+
+### 5.1 Integration Point
+
+The existing `ReminderService.onRemindersActive$` is a derived observable — external code cannot emit to it directly. Two approaches:
+
+**Option A (recommended):** Add a new public subject on `ReminderService`:
+
+```typescript
+// In ReminderService
+private _onLocationReminders$ = new Subject();
+locationReminders$ = this._onLocationReminders$.asObservable();
+```
+
+Then merge in `ReminderModule`:
+
+```typescript
+merge(
+ this._reminderService.onRemindersActive$,
+ this._reminderService.locationReminders$,
+).subscribe(reminders => /* existing dialog handling */);
+```
+
+**Option B:** Add a public method `emitLocationReminders()` that pushes to the private `_onRemindersActive$` subject.
+
+### 5.2 Data Shape
+
+Location-triggered reminders must satisfy `TaskWithReminderData`:
+
+```typescript
+interface TaskWithReminderData extends Task {
+ readonly reminderData: { remindAt: number }; // use trigger timestamp
+ readonly parentData?: Task;
+ readonly isDeadlineReminder?: boolean; // false for location
+}
+```
+
+Consider adding `isLocationReminder?: boolean` for UI differentiation.
+
+### 5.3 Dialog UX Changes
+
+The existing `DialogViewTaskRemindersComponent` works as-is with minor additions:
+- Show location name/icon in header (e.g., "At: Grocery Store")
+- **Snooze** = suppress this location reminder for 1 hour (temporary geofence pause)
+- **Dismiss** = clear `locationReminderId` on the task
+- **Done** = mark task complete (same as today)
+- Hide "Edit Reminder" time picker for location-triggered reminders
+
+### 5.4 Native Notifications (Background)
+
+Reuse `CapacitorReminderService` patterns:
+- Android: native notification via `ReminderNotificationHelper` triggered from geofence `BroadcastReceiver`
+- iOS: `LocalNotifications.schedule()` triggered from `CLLocationManager` delegate
+- Both support Done/Snooze action buttons
+
+---
+
+## 6. UI Components
+
+### 6.1 New Components
+
+| Component | Path | Purpose |
+|-----------|------|---------|
+| `saved-location-settings` | `src/app/features/saved-location/saved-location-settings/` | CRUD list in settings page |
+| `location-picker-dialog` | `src/app/features/saved-location/location-picker-dialog/` | Assign location to task (dropdown + "Use current location") |
+
+### 6.2 Modified Components
+
+| Component | Change |
+|-----------|--------|
+| Task detail panel | Add "Location" field showing assigned location |
+| Task schedule dialog | Add location option alongside time-based reminder |
+| Settings page | Add "Locations" section |
+| Reminder dialog | Show location name when triggered by geofence |
+
+### 6.3 Location Picker UX
+
+```
+┌─────────────────────────────────┐
+│ Set Location Reminder │
+├─────────────────────────────────┤
+│ │
+│ 📍 Use Current Location │ ← gets GPS, prompts for label
+│ │
+│ ─── Saved Locations ────────── │
+│ │
+│ 🏠 Home │
+│ 🏢 Office │
+│ 🛒 Grocery Store │
+│ │
+│ [ Remove Location ] [ Cancel ] │
+└─────────────────────────────────┘
+```
+
+### 6.4 Settings Section
+
+```
+┌─────────────────────────────────┐
+│ Location Reminders │
+├─────────────────────────────────┤
+│ [Toggle] Enable location │
+│ reminders │
+│ │
+│ Saved Locations: │
+│ ┌─────────────────────────┐ │
+│ │ 🏠 Home 200m [✏️][🗑]│ │
+│ │ 🏢 Office 150m [✏️][🗑]│ │
+│ │ 🛒 Grocery 200m [✏️][🗑]│ │
+│ └─────────────────────────┘ │
+│ [ + Add Location ] │
+└─────────────────────────────────┘
+```
+
+---
+
+## 7. Cross-Platform Behavior
+
+| Platform | Geofencing | Location Display | Notifications |
+|----------|-----------|-----------------|---------------|
+| **Android** | Native `GeofencingClient`, up to 100 fences, background | Yes | Native via `BroadcastReceiver` |
+| **iOS** | Native `CLLocationManager`, up to 20 regions, background | Yes | Native via `LocalNotifications` |
+| **Electron** | None | Yes (label only) | None |
+| **Web** | None | Yes (label only) | None |
+
+---
+
+## 8. File Structure
+
+```
+src/app/features/saved-location/
+├── saved-location.model.ts
+├── saved-location.const.ts # DEFAULT_RADIUS = 200
+├── saved-location.service.ts
+├── geofence.service.ts # Capacitor geofencing bridge
+├── store/
+│ ├── saved-location.actions.ts
+│ ├── saved-location.reducer.ts
+│ └── saved-location.selectors.ts
+├── saved-location-settings/
+│ ├── saved-location-settings.component.ts
+│ └── saved-location-settings.component.html
+└── location-picker-dialog/
+ ├── location-picker-dialog.component.ts
+ └── location-picker-dialog.component.html
+
+src/app/root-store/meta/task-shared-meta-reducers/
+└── saved-location-shared.reducer.ts # Cascading delete
+
+android/app/src/main/java/.../
+└── GeofencePlugin.kt # Native Android geofencing
+ GeofenceBroadcastReceiver.kt # Handles fence enter events
+
+ios/App/App/
+└── GeofencePlugin.swift # Native iOS geofencing
+```
+
+---
+
+## 9. All Files to Touch
+
+### New Files
+
+| File | Purpose |
+|------|---------|
+| `src/app/features/saved-location/saved-location.model.ts` | Entity interface |
+| `src/app/features/saved-location/saved-location.const.ts` | Defaults |
+| `src/app/features/saved-location/saved-location.service.ts` | Service |
+| `src/app/features/saved-location/geofence.service.ts` | Capacitor bridge |
+| `src/app/features/saved-location/store/saved-location.actions.ts` | Actions |
+| `src/app/features/saved-location/store/saved-location.reducer.ts` | Reducer + adapter |
+| `src/app/features/saved-location/store/saved-location.selectors.ts` | Selectors |
+| `src/app/features/saved-location/saved-location-settings/*` | Settings UI |
+| `src/app/features/saved-location/location-picker-dialog/*` | Picker dialog |
+| `src/app/root-store/meta/task-shared-meta-reducers/saved-location-shared.reducer.ts` | Cascading deletes |
+| `android/.../GeofencePlugin.kt` | Android native geofencing |
+| `android/.../GeofenceBroadcastReceiver.kt` | Android fence event handler |
+| `ios/App/App/GeofencePlugin.swift` | iOS native geofencing |
+
+### Modified Files
+
+| File | Change |
+|------|--------|
+| `packages/shared-schema/src/entity-types.ts` | Add `'SAVED_LOCATION'` |
+| `src/app/op-log/core/action-types.enum.ts` | Add `SAVED_LOCATION_ADD/UPDATE/DELETE` |
+| `src/app/op-log/core/entity-registry.ts` | Add `SAVED_LOCATION` config |
+| `src/app/op-log/model/model-config.ts` | Add to `AllModelConfig` + `MODEL_CONFIGS` |
+| `src/app/root-store/root-state.ts` | Add to `RootState` |
+| `src/app/root-store/feature-stores.module.ts` | Register feature store |
+| `src/app/root-store/meta/meta-reducer-registry.ts` | Register cascading delete in Phase 5 |
+| `src/app/features/tasks/task.model.ts` | Add `locationReminderId` field |
+| `src/app/features/config/global-config.model.ts` | Add `isLocationRemindersEnabled` |
+| `src/app/features/config/default-global-config.const.ts` | Set default `false` |
+| `src/app/core/platform/platform-capabilities.model.ts` | Add `geofencing` capability |
+| `src/app/features/reminder/reminder.service.ts` | Add `locationReminders$` subject |
+| `src/app/features/reminder/reminder.module.ts` | Merge location reminders into dialog flow |
+| `android/app/src/main/AndroidManifest.xml` | Add location permissions |
+| `android/.../CapacitorMainActivity.kt` | Register `GeofencePlugin` |
+
+---
+
+## 10. Future Enhancements (Out of Scope)
+
+- Map picker with OpenStreetMap tiles
+- Address search / geocoding (Nominatim)
+- Leave triggers ("remind when I leave the office")
+- Time + location combos ("at the store, but only after 5 PM")
+- Project default locations
+- Location-based task views ("show tasks for where I am now")
+- WiFi-based triggers (alternative to GPS, works indoors)
+- Bluetooth beacon triggers
+
+---
+
+## 11. Verification Plan
+
+1. **Unit tests:** SavedLocation reducer, selectors, service, cascading delete meta-reducer
+2. **Manual mobile testing:**
+ - Create location from "Use current location"
+ - Assign to a task, verify geofence registration
+ - Move in/out of geofence, verify notification fires
+ - Complete task, verify geofence unregistered
+ - Delete location, verify `locationReminderId` cleared on tasks
+3. **Desktop/web:** Verify location label displays on tasks, no geofencing attempted
+4. **Sync:** Create location on device A, verify it appears on device B
+5. **Lint/format:** `npm run lint`, `npm run prettier`, `npm test`
diff --git a/e2e/fixtures/supersync.fixture.ts b/e2e/fixtures/supersync.fixture.ts
index ad7013f1bc..1c91d4dbff 100644
--- a/e2e/fixtures/supersync.fixture.ts
+++ b/e2e/fixtures/supersync.fixture.ts
@@ -47,9 +47,18 @@ export const test = base.extend({
* Also checks server health and skips test if server unavailable.
*/
testRunId: async ({}, use, testInfo) => {
- // Check server health once per worker
+ // Check server health once per worker.
+ // Prefer the result pre-computed in globalSetup (stored as an env var before workers
+ // were forked) to avoid a stampede of concurrent HTTP requests from all workers
+ // simultaneously — which can overload the server and produce false negatives.
if (serverHealthyCache === null) {
- serverHealthyCache = await isServerHealthy();
+ if (process.env.SUPERSYNC_SERVER_HEALTHY !== undefined) {
+ serverHealthyCache = process.env.SUPERSYNC_SERVER_HEALTHY === 'true';
+ } else {
+ // Fallback for when tests are run directly without going through globalSetup
+ // (e.g., `npx playwright test --config ... e2e/tests/sync/foo.spec.ts`)
+ serverHealthyCache = await isServerHealthy();
+ }
if (!serverHealthyCache) {
console.warn(
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
@@ -69,9 +78,13 @@ export const test = base.extend({
* Tests are automatically skipped if the server is not healthy.
*/
serverHealthy: async ({}, use, testInfo) => {
- // Only check once per worker
+ // Only check once per worker — use globalSetup env var if available (see testRunId above)
if (serverHealthyCache === null) {
- serverHealthyCache = await isServerHealthy();
+ if (process.env.SUPERSYNC_SERVER_HEALTHY !== undefined) {
+ serverHealthyCache = process.env.SUPERSYNC_SERVER_HEALTHY === 'true';
+ } else {
+ serverHealthyCache = await isServerHealthy();
+ }
if (!serverHealthyCache) {
console.warn(
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
diff --git a/e2e/global-setup.ts b/e2e/global-setup.ts
index 9217ded795..b390d4cb6e 100644
--- a/e2e/global-setup.ts
+++ b/e2e/global-setup.ts
@@ -2,6 +2,7 @@ import { FullConfig } from '@playwright/test';
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
+import { isServerHealthy } from './utils/supersync-helpers';
/**
* Warm up the dev server by fetching the app once before any tests start.
@@ -69,6 +70,20 @@ const globalSetup = async (config: FullConfig): Promise => {
config.projects[0]?.use?.baseURL ||
'http://localhost:4242';
await warmUpDevServer(baseURL);
+
+ // Check SuperSync server health ONCE here, before workers start.
+ // Without this, each worker checks independently on startup — with many workers
+ // running simultaneously, the concurrent health-check requests can overload the
+ // supersync server and cause false negatives, making workers skip all their tests.
+ // By storing the result in an env var set before workers are forked, every worker
+ // reads the cached result instantly instead of making HTTP requests.
+ const healthy = await isServerHealthy().catch(() => false);
+ process.env.SUPERSYNC_SERVER_HEALTHY = healthy ? 'true' : 'false';
+ if (healthy) {
+ console.log('SuperSync server healthy — supersync tests will run');
+ } else {
+ console.log('SuperSync server not available — supersync tests will be skipped');
+ }
};
export default globalSetup;
diff --git a/e2e/pages/supersync.page.ts b/e2e/pages/supersync.page.ts
index 324bd3e1d3..52a60276d8 100644
--- a/e2e/pages/supersync.page.ts
+++ b/e2e/pages/supersync.page.ts
@@ -191,8 +191,14 @@ export class SuperSyncPage extends BasePage {
// piggybacked ops in the response, causing data to sync between clients
// outside of explicit syncAndWait(). Set here (not in createSimulatedClient)
// so tests using enableWebSocket:true still get immediate uploads.
+ // 3. Block WsTriggeredDownloadService: Even when routeWebSocket() closes the
+ // connection, a WS notification can slip through the moment the connection
+ // opens (before it's closed). Setting __SP_E2E_BLOCK_WS_DOWNLOAD ensures
+ // WsTriggeredDownloadService ignores any such notifications, preventing
+ // uncontrolled background syncs that race with explicit syncAndWait() calls.
await this.page.evaluate(() => {
(globalThis as any).__SP_E2E_BLOCK_IMMEDIATE_UPLOAD = true;
+ (globalThis as any).__SP_E2E_BLOCK_WS_DOWNLOAD = true;
});
}
diff --git a/e2e/tests/sync/supersync.spec.ts b/e2e/tests/sync/supersync.spec.ts
index b078240a3b..548a6470ea 100644
--- a/e2e/tests/sync/supersync.spec.ts
+++ b/e2e/tests/sync/supersync.spec.ts
@@ -672,8 +672,11 @@ test.describe('@supersync SuperSync E2E', () => {
await saveAndGoHomeBtn.click();
console.log('[Archive Test] Client B clicked Save and go home (archiving)');
- // Wait for navigation back to work view
- await clientB.page.waitForURL(/tag\/TODAY/, { timeout: 10000 });
+ // Wait for navigation back to work view.
+ // Use negative lookahead to avoid matching /tag/TODAY/daily-summary prematurely.
+ await clientB.page.waitForURL(/(tag\/TODAY(?!\/daily-summary))/, {
+ timeout: 10000,
+ });
await clientB.page.waitForLoadState('networkidle');
console.log('[Archive Test] Client B back on work view after archiving');
diff --git a/e2e/utils/supersync-helpers.ts b/e2e/utils/supersync-helpers.ts
index 8a4e2368d4..38205b44b3 100644
--- a/e2e/utils/supersync-helpers.ts
+++ b/e2e/utils/supersync-helpers.ts
@@ -239,8 +239,34 @@ export const createSimulatedClient = async (
}
});
- // Navigate to app and wait for ready
- await page.goto('/');
+ // Navigate to app with retry for transient ERR_CONNECTION_REFUSED.
+ // Under parallel load (many workers × 2-3 browser contexts each), the Angular
+ // dev server can temporarily refuse connections. Retrying recovers from this
+ // without failing the test outright.
+ let lastGotoError: Error | null = null;
+ for (let attempt = 0; attempt < 3; attempt++) {
+ try {
+ await page.goto('/');
+ lastGotoError = null;
+ break;
+ } catch (e) {
+ lastGotoError = e as Error;
+ const isConnectionRefused = lastGotoError.message.includes(
+ 'ERR_CONNECTION_REFUSED',
+ );
+ if (attempt < 2 && isConnectionRefused) {
+ const delay = 1000 * (attempt + 1);
+ console.log(
+ `[Client ${clientName}] page.goto('/') failed (attempt ${attempt + 1}/3): ERR_CONNECTION_REFUSED — retrying in ${delay}ms`,
+ );
+ await new Promise((r) => setTimeout(r, delay));
+ } else {
+ break; // Non-connection error or last attempt — let it throw below
+ }
+ }
+ }
+ if (lastGotoError) throw lastGotoError;
+
await waitForAppReady(page);
const workView = new WorkViewPage(page, `${clientName}-${testPrefix}`);
diff --git a/electron/clear-stale-idb-locks.ts b/electron/clear-stale-idb-locks.ts
new file mode 100644
index 0000000000..47138c52fa
--- /dev/null
+++ b/electron/clear-stale-idb-locks.ts
@@ -0,0 +1,57 @@
+import { join } from 'path';
+import { readdir, unlink } from 'fs/promises';
+import { log, warn } from 'electron-log/main';
+
+/**
+ * Deletes stale LevelDB LOCK files left in the IndexedDB directory.
+ *
+ * When Electron (Chromium) exits uncleanly (e.g., session logout with autostart),
+ * the LevelDB LOCK files inside the IndexedDB backing stores are sometimes not
+ * cleaned up. On the next launch these orphaned files block IndexedDB from opening,
+ * producing the "Internal error opening backing store for indexedDB.open" error.
+ *
+ * This is safe to call because by the time it runs, `requestSingleInstanceLock()`
+ * has already ensured we are the only running instance, so no legitimate process
+ * can hold those locks.
+ *
+ * Only runs on Linux, where this startup race condition has been observed.
+ *
+ * @see https://github.com/electron/electron/issues/18263
+ * @see https://github.com/super-productivity/super-productivity/issues/7191
+ */
+export const clearStaleLevelDbLocks = async (userDataPath: string): Promise => {
+ if (process.platform !== 'linux') {
+ return;
+ }
+
+ const idbDir = join(userDataPath, 'IndexedDB');
+
+ let entries: string[];
+ try {
+ entries = await readdir(idbDir);
+ } catch (e: unknown) {
+ // Directory doesn't exist yet (fresh install) — nothing to clean up.
+ // Any other error (e.g., EACCES, EPERM) is unexpected and worth logging.
+ if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {
+ warn(`[clearStaleLevelDbLocks] Could not read IndexedDB directory:`, e);
+ }
+ return;
+ }
+
+ const leveldbDirs = entries.filter((e) => e.endsWith('.leveldb'));
+
+ await Promise.all(
+ leveldbDirs.map(async (dir) => {
+ const lockPath = join(idbDir, dir, 'LOCK');
+ try {
+ await unlink(lockPath);
+ log(`[clearStaleLevelDbLocks] Removed stale lock: ${lockPath}`);
+ } catch (e: unknown) {
+ // LOCK file doesn't exist or is already released — this is the normal case
+ if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {
+ warn(`[clearStaleLevelDbLocks] Could not remove ${lockPath}:`, e);
+ }
+ }
+ }),
+ );
+};
diff --git a/electron/electronAPI.d.ts b/electron/electronAPI.d.ts
index e0df585db0..c2f17fd858 100644
--- a/electron/electronAPI.d.ts
+++ b/electron/electronAPI.d.ts
@@ -48,7 +48,7 @@ export interface ElectronAPI {
fileSyncRemove(args: { filePath: string }): Promise;
- fileSyncListFiles(args: { dirPath: string }): Promise; // NEW
+ fileSyncListFiles(args: { dirPath: string }): Promise;
checkDirExists(args: { dirPath: string }): Promise;
diff --git a/electron/local-file-sync.ts b/electron/local-file-sync.ts
index 100abfc7d5..63c69d0a03 100644
--- a/electron/local-file-sync.ts
+++ b/electron/local-file-sync.ts
@@ -112,6 +112,36 @@ export const initLocalFileSyncAdapter = (): void => {
return true;
} catch (e) {
log('ERR: error while checking dir ' + dirPath);
+ if ((e as NodeJS.ErrnoException).code === 'EACCES') {
+ log(
+ 'ERR: Permission denied. If running as a snap, ensure the "home" or "removable-media" interface is connected.',
+ );
+ }
+ error(e);
+ return e instanceof Error ? e : new Error(String(e));
+ }
+ },
+ );
+
+ ipcMain.handle(
+ IPC.FILE_SYNC_LIST_FILES,
+ (
+ ev,
+ {
+ dirPath,
+ }: {
+ dirPath: string;
+ },
+ ): string[] | Error => {
+ try {
+ return readdirSync(dirPath);
+ } catch (e) {
+ log('ERR: Sync error while listing files in ' + dirPath);
+ if ((e as NodeJS.ErrnoException).code === 'EACCES') {
+ log(
+ 'ERR: Permission denied. If running as a snap, ensure the "home" or "removable-media" interface is connected.',
+ );
+ }
error(e);
return e instanceof Error ? e : new Error(String(e));
}
diff --git a/electron/main-window.ts b/electron/main-window.ts
index ed36e48332..7323cba23d 100644
--- a/electron/main-window.ts
+++ b/electron/main-window.ts
@@ -459,7 +459,7 @@ const appCloseHandler = (app: App): void => {
mainWin.on('close', (event) => {
// NOTE: this might not work if we run a second instance of the app
- log('close, isQuiting:', getIsQuiting());
+ log('close event: isQuiting=', getIsQuiting(), 'pendingBeforeCloseIds=', ids);
if (!getIsQuiting()) {
if (getIsMinimizeToTray()) {
const indicator = ensureIndicator();
diff --git a/electron/preload.ts b/electron/preload.ts
index 2e3fcd96b2..db715e9520 100644
--- a/electron/preload.ts
+++ b/electron/preload.ts
@@ -68,8 +68,8 @@ const ea: ElectronAPI = {
dataStr: string | undefined;
}>,
fileSyncRemove: (filePath) => _invoke('FILE_SYNC_REMOVE', filePath) as Promise,
- fileSyncListFiles: ({ dirPath }) =>
- _invoke('FILE_SYNC_LIST_FILES', dirPath) as Promise,
+ fileSyncListFiles: (args) =>
+ _invoke('FILE_SYNC_LIST_FILES', args) as Promise,
checkDirExists: (dirPath) =>
_invoke('CHECK_DIR_EXISTS', dirPath) as Promise,
diff --git a/electron/shared-with-frontend/ipc-events.const.ts b/electron/shared-with-frontend/ipc-events.const.ts
index b0d5ac251d..85998c2cee 100644
--- a/electron/shared-with-frontend/ipc-events.const.ts
+++ b/electron/shared-with-frontend/ipc-events.const.ts
@@ -43,7 +43,7 @@ export enum IPC {
FILE_SYNC_LOAD = 'FILE_SYNC_LOAD',
FILE_SYNC_SAVE = 'FILE_SYNC_SAVE',
FILE_SYNC_REMOVE = 'FILE_SYNC_REMOVE',
- FILE_SYNC_LIST_FILES = 'FILE_SYNC_LIST_FILES', // NEW
+ FILE_SYNC_LIST_FILES = 'FILE_SYNC_LIST_FILES',
FILE_SYNC_GET_REV_AND_CLIENT_UPDATE = 'FILE_SYNC_GET_REV_AND_CLIENT_UPDATE',
CHECK_DIR_EXISTS = 'CHECK_DIR_EXISTS',
diff --git a/electron/start-app.ts b/electron/start-app.ts
index 08a55169d3..af936c8db0 100644
--- a/electron/start-app.ts
+++ b/electron/start-app.ts
@@ -13,14 +13,15 @@ import { CONFIG } from './CONFIG';
import { lazySetInterval } from './shared-with-frontend/lazy-set-interval';
import { initIndicator } from './indicator';
import { quitApp, showOrFocus } from './various-shared';
-import { createWindow } from './main-window';
+import { createWindow, getWin } from './main-window';
import { IdleTimeHandler } from './idle-time-handler';
import { destroyTaskWidget } from './task-widget/task-widget';
import {
initializeProtocolHandling,
processPendingProtocolUrls,
} from './protocol-handler';
-import { getIsQuiting, setIsLocked } from './shared-state';
+import { getIsQuiting, setIsQuiting, setIsLocked } from './shared-state';
+import { clearStaleLevelDbLocks } from './clear-stale-idb-locks';
import * as fs from 'fs';
const ICONS_FOLDER = __dirname + '/assets/icons/';
@@ -356,18 +357,32 @@ export const startApp = (): void => {
appIN.on('will-quit', () => {
// un-register all shortcuts.
globalShortcut.unregisterAll();
+ // Safe to remove IPC listeners here: all windows are closed and before-close
+ // IPC flows (sync, finish-day) are guaranteed to have completed.
+ ipcMain.removeAllListeners();
});
- appIN.on('before-quit', () => {
- log('App before-quit: cleaning up resources');
-
- // Clean up task widget before quitting
+ appIN.on('before-quit', (event) => {
+ log('App before-quit: isQuiting=', getIsQuiting());
+ if (!getIsQuiting()) {
+ // Native quit path (Cmd+Q, Dock > Quit on macOS): app.quit() was called
+ // by the OS without going through quitApp(), so isQuiting was never set.
+ // Prevent the immediate quit and delegate to the window close handler,
+ // which manages the before-close callback flow (sync, finish-day, etc.)
+ // and sets isQuiting=true before re-quitting.
+ event.preventDefault();
+ const win = getWin();
+ if (win && !win.isDestroyed()) {
+ win.close();
+ } else {
+ // No window to close — set flag and re-trigger quit directly.
+ setIsQuiting(true);
+ app.quit();
+ }
+ return;
+ }
+ // isQuiting=true: all before-close IPC work is complete — safe to clean up.
destroyTaskWidget();
-
- // Remove all IPC listeners to prevent memory leaks
- ipcMain.removeAllListeners();
-
- // Clear any pending timeouts/intervals
if (global.gc) {
global.gc();
}
@@ -425,6 +440,10 @@ export const startApp = (): void => {
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
async function createMainWin(): Promise {
+ // Remove stale LevelDB LOCK files before the renderer opens IndexedDB.
+ // Orphaned locks from unclean session shutdowns block the backing store open.
+ await clearStaleLevelDbLocks(app.getPath('userData'));
+
mainWin = await createWindow({
app,
IS_DEV,
diff --git a/packages/plugin-dev/github-issue-provider/i18n/en.json b/packages/plugin-dev/github-issue-provider/i18n/en.json
index 012babeba6..d97589e083 100644
--- a/packages/plugin-dev/github-issue-provider/i18n/en.json
+++ b/packages/plugin-dev/github-issue-provider/i18n/en.json
@@ -1,7 +1,7 @@
{
"CFG": {
"REPO": "Repository (owner/repo)",
- "TOKEN": "Personal Access Token (optional, for private repos)",
+ "TOKEN": "Personal Access Token (required for private repos/optional for public repos)",
"FILTER_USERNAME": "Filter username (ignore own comments for update detection)",
"BACKLOG_QUERY": "Backlog query (default: \"sort:updated state:open assignee:@me\")",
"HOW_TO_GET_TOKEN": "How to get a token"
diff --git a/src/app/core-ui/mobile-bottom-nav/mobile-bottom-nav.component.html b/src/app/core-ui/mobile-bottom-nav/mobile-bottom-nav.component.html
index e62a17af99..7d3542f9fe 100644
--- a/src/app/core-ui/mobile-bottom-nav/mobile-bottom-nav.component.html
+++ b/src/app/core-ui/mobile-bottom-nav/mobile-bottom-nav.component.html
@@ -1,6 +1,7 @@
",
+ "CORS_INFO": "Making it work in a web browser: You need to allow Super Productivity to make CORS requests to your server. For Nextcloud, one approach is allowing \"https://app.super-productivity.com\" via the webapppassword app. Refer to this GitHub thread for more information.",
"D_SYNC_FOLDER_PATH": "Path relative to the WebDAV server root where sync files will be stored (e.g. '/super-productivity' or '/'). This is NOT your server's internal directory path.",
- "INFO": "Warning: WebDAV implementations differ wildly. Super Productivity is known to work well with Nextcloud, but it may not work with your provider. Test thoroughly before relying on it.",
+ "INFO": "Warning: Generic WebDAV sync is provided as-is without support. If you are using Nextcloud, please use the Nextcloud sync option instead.",
"L_BASE_URL": "Base URL",
"L_PASSWORD": "Password",
"L_SYNC_FOLDER_PATH": "Sync Folder Path",
@@ -1315,6 +1337,8 @@
"ERROR_PERMISSION": "File access denied. Please check your filesystem permissions.",
"ERROR_PERMISSION_FLATPAK": "File access denied. Grant filesystem permission via Flatseal or use a path inside ~/.var/app/",
"ERROR_PERMISSION_SNAP": "File access denied. Run 'snap connect super-productivity:home' or use a path inside ~/snap/super-productivity/common/",
+ "ERROR_REMOTE_FILE_EMPTY": "Remote sync file is empty. This can happen after an interrupted sync. Overwrite remote with your local data?",
+ "ERROR_REMOTE_FILE_LOCKED": "Remote sync file is locked by the server. This may resolve on the next sync attempt.",
"ERROR_UNABLE_TO_READ_REMOTE_DATA": "Error while syncing. Unable to read remote data. Maybe you enabled encryption and your local password does not match the one used to encrypt the remote data?",
"FINISH_DAY_SYNC_ERROR": "Sync error while finishing day. Please try again.",
"FRESH_CLIENT_SYNC_CANCELLED": "Sincronizare inițială anulată. Datele de la distanță nu au fost aplicate.",
@@ -1405,11 +1429,11 @@
"TITLE": "Basic Settings"
},
"S": {
- "ARCHIVE_CLEANUP_FAILED": "Curățarea datelor arhivate pentru eticheta ștearsă a eșuat",
- "UPDATED": "Tag settings were updated"
+ "ARCHIVE_CLEANUP_FAILED": "A eșuat curățarea datelor arhivate pentru eticheta ștearsă",
+ "UPDATED": "Setările etichetei au fost actualizate"
},
"TTL": {
- "ADD_NEW_TAG": "Add new Tag"
+ "ADD_NEW_TAG": "Adaugă etichetă nouă"
}
},
"TAG_FOLDER": {
@@ -1489,47 +1513,47 @@
"ADD_SUB_TASK": "Adaugă subsarcină",
"ADD_TO_MY_DAY": "Adaugă la lista de azi",
"ADD_TO_PROJECT": "Adaugă la proiect",
- "CONVERT_TO_PARENT_TASK": "Convert to parent task",
- "DELETE": "Delete task",
- "DELETE_REPEAT_INSTANCE": "Delete recurring task instance",
- "DROP_ATTACHMENT": "Drop here to attach to \"{{title}}\"",
- "DUPLICATE": "Duplicate",
+ "CONVERT_TO_PARENT_TASK": "Convertește în sarcină părinte",
+ "DELETE": "Șterge sarcina",
+ "DELETE_REPEAT_INSTANCE": "Șterge instanța de sarcină recurentă",
+ "DROP_ATTACHMENT": "Plasează aici pentru a atașa la \"{{title}}\"",
+ "DUPLICATE": "Duplică",
"EDIT_DEADLINE": "Editează termen limită",
- "EDIT_SCHEDULED": "Reschedule",
- "FOCUS_SESSION": "Start focus session",
- "MARK_DONE": "Mark as completed",
- "MOVE_TO_BACKLOG": "Move to backlog",
- "MOVE_TO_OTHER_PROJECT": "Move to project",
- "MOVE_TO_REGULAR": "Move to regular list",
- "MOVE_TO_TOP": "Move to top",
- "OPEN_ATTACH": "Attach file or link",
- "OPEN_ISSUE": "Open in browser",
- "OPEN_TIME": "Time tracking",
+ "EDIT_SCHEDULED": "Planifică din nou",
+ "FOCUS_SESSION": "Începe sesiune de concentrare",
+ "MARK_DONE": "Marchează ca finalizată",
+ "MOVE_TO_BACKLOG": "Mută în backlog",
+ "MOVE_TO_OTHER_PROJECT": "Mută în proiect",
+ "MOVE_TO_REGULAR": "Mută în lista obișnuită",
+ "MOVE_TO_TOP": "Mută în partea de sus",
+ "OPEN_ATTACH": "Atașează fișier sau legătură",
+ "OPEN_ISSUE": "Deschide în navigatorul web",
+ "OPEN_TIME": "Contorizare timp",
"REMOVE_DEADLINE": "Șterge termen limită",
"REMOVE_FROM_MY_DAY": "Elimină din lista de azi",
- "SCHEDULE": "Schedule task",
+ "SCHEDULE": "Planifică sarcina",
"SET_DEADLINE": "Setează termen limită",
- "TOGGLE_ATTACHMENTS": "Show/hide attachments",
- "TOGGLE_DETAIL_PANEL": "Show/hide additional info",
- "TOGGLE_DONE": "Toggle completion status",
- "TOGGLE_SUB_TASK_VISIBILITY": "Toggle subtask visibility",
- "TOGGLE_TAGS": "Toggle tags",
- "TRACK_TIME": "Start tracking time",
- "TRACK_TIME_STOP": "Pause tracking time",
- "UNSCHEDULE_TASK": "Unschedule task",
- "UPDATE_ISSUE_DATA": "Update issue data"
+ "TOGGLE_ATTACHMENTS": "Arată/Ascunde atașamente",
+ "TOGGLE_DETAIL_PANEL": "Arată/Ascunde informații suplimentare",
+ "TOGGLE_DONE": "Comută starea de finalizare",
+ "TOGGLE_SUB_TASK_VISIBILITY": "Comută vizibilitatea subsarcinilor",
+ "TOGGLE_TAGS": "Comută etichetele",
+ "TRACK_TIME": "Începe contorizarea timpului",
+ "TRACK_TIME_STOP": "Pune pe pauză contorizarea timpului",
+ "UNSCHEDULE_TASK": "Șterge planificarea sarcinii",
+ "UPDATE_ISSUE_DATA": "Actualizează datele problemei"
},
"D_CONFIRM_DELETE": {
"MSG": "Dorești să ștergi sarcina \"{{title}}\"?",
- "OK": "Delete"
+ "OK": "Șterge"
},
"D_CONFIRM_SHORT_SYNTAX_NEW_TAG": {
- "MSG": "Do you want to create the new tag {{tagsTxt}}?",
- "OK": "Create tag"
+ "MSG": "DDorești să creezi eticheta nouă {{tagsTxt}}?",
+ "OK": "Creează etichetă"
},
"D_CONFIRM_SHORT_SYNTAX_NEW_TAGS": {
- "MSG": "Do you want to create the new tags {{tagsTxt}}?",
- "OK": "Create tags"
+ "MSG": "Dorești să creezi etichetele noi {{tagsTxt}}?",
+ "OK": "Creează etichete"
},
"D_DEADLINE": {
"ADD_TIME": "Adaugă ora",
@@ -1549,28 +1573,28 @@
"SET_DEADLINE": "Setează termen limită"
},
"D_REMINDER_VIEW": {
- "ADD_ALL_TO_TODAY": "Add all to Today",
- "ADD_TO_TODAY": "Add to Today",
+ "ADD_ALL_TO_TODAY": "Adaugă-le pe toate la Astăzi",
+ "ADD_TO_TODAY": "Adaugă la Astăzi",
"CLEAR_ALL_REMINDERS": "Șterge toate memento-urile",
"CLEAR_REMINDER": "Șterge memento",
- "COMPLETE": "Complete",
- "COMPLETE_ALL": "Complete all",
+ "COMPLETE": "Finalizează",
+ "COMPLETE_ALL": "Finalizează-le pe toate",
"DEADLINE_REMINDER": "Memento termen limită",
"DEADLINE_SECTION": "Termene limită",
- "DISMISS_ALL_REMINDERS_KEEP_TODAY": "Dismiss All Reminders (Keep in Today)",
- "DISMISS_REMINDER_KEEP_TODAY": "Dismiss Reminder (Keep in Today)",
- "DONE": "Done",
+ "DISMISS_ALL_REMINDERS_KEEP_TODAY": "Înlătură toate mementourile (păstrează în Astăzi)",
+ "DISMISS_REMINDER_KEEP_TODAY": "Înlătură memento (păstrează în Astăzi)",
+ "DONE": "Realizat",
"DUE_SECTION": "Planificate",
- "DUE_TASK": "Reminder for planned task",
- "DUE_TASKS": "Reminder for planned tasks",
- "RESCHEDULE_EDIT": "Edit (reschedule)",
- "RESCHEDULE_UNTIL_TOMORROW": "Reschedule for tomorrow",
- "SNOOZE": "Snooze",
- "SNOOZE_ALL": "Snooze all",
- "START": "Start",
- "TASK_REMINDERS": "Memento-uri sarcini",
- "UNSCHEDULE": "Unschedule",
- "UNSCHEDULE_ALL": "Unschedule all"
+ "DUE_TASK": "Memento sarcină planificată",
+ "DUE_TASKS": "Memento sarcini planificate",
+ "RESCHEDULE_EDIT": "Editează (planifică din nou)",
+ "RESCHEDULE_UNTIL_TOMORROW": "Planifică din nou pentru mîine",
+ "SNOOZE": "Amînă",
+ "SNOOZE_ALL": "Amînă-le pe toate",
+ "START": "Începe",
+ "TASK_REMINDERS": "Mementouri sarcini",
+ "UNSCHEDULE": "Șterge planificarea",
+ "UNSCHEDULE_ALL": "Șterge planificarea tuturor"
},
"D_SCHEDULE_TASK": {
"QA_NEXT_MONTH": "Schedule next month",
@@ -1589,9 +1613,9 @@
"UNSCHEDULE": "Unschedule"
},
"D_SELECT_DATE_AND_TIME": {
- "DATE": "Date",
- "TIME": "Time",
- "TITLE": "Select Date and Time"
+ "DATE": "Dată",
+ "TIME": "Oră",
+ "TITLE": "Alege data și ora"
},
"D_TIME": {
"ADD_FOR_OTHER_DAY": "Add time spent for other day",
@@ -1671,18 +1695,18 @@
"OK": "Remove completely"
},
"D_CONFIRM_UPDATE_INSTANCES": {
- "CANCEL": "Only future tasks",
- "MSG": "There are {{tasksNr}} instances created for this recurring task. Do you want to update all of them with the new defaults or just future tasks?",
- "OK": "Update all instances"
+ "CANCEL": "Doar sarcinile viitoare",
+ "MSG": "Există {{tasksNr}} instanțe create pentru această sarcină recurentă. Dorești să le actualizezi pe toate cu noile valori implicite sau doar să actualizezi sarcinile viitoare?",
+ "OK": "Actualizează toate instanțele"
},
"D_SKIP_INSTANCE": {
- "MSG": "Skip the recurring task on {{date}}? This will prevent the task from being created on this date only.",
+ "MSG": "Omiți sarcina recurentă pe {{date}}? Acest lucru va preveni crearea sarcinii doar pe această dată.",
"OK": "Omite"
},
"D_EDIT": {
- "ADD": "Add Recurring Task Config",
- "ADVANCED_CFG": "Advanced configuration",
- "EDIT": "Edit Recurring Task Config",
+ "ADD": "Adaugă configurație sarcină recurentă",
+ "ADVANCED_CFG": "Configurare avansată",
+ "EDIT": "Editează configurația sarcinii recurente",
"HEATMAP_LABEL": "Activitate",
"TAG_LABEL": "Etichete",
"TIME_SPENT_THIS_MONTH": "Luna aceasta",
@@ -1690,105 +1714,105 @@
"TIME_SPENT_TOTAL": "Total"
},
"F": {
- "C_DAY": "Day",
- "C_MONTH": "Month",
- "C_WEEK": "Week",
- "C_YEAR": "Year",
- "DEFAULT_ESTIMATE": "Default Estimate",
- "DISABLE_AUTO_UPDATE_SUBTASKS": "Disable auto-updating subtasks",
- "DISABLE_AUTO_UPDATE_SUBTASKS_DESCRIPTION": "Do not update inherited subtasks automatically when the newest instance changes",
- "FRIDAY": "Friday",
- "INHERIT_SUBTASKS": "Inherit subtasks",
- "INHERIT_SUBTASKS_DESCRIPTION": "When enabled, subtasks from the most recent task instance will be recreated with the recurring task",
- "MONDAY": "Monday",
- "NOTES": "Default notes",
- "Q_CUSTOM": "Custom recurring config",
- "Q_DAILY": "Every day",
- "Q_MONDAY_TO_FRIDAY": "Every Monday through Friday",
- "Q_MONTHLY_CURRENT_DATE": "Every month on the day {{dateDayStr}}",
+ "C_DAY": "Zi",
+ "C_MONTH": "Lună",
+ "C_WEEK": "Săptămînă",
+ "C_YEAR": "An",
+ "DEFAULT_ESTIMATE": "Estimare implicită",
+ "DISABLE_AUTO_UPDATE_SUBTASKS": "Dezactivează auto-actualizarea subsarcinilor",
+ "DISABLE_AUTO_UPDATE_SUBTASKS_DESCRIPTION": "Nu actualiza automat subsarcinile moștenate cînd instanța cea mai nouă se modifică",
+ "FRIDAY": "vineri",
+ "INHERIT_SUBTASKS": "Moștenește subsarcini",
+ "INHERIT_SUBTASKS_DESCRIPTION": "Cînd este activat, subsarcinile din instanța cea mai recentă a sarcinii vor fi recreate cu sarcina recurentă",
+ "MONDAY": "luni",
+ "NOTES": "Notițe implicite",
+ "Q_CUSTOM": "Configurare recurentă personalizată",
+ "Q_DAILY": "În fiecare zi",
+ "Q_MONDAY_TO_FRIDAY": "În fiecare zi de luni pînă vineri",
+ "Q_MONTHLY_CURRENT_DATE": "În fiecare lună în ziua {{dateDayStr}}",
"Q_MONTHLY_FIRST_DAY": "În fiecare lună în prima zi",
"Q_MONTHLY_LAST_DAY": "În fiecare lună în ultima zi",
- "Q_WEEKLY_CURRENT_WEEKDAY": "Every week on {{weekdayStr}}",
- "Q_YEARLY_CURRENT_DATE": "Every year on the {{dayAndMonthStr}}",
- "QUICK_SETTING": "Recurring Config",
- "REMIND_AT": "Remind at",
- "REMIND_AT_PLACEHOLDER": "Select when to remind",
- "SKIP_FOR_DATE": "Skip for {{date}}",
- "SKIP_INSTANCE": "Skip for today",
- "REPEAT_CYCLE": "Recur cycle",
- "REPEAT_EVERY": "Recur every",
- "SATURDAY": "Saturday",
- "SCHEDULE_TYPE_LABEL": "Schedule type",
+ "Q_WEEKLY_CURRENT_WEEKDAY": "În fiecare săptămînă {{weekdayStr}}",
+ "Q_YEARLY_CURRENT_DATE": "În fiecare an pe {{dayAndMonthStr}}",
+ "QUICK_SETTING": "Configurație recurentă",
+ "REMIND_AT": "Amintește la",
+ "REMIND_AT_PLACEHOLDER": "Selectează cînd să fie amintit",
+ "SKIP_FOR_DATE": "Omite pentru {{date}}",
+ "SKIP_INSTANCE": "Omite pentru astăzi",
+ "REPEAT_CYCLE": "Ciclu de repetare",
+ "REPEAT_EVERY": "Repetă la fiecare",
+ "SATURDAY": "sîmbătă",
+ "SCHEDULE_TYPE_LABEL": "Tip programare",
"SKIP_OVERDUE": "Omite instanțele întîrziate",
"SKIP_OVERDUE_DESCRIPTION": "Cînd este activat, dacă aplicația este deschisă după ce a fost omisă o apariție programată (de ex. o sarcină săptămînală în care azi nu este o zi programată), instanța ratată va fi omisă silențios în loc să fie creată ca sarcină întîrziată",
- "START_DATE": "Start date",
- "START_TIME": "Scheduled start time",
- "START_TIME_DESCRIPTION": "E.g. 15:00. Leave blank for an all day task",
- "SUNDAY": "Sunday",
- "THURSDAY": "Thursday",
- "TITLE": "Task title",
- "TUESDAY": "Tuesday",
- "WEDNESDAY": "Wednesday"
+ "START_DATE": "Dată de început",
+ "START_TIME": "Timpul programat de început",
+ "START_TIME_DESCRIPTION": "De ex. 15:00. Lasă necompletat pentru o sarcină pe toată ziua",
+ "SUNDAY": "duminică",
+ "THURSDAY": "joi",
+ "TITLE": "Nume sarcină",
+ "TUESDAY": "marți",
+ "WEDNESDAY": "miercuri"
},
"SNACK_REPEAT_DIALOG_FAIL": "Nu s-a putut deschide dialogul de configurare repetare. Puteți configura din meniul sarcinii."
},
"TASK_VIEW": {
"CUSTOMIZER": {
- "DEADLINE_NEXT_MONTH": "Due Next Month",
- "DEADLINE_NEXT_WEEK": "Due Next Week",
- "DEADLINE_THIS_MONTH": "Due This Month",
- "DEADLINE_THIS_WEEK": "Due This Week",
- "DEADLINE_TODAY": "Due Today",
- "DEADLINE_TOMORROW": "Due Tomorrow",
- "ENTER_PROJECT": "Filter Projects",
- "ENTER_TAG": "Filter Tag",
- "ESTIMATED_TIME": "Estimated Time",
- "FILTER_BY": "Filter By",
- "FILTER_DEADLINE": "Deadline",
- "FILTER_DEFAULT": "No Filter",
- "FILTER_ESTIMATED_TIME": "Estimated Time",
- "FILTER_NOT_SPECIFIED": "Not specified",
- "FILTER_PROJECT": "Project",
- "FILTER_SCHEDULED_DATE": "Scheduled Date",
- "FILTER_TAG": "Tag",
- "FILTER_TIME_SPENT": "Time Spent",
- "GROUP_BY": "Group By",
- "GROUP_DEADLINE": "Deadline",
- "GROUP_DEADLINE_NONE": "No deadline",
- "GROUP_DEFAULT": "No Group",
- "GROUP_PROJECT": "Project",
- "GROUP_SCHEDULED_DATE": "Scheduled Date",
- "GROUP_TAG": "Tag",
- "RESET_ALL": "Reset All",
+ "DEADLINE_NEXT_MONTH": "Scadente luna viitoare",
+ "DEADLINE_NEXT_WEEK": "Scadente săptămîna viitoare",
+ "DEADLINE_THIS_MONTH": "Scadente luna aceasta",
+ "DEADLINE_THIS_WEEK": "Scadente săptămîna aceasta",
+ "DEADLINE_TODAY": "Scadente astăzi",
+ "DEADLINE_TOMORROW": "Scadente mîine",
+ "ENTER_PROJECT": "Filtrează proiecte",
+ "ENTER_TAG": "Filtrează etichete",
+ "ESTIMATED_TIME": "Timp estimat",
+ "FILTER_BY": "Filtrează după",
+ "FILTER_DEADLINE": "Scadență",
+ "FILTER_DEFAULT": "Fără filtru",
+ "FILTER_ESTIMATED_TIME": "Timp estimat",
+ "FILTER_NOT_SPECIFIED": "Nu este specificat",
+ "FILTER_PROJECT": "Proiect",
+ "FILTER_SCHEDULED_DATE": "Dată programată",
+ "FILTER_TAG": "Etichetă",
+ "FILTER_TIME_SPENT": "Timp petrecut",
+ "GROUP_BY": "Grupare după",
+ "GROUP_DEADLINE": "Scadență",
+ "GROUP_DEADLINE_NONE": "Fără scadență",
+ "GROUP_DEFAULT": "Fără grup",
+ "GROUP_PROJECT": "Proiect",
+ "GROUP_SCHEDULED_DATE": "Dată programată",
+ "GROUP_TAG": "Etichetă",
+ "RESET_ALL": "Resetează tot",
"SAVE_SORT": "Salvează ca implicit",
- "SCHEDULED_NEXT_MONTH": "Next Month",
- "SCHEDULED_NEXT_WEEK": "Next Week",
- "SCHEDULED_THIS_MONTH": "This Month",
- "SCHEDULED_THIS_WEEK": "This Week",
+ "SCHEDULED_NEXT_MONTH": "Luna viitoare",
+ "SCHEDULED_NEXT_WEEK": "Săptămîna viitoare",
+ "SCHEDULED_THIS_MONTH": "Luna aceasta",
+ "SCHEDULED_THIS_WEEK": "Săptămîna aceasta",
"SCHEDULED_TODAY": "Astăzi",
"SCHEDULED_TOMORROW": "Mîine",
"SORT": "Sortare",
- "SORT_CREATION_DATE": "Creation Date",
- "SORT_DEADLINE": "Deadline",
- "SORT_DEFAULT": "Default",
- "SORT_NAME": "Name",
- "SORT_SCHEDULED_DATE": "Scheduled Date",
- "TIME_1HOUR": "> 1 Hour",
- "TIME_2HOUR": "> 2 Hours",
- "TIME_10MIN": "> 10 Minutes",
- "TIME_30MIN": "> 30 Minutes",
- "TIME_SPENT": "Time Spent"
+ "SORT_CREATION_DATE": "Dată de creare",
+ "SORT_DEADLINE": "Scadență",
+ "SORT_DEFAULT": "Implicit",
+ "SORT_NAME": "Nume",
+ "SORT_SCHEDULED_DATE": "Dată programată",
+ "TIME_1HOUR": "> 1 oră",
+ "TIME_2HOUR": "> 2 ore",
+ "TIME_10MIN": "> 10 minute",
+ "TIME_30MIN": "> 30 minute",
+ "TIME_SPENT": "Timp petrecut"
}
},
"TIME_TRACKING": {
"B": {
- "ALREADY_DID": "I already did",
- "SNOOZE": "Snooze {{time}}"
+ "ALREADY_DID": "Deja am făcut",
+ "SNOOZE": "Amînă {{time}}"
},
"B_TTR": {
- "ADD_TO_TASK": "Add to Task",
- "MSG": "You have not been tracking time for {{time}}",
- "MSG_WITHOUT_TIME": "You have not been tracking time"
+ "ADD_TO_TASK": "Adaugă la sarcină",
+ "MSG": "Nu ai ținut evidența timpului pentru {{time}}",
+ "MSG_WITHOUT_TIME": "Nu ai ținut evidența timpului"
},
"D_ARCHIVE_COMPRESS": {
"BTN_COMPRESS": "Comprimă arhiva",
@@ -1928,10 +1952,10 @@
"DO_IT": "Fă-l!",
"DONT_SHOW_AGAIN": "Nu mai afișa din nou",
"DUPLICATE": "Duplică",
- "DURATION_DESCRIPTION": "ex: \"5h 23m\" care rezultă în 5 ore și 23 minute",
+ "DURATION_DESCRIPTION": "d.e. \"5h 23m\" care rezultă în 5 ore și 23 minute",
"EDIT": "Editează",
"ENABLED": "Activat",
- "EXAMPLE_VAL": "ex: 32m",
+ "EXAMPLE_VAL": "d.e. 32m",
"EXTENSION_INFO": "Te rog descarcă extensia Chrome pentru a permite comunicarea cu API-ul Jira și tratarea timpului mort. Acest lucru nu funcționează pe mobil. Fără extensie, aceste funcții nu vor funcționa!",
"HIDE": "Ascunde",
"ICON_INP_DESCRIPTION": "Toate emoji-urile UTF-8 sînt de asemenea suportate!",
@@ -1942,7 +1966,7 @@
"NEXT": "Următorul",
"NO_CON": "Momentan ești offline. Te rog reconectează-te la internet.",
"NONE": "Niciunul",
- "OK": "Ok",
+ "OK": "OK",
"OPEN_IN_BROWSER": "Deschide în navigatorul web",
"OVERDUE": "Restante",
"PASSWORD_STRENGTH_FAIR": "Acceptabilă",
@@ -1962,25 +1986,27 @@
},
"GCF": {
"APP_FEATURES": {
- "BOARDS": "Boards",
- "DONATE_PAGE": "Donate page",
+ "BOARDS": "Table",
+ "DONATE_PAGE": "Pagină de donații",
"FINISH_DAY": "Încheie ziua",
- "FOCUS_MODE": "Focus mode",
+ "FOCUS_MODE": "Mod concentrare",
"HABITS": "Obiceiuri",
- "HELP": "Enable or disable specific app features across the UI.",
- "ISSUES_PANEL": "Issues panel",
- "PLANNER": "Planner",
- "PROJECT_NOTES": "Project notes",
+ "HELP": "Activează sau dezactivează anumite funcții ale aplicației din interfață.",
+ "ISSUES_PANEL": "Panou probleme",
+ "PLANNER": "Planificator",
+ "PROJECT_NOTES": "Notițe proiect",
"SCHEDULE": "Orar",
- "SCHEDULE_DAY_PANEL": "Schedule day panel",
+ "SCHEDULE_DAY_PANEL": "Panou programare zi",
"SEARCH": "Caută",
- "SYNC_BUTTON": "Sync button",
- "TIME_TRACKING": "Stopwatch time tracking",
- "TITLE": "App Features",
- "USER_PROFILES": "User profiles (experimental)",
- "USER_PROFILES_HINT": "Allows you to create and switch between different user profiles, each with separate settings, tasks, and sync configurations. The profile management button will appear in the top-right corner when enabled. Note: Disabling this feature will hide the UI but preserve your profile data. (Experimental feature. No guarantees. Make sure to have a backup!)",
- "USER_PROFILES_WARNING": "Funcție experimentală: Această funcție este încă în dezvoltare și poate fi eliminată sau modificată semnificativ într-o actualizare viitoare. Folosește pe propriul risc și asigură-te că ai backup-uri ale datelor."
- },
+ "SYNC_BUTTON": "Buton sincronizare",
+ "TIME_TRACKING": "Cronometru urmărire timp",
+ "TITLE": "Funcții aplicație",
+ "USER_PROFILES": "Profiluri utilizator (experimental — ar putea fi eliminate)",
+ "USER_PROFILES_HINT": "Îți permite să creezi și să comuti între diferite profiluri de utilizator, fiecare cu setări, sarcini și configurații de sincronizare separate. Butonul de gestionare a profilurilor va apărea în colțul din dreapta sus cînd este activat. Notă: Dezactivarea acestei funcții va ascunde interfața dar va păstra datele profilului. (Funcție experimentală. Fără garanții. Asigură-te că ai o copie de rezervă!)",
+ "USER_PROFILES_WARNING": "Funcție experimentală: Această funcție este încă în dezvoltare și ar putea fi eliminată sau modificată semnificativ într-o actualizare viitoare. Folosește cu grijă și asigură-te că ai copii de rezervă ale datelor tale.",
+ "EXPERIMENTAL_WARNING_TITLE": "Feature Experimental",
+ "EXPERIMENTAL_WARNING_MSG": "Această funcție este experimentală. Poate conține erori, poate fi modificată semnificativ sau poate fi eliminată într-o actualizare viitoare. Asigură-te că ai copii de rezervă ale datelor tale.
Vrei să o activezi?"
+ },
"AUTO_BACKUPS": {
"HELP": "Automatically save all data to your app folder to have it ready if something goes wrong.",
"LABEL_IS_ENABLED": "Enable automatic backups",
@@ -1995,8 +2021,8 @@
"SHOW_BANNER_THRESHOLD": "Show a notification X before the event (blank for disabled)"
},
"CLIPBOARD_IMAGES": {
- "DELETE": "Delete image",
- "DELETE_ALL": "Delete All",
+ "DELETE": "Șterge imaginea",
+ "DELETE_ALL": "Șterge-le pe toate",
"DELETE_ALL_SUCCESS": "Toate imaginile din clipboard șterse cu succes",
"DELETE_SUCCESS": "Imagine din clipboard ștearsă cu succes",
"ERROR_DELETING": "Ștergerea imaginii din clipboard a eșuat",
@@ -2013,14 +2039,14 @@
"PATH_LABEL": "Cale stocare imagini",
"PATH_PLACEHOLDER": "Lasă gol pentru locația implicită",
"PATH_TITLE": "Locație stocare",
- "SELECT_PATH": "Select Folder",
+ "SELECT_PATH": "Selectează dosarul",
"SELECT_PATH_TITLE": "Selectează dosarul pentru imagini clipboard",
- "SORT_BY": "Sort by",
- "SORT_LARGEST": "Largest first",
- "SORT_NEWEST": "Newest first",
- "SORT_OLDEST": "Oldest first",
- "SORT_SMALLEST": "Smallest first",
- "TITLE": "Imagini clipboard",
+ "SORT_BY": "Sortează după",
+ "SORT_LARGEST": "Cele mai mari primele",
+ "SORT_NEWEST": "Cele mai noi primele",
+ "SORT_OLDEST": "Cele mai vechi primele",
+ "SORT_SMALLEST": "Cele mai mici primele",
+ "TITLE": "Imagini Clipboard",
"TOTAL_IMAGES": "Total imagini",
"TOTAL_SIZE": "Dimensiune totală"
},
@@ -2149,6 +2175,7 @@
"TIME_LOCALE_IT_IT": "Italienesc: 24 de ore, DD/MM/YYYY",
"TIME_LOCALE_JA_JP": "Japonez: 24 de ore, YYYY/MM/DD",
"TIME_LOCALE_KO_KR": "Korean: 12 ore AM/PM, YYYY. MM. DD",
+ "TIME_LOCALE_PL_PL": "Polonez: 24 de ore, DD.MM.YYYY",
"TIME_LOCALE_PT_BR": "Portughez (Brazilia): 24 de ore, DD/MM/YYYY",
"TIME_LOCALE_RO_MD": "Românesc (Moldova): 24 de ore, DD.MM.YYYY",
"TIME_LOCALE_RO_RO": "Românesc: 24 de ore, DD.MM.YYYY",
@@ -2218,10 +2245,10 @@
"L_LUNCH_BREAK_START": "Lunch break start",
"L_WORK_END": "Work Day End",
"L_WORK_START": "Work Day Start",
- "LUNCH_BREAK_START_END_DESCRIPTION": "ex: 13:00",
+ "LUNCH_BREAK_START_END_DESCRIPTION": "d.e. 13:00",
"MONTH": "Lună",
"TITLE": "Programare",
- "WORK_START_END_DESCRIPTION": "ex: 17:00"
+ "WORK_START_END_DESCRIPTION": "d.e. 17:00"
},
"SHORT_SYNTAX": {
"HELP": "
Here you can control short syntax options when creating a task
",
@@ -2235,9 +2262,9 @@
"URL_BEHAVIOR_LABEL": "URL behavior when adding tasks"
},
"SOUND": {
- "BREAK_REMINDER_SOUND": "Take a break reminder sound",
- "DONE_SOUND": "Task completed sound",
- "IS_INCREASE_DONE_PITCH": "Increase pitch for every task completed",
+ "BREAK_REMINDER_SOUND": "Sunet memento de luat o pauză",
+ "DONE_SOUND": "Sunet sarcină finalizată",
+ "IS_INCREASE_DONE_PITCH": "Crește tonul pentru fiecare sarcină finalizată",
"TITLE": "Sunet",
"TRACK_TIME_SOUND": "Sunet memento de urmărire a timpului",
"VOLUME": "Volum"
@@ -2646,18 +2673,19 @@
"CREATE_NEW_PROFILE": "Creează un nou profil",
"CREATED": "Creat:",
"DELETE_PROFILE": "Șterge profilul",
+ "DEPRECATION_WARNING": "Funcția experimentală Profile utilizator va fi eliminată într-o actualizare viitoare. Te rog să îți exporti datele de profil ca backup.",
"DIALOG_TITLE": "Administrează profilurile de utilizator",
"EXISTING_PROFILES": "Profiluri existente",
"EXPORT_PROFILE": "Exportă profilul",
"MANAGE_PROFILES": "Gestionează profilurile...",
"PROFILE_NAME": "Nume profil",
- "PROFILE_NAME_PLACEHOLDER": "ex: Muncă, Personal",
+ "PROFILE_NAME_PLACEHOLDER": "d.e. Muncă, Personal",
"RENAME": "Redenumește",
"SAVE": "Salvează"
},
"V": {
"E_DATETIME": "Valoare întrodusă nu este o dată și oră!",
- "E_DURATION": "Te rog întrodu o durată validă (ex: 1h)",
+ "E_DURATION": "Te rog întrodu o durată validă (d.e. 1h)",
"E_MAX": "Nu trebuie să fie mai mare decît {{val}}",
"E_MAX_LENGTH": "Trebuie să aibă cel mult {{val}} caractere",
"E_MIN": "Nu trebuie să fie mai mică decît {{val}}",
diff --git a/src/assets/i18n/ro.json b/src/assets/i18n/ro.json
index f4a711e6c1..b3e0c67292 100644
--- a/src/assets/i18n/ro.json
+++ b/src/assets/i18n/ro.json
@@ -34,7 +34,7 @@
"BY": "după",
"NO_RESULTS": "Nicio imagine găsită. Te rog încearcă un alt termen de căutare.",
"SEARCH_LABEL": "Caută imagini",
- "SEARCH_PLACEHOLDER": "ex: natură, munți, abstract",
+ "SEARCH_PLACEHOLDER": "d.e. natură, munți, abstract",
"TITLE": "Alege o imagine de fundal din Unsplash"
},
"DONATE_PAGE": {
@@ -94,8 +94,8 @@
"ADD_NEW_PANEL": "Adaugă un nou panou",
"BACKLOG_TASK_FILTER_ALL": "Toate",
"BACKLOG_TASK_FILTER_NO_BACKLOG": "Exclude",
- "BACKLOG_TASK_FILTER_ONLY_BACKLOG": "Doar restante",
- "BACKLOG_TASK_FILTER_TYPE": "Sarcini restante",
+ "BACKLOG_TASK_FILTER_ONLY_BACKLOG": "Doar backlog",
+ "BACKLOG_TASK_FILTER_TYPE": "Sarcini din backlog",
"COLUMNS": "Coloane",
"TAGS_EXCLUDED": "Etichete excluse",
"TAGS_REQUIRED": "Etichete obligatorii",
@@ -167,8 +167,18 @@
"EVENT_STR": "eveniment",
"EVENTS_STR": "evenimente"
},
+ "CONTEXT_MENU": {
+ "CREATE_TASK": "Creează ca sarcină",
+ "OPEN_IN_CALENDAR": "Deschide în calendar",
+ "RESCHEDULE": "Replanifică",
+ "DELETE_EVENT": "Șterge evenimentul"
+ },
"S": {
- "CAL_PROVIDER_ERROR": "Eroare furnizor calendar: {{errTxt}}"
+ "CAL_PROVIDER_ERROR": "Eroare furnizor calendar: {{errTxt}}",
+ "EVENT_HIDDEN": "Eveniment calendar ascuns",
+ "EVENT_RESCHEDULED": "Eveniment reprogramat",
+ "EVENT_DELETED": "Eveniment șters",
+ "TIME_BLOCK_ERROR": "Nu s-a putut sincroniza blocul de timp: {{errTxt}}"
}
},
"CLIPBOARD_IMAGE": {
@@ -265,7 +275,7 @@
},
"GITEA": {
"FORM": {
- "HOST": "Gazdă (ex: https://try.gitea.io)",
+ "HOST": "Gazdă (d.e. https://try.gitea.io)",
"REPO_FULL_NAME": "Nume utilizator sau nume organizație/proiect",
"REPO_FULL_NAME_DESCRIPTION": "Poate fi găsit ca parte a URL-ului când vizualizezi proiectul într-un navigator web.",
"SCOPE": "Domeniu de aplicare",
@@ -303,7 +313,7 @@
"FILTER_USER": "Filtrează după numele de utilizator",
"GITLAB_BASE_URL": "URL de bază personalizat GitLab (opțional)",
"PROJECT": "nume utilizator/proiect",
- "PROJECT_HINT": "ex: super-productivity/super-productivity",
+ "PROJECT_HINT": "d.e. super-productivity/super-productivity",
"SCOPE": "Domeniu de aplicare",
"SCOPE_ALL": "Toate",
"SCOPE_ASSIGNED": "Atribuite mie",
@@ -421,8 +431,16 @@
"DIALOG": {
"ADVANCED_CONFIG": "Configurare avansată",
"CONNECTED": "Conectat",
- "DELETE_CONFIRM": "Dorești să ștergi acest furnizor de probleme? Ștergerea înseamnă că toate sarcinile de probleme importate anterior vor pierde referința. Aceasta nu poate fi anulată!",
- "DISCONNECT": "Deconectează"
+ "DELETE_CONFIRM": "Dorești să ștergi acest furnizor de probleme? Ștergerea înseamnă că toate sarcinile de probleme importate anterior vor pierde referința. Aceasta acțiune nu poate fi anulată!",
+ "DISCONNECT": "Deconectează",
+ "EDIT_TITLE": "Editează configurația {{title}}",
+ "LOAD_OPTIONS": "Încarcă opțiunile",
+ "LOAD_OPTIONS_FAILED": "Nu s-au putut încărca opțiunile",
+ "LOADING_OPTIONS": "Se încarcă opțiunile...",
+ "OPTIONS_LOADED": "Opțiuni încărcate",
+ "RELOAD_OPTIONS": "Reîncarcă opțiunile",
+ "SETUP_TITLE": "Editează configurația {{title}}",
+ "TEST_CONNECTION": "Testează conexiunea"
}
},
"ISSUE_PANEL": {
@@ -432,37 +450,37 @@
},
"JIRA": {
"BANNER": {
- "BLOCK_ACCESS_MSG": "Jira: To prevent API lock out, access has been blocked by Super Productivity. You probably should check your Jira settings!",
- "BLOCK_ACCESS_UNBLOCK": "Unblock"
+ "BLOCK_ACCESS_MSG": "Jira: Pentru a preveni blocarea API-ului, accesul a fost blocat de Super Productivity. Probabil ar trebui să verifici setările Jira!",
+ "BLOCK_ACCESS_UNBLOCK": "Deblochează"
},
"CFG_CMP": {
- "ALWAYS_ASK": "Always open dialog",
- "DO_NOT": "Don't transition",
- "DONE": "Status for completing task",
- "ENABLE": "Enable Jira integration",
- "ENABLE_TRANSITIONS": "Enable Transition Handling",
- "IN_PROGRESS": "Status for starting task",
- "LOAD_SUGGESTIONS": "Load Suggestions",
- "MAP_CUSTOM_FIELDS": "Load Story Points",
- "MAP_CUSTOM_FIELDS_INFO": "Unfortunately some of Jira's data is saved under custom fields which are different for every installation. To include this data, you need to select the proper custom field for it. Currently there is only the story points field that needs to be mapped.",
- "OPEN": "Status for pausing task",
- "SELECT_ISSUE_FOR_TRANSITIONS": "Select issue to load available transitions",
- "STORY_POINTS": "Story Points Field Name",
- "TRANSITION": "Transition Handling"
+ "ALWAYS_ASK": "Deschide mereu dialogul",
+ "DO_NOT": "Nu tranziționa",
+ "DONE": "Stare pentru finalizarea sarcinii",
+ "ENABLE": "Activează integrarea Jira",
+ "ENABLE_TRANSITIONS": "Activează gestionarea tranzițiilor",
+ "IN_PROGRESS": "Stare pentru începerea sarcinii",
+ "LOAD_SUGGESTIONS": "Încarcă sugestiile",
+ "MAP_CUSTOM_FIELDS": "Încarcă Story Points",
+ "MAP_CUSTOM_FIELDS_INFO": "Din păcate unele din datele Jira sunt salvate sub câmpuri particulare (custom fields) care sunt diferite pentru fiecare instalare. Pentru a include aceste date, trebuie să selectezi câmpul personalizat adecvat. Momentan, există doar câmpul pentru Story Points care trebuie asociat.",
+ "OPEN": "Stare pentru punerea pe pauză a sarcinii",
+ "SELECT_ISSUE_FOR_TRANSITIONS": "Selectează problema pentru a încărca tranzițiile disponibile",
+ "STORY_POINTS": "Nume câmp Story Points",
+ "TRANSITION": "Gestionarea tranzițiilor"
},
"DIALOG_CONFIRM_ASSIGNMENT": {
- "MSG": "{{summary}} is currently assigned to {{assignee}}. Do you want to assign it to yourself?",
- "OK": "Do it!"
+ "MSG": "{{summary}} Este asociat curent cu {{assignee}}. Dorești să îl asociezi pentru tine?",
+ "OK": "Fă-o!"
},
"DIALOG_INITIAL": {
- "TITLE": "Setup Jira for Project"
+ "TITLE": "Configurează Jira pentru proiect"
},
"DIALOG_TRANSITION": {
- "CHOOSE_STATUS": "Choose status to assign",
- "CURRENT_ASSIGNEE": "Current Assignee:",
- "CURRENT_STATUS": "Current Status:",
- "TASK_NAME": "Task name:",
- "TITLE": "Jira: Update Status"
+ "CHOOSE_STATUS": "Alege starea de atribuit",
+ "CURRENT_ASSIGNEE": "Atribuit curent:",
+ "CURRENT_STATUS": "Starea curentă:",
+ "TASK_NAME": "Nume sarcină:",
+ "TITLE": "Jira: Actualizează starea"
},
"DIALOG_WORKLOG": {
"CHECKBOXES": {
@@ -929,32 +947,32 @@
"TEXT": "
Orarul îți oferă o viziune de ansamblu despre cum se desfășoară sarcinile planificate în timp. Este generat automat din sarcinile tale și necesită doar estimări de timp pentru a funcționa.
Două lucruri sunt distinse: Sarcini programate, care sunt afișate la ora planificată și Sarcini obișnuite, care ar trebui să curgă în jurul acestor evenimente fixe.
Dacă setezi o oră de început și sfârșit a lucrului (recomandat), sarcinile obișnuite nu vor fi niciodată plasate în afara acestor limite.
",
"TITLE": "Orar"
},
- "END": "Work End",
- "INSERT_BEFORE": "Before",
- "LUNCH_BREAK": "Lunch Break",
- "MONTH": "Month",
- "NO_TASKS": "Currently there are no tasks. Please add some tasks via the plus (+) button in the top bar.",
- "NOW": "Now",
- "PLAN_END_DAY": "End of {{date}}",
- "PLAN_START_DAY": "Start of {{date}}",
- "SHIFT_KEY_INFO": "Hold Shift to toggle day planning mode",
- "START": "Work Start"
+ "END": "Finalul muncii",
+ "INSERT_BEFORE": "Înainte",
+ "LUNCH_BREAK": "Pauză de masă",
+ "MONTH": "Lună",
+ "NO_TASKS": "Momentan nu există sarcini. Adaugă câteva sarcini prin butonul plus (+) din bara de sus.",
+ "NOW": "Acum",
+ "PLAN_END_DAY": "Final de {{date}}",
+ "PLAN_START_DAY": "Început de {{date}}",
+ "SHIFT_KEY_INFO": "Ține apăsat Shift pentru a comuta modul de planificare zilnică",
+ "START": "Începerea muncii"
},
"SEARCH_BAR": {
- "INFO": "Start typing to search for tasks",
- "NO_RESULTS": "No tasks found matching your search",
- "PLACEHOLDER": "Search for task or task notes"
+ "INFO": "Începe să tastezi pentru a căuta sarcini",
+ "NO_RESULTS": "Nu s-au găsit sarcini care să se potrivească căutării",
+ "PLACEHOLDER": "Caută sarcină sau notițe despre sarcină"
},
"SIMPLE_COUNTER": {
"D_CONFIRM_REMOVE": {
- "MSG": "Deleting a simple counter, will also delete all past data tracked on it. Are you sure you want to proceed?",
- "OK": "Do it!"
+ "MSG": "Ștergerea unui contor simplu va șterge și toate datele urmărite pe el. Ești sigur că vrei să continui?",
+ "OK": "Fă-o!"
},
"D_EDIT": {
- "CURRENT_STREAK": "Current Streak",
- "DAILY_GOAL": "Daily Goal",
- "DAYS": "Days",
- "L_COUNTER": "Count"
+ "CURRENT_STREAK": "Serie curentă",
+ "DAILY_GOAL": "Obiectiv zilnic",
+ "DAYS": "Zile",
+ "L_COUNTER": "Număr"
},
"FORM": {
"ADD_NEW": "Adaugă un contor simplu sau un obicei",
@@ -967,7 +985,7 @@
"L_IS_HIDE_BUTTON": "Ascunde butonul din bara de unelte",
"L_STREAK_MODE": "Mod urmărire serie",
"L_STREAK_MODE_SPECIFIC_DAYS": "Zilele săptămânii",
- "L_STREAK_MODE_WEEKLY_FREQUENCY": "Frecvență săptămânală (ex: de 3 ori pe săptămână)",
+ "L_STREAK_MODE_WEEKLY_FREQUENCY": "Frecvență săptămânală (d.e. de 3 ori pe săptămână)",
"L_TITLE": "Titlu",
"L_TRACK_STREAKS": "Urmărește seriile",
"L_TYPE": "Tip",
@@ -1000,40 +1018,40 @@
"FORCE_UPLOAD": "Forțarea încărcării datelor poate duce la pierderea acestora. Ești sigur că vrei să continui?"
},
"D_AUTH_CODE": {
- "FOLLOW_LINK": "Please open the following link and copy the auth code provided there into the input field below.",
- "FOLLOW_LINK_MOBILE": "Please tap the button below to authorize. You will be automatically redirected back to the app.",
- "GET_AUTH_CODE": "Get Authorization Code",
- "L_AUTH_CODE": "Enter Auth Code",
- "TITLE": "Login: {{provider}}",
- "WAITING_FOR_AUTH": "Waiting for authentication..."
+ "FOLLOW_LINK": "Te rog deschide următoarea legătură și copiază codul de autorizare furnizat acolo în câmpul de intrare de mai jos.",
+ "FOLLOW_LINK_MOBILE": "Te rog apasă butonul de mai jos pentru a autoriza. Vei fi redirecționat automat înapoi la aplicație.",
+ "GET_AUTH_CODE": "Obține codul de autorizare",
+ "L_AUTH_CODE": "Introdu codul de autorizare",
+ "TITLE": "Autentificare: {{provider}}",
+ "WAITING_FOR_AUTH": "Se așteaptă autentificarea..."
},
"D_CONFLICT": {
- "ADDITIONAL_INFO": "Additional Info",
- "CHANGES": "Changes",
- "CHANGES_SINCE_LAST_SYNC": "Changes since last sync",
- "COMPARISON_RESULT": "Comparison Result",
- "DATE": "Date",
- "LAST_SYNC": "Last Sync:",
- "LAST_SYNCED": "Last Synced",
- "LAST_WRITE": "Last Write",
+ "ADDITIONAL_INFO": "Informații suplimentare",
+ "CHANGES": "Modificări",
+ "CHANGES_SINCE_LAST_SYNC": "Modificări de la ultima sincronizare",
+ "COMPARISON_RESULT": "Rezultatul comparării",
+ "DATE": "Dată",
+ "LAST_SYNC": "Ultima Sincronizare:",
+ "LAST_SYNCED": "Ultima sincronizare",
+ "LAST_WRITE": "Ultima scriere",
"LOCAL": "Local",
- "LOCAL_REMOTE": "Local -> Remote",
- "NEVER": "Never",
- "OVERWRITE_WARNING": "WARNING: The {{targetName}} data contains approximately {{targetChanges}} changes while the {{sourceName}} data only has {{sourceChanges}} changes. Are you sure you want to overwrite the {{targetName}} data with the {{sourceName}} version? This action cannot be undone.",
- "REMOTE": "Remote",
- "RESULT": "Result",
- "TEXT": "
Update from remote. Both local and remote data seem to be modified.
",
- "TIME": "Time",
- "TIMESTAMP": "Timestamp",
- "TITLE": "Sync: Conflicting Data",
- "USE_LOCAL": "Keep local",
- "USE_REMOTE": "Keep remote",
- "VECTOR_CLOCK": "Vector Clock",
- "VECTOR_CLOCK_HEADING": "Vector Clock",
- "VECTOR_COMPARISON_CONCURRENT": "Concurrent (True Conflict)",
- "VECTOR_COMPARISON_EQUAL": "Equal",
- "VECTOR_COMPARISON_LOCAL_GREATER": "Local > Remote",
- "VECTOR_COMPARISON_LOCAL_LESS": "Local < Remote"
+ "LOCAL_REMOTE": "Local -> Distant",
+ "NEVER": "Niciodată",
+ "OVERWRITE_WARNING": "ATENȚIE: Datele {{targetName}} conțin aproximativ {{targetChanges}} modificări în timp ce datele {{sourceName}} au doar {{sourceChanges}} modificări. Ești sigur că vrei să înlocuiești datele {{targetName}} cu versiunea {{sourceName}}? Această acțiune nu poate fi anulată.",
+ "REMOTE": "Distant",
+ "RESULT": "Rezultat",
+ "TEXT": "
Actualizează de la distanță. Atât datele locale cât și cele de pe server par modificate.
",
+ "TIME": "Timp",
+ "TIMESTAMP": "Marca temporală",
+ "TITLE": "Sincronizare: Date conflictuale",
+ "USE_LOCAL": "Păstrează local",
+ "USE_REMOTE": "Păstrează distant",
+ "VECTOR_CLOCK": "Ceas vectorial",
+ "VECTOR_CLOCK_HEADING": "Ceas vectorial",
+ "VECTOR_COMPARISON_CONCURRENT": "Concurent (conflict adevărat)",
+ "VECTOR_COMPARISON_EQUAL": "Egal",
+ "VECTOR_COMPARISON_LOCAL_GREATER": "Local > Distant",
+ "VECTOR_COMPARISON_LOCAL_LESS": "Local < Distant"
},
"D_DATA_REPAIR_CONFIRM": {
"MSG": "Datele tale par deteriorate. Dorești să încerci repararea automată? Aceasta poate duce la pierderea minoră de date.",
@@ -1045,28 +1063,28 @@
"UNKNOWN_ERROR": "Eroare necunoscută de sincronizare: {{err}}"
},
"D_DECRYPT_ERROR": {
- "BTN_OVER_WRITE_REMOTE": "Use This Password & Overwrite Remote",
- "CHANGE_PW_AND_DECRYPT": "Retry Decrypt",
- "P1": "Your remote data is encrypted and decryption failed.",
- "P2_FORGOT_PW": "If you forgot your password, enter the correct password and click \"Retry Decrypt\".",
- "P3_PW_CHANGED": "If you changed your password on another device, enter your new password and click \"Overwrite Remote\". This will upload your local data to the server.",
- "PASSWORD": "Password",
- "TITLE": "Decryption Failed"
+ "BTN_OVER_WRITE_REMOTE": "Folosește această parolă și suprascrie-o pe cea de la distanță",
+ "CHANGE_PW_AND_DECRYPT": "Reîncearcă decriptarea",
+ "P1": "Datele tale de la distanță sunt criptate și decriptarea a eșuat.",
+ "P2_FORGOT_PW": "Dacă ai uitat parola, introdu parola corectă și apasă \"Reîncearcă decriptarea\".",
+ "P3_PW_CHANGED": "Dacă ai schimbat parola pe un alt dispozitiv, introdu noua parolă și apasă \"Suprascrie datele de la distanță\". Aceasta va încărca datele locale criptate cu această parolă pe server.",
+ "PASSWORD": "Parolă",
+ "TITLE": "Decriptarea a eșuat"
},
"D_ENTER_PASSWORD": {
- "BTN_FORCE_OVERWRITE": "Use Local Data",
- "BTN_SAVE_AND_SYNC": "Save & Sync",
- "FORCE_OVERWRITE_CONFIRM": "This will delete all data on the server and upload your local data encrypted with this password. Continue?",
- "FORCE_OVERWRITE_INFO": "If you prefer to keep your local data, you can overwrite the server instead.",
- "FORCE_OVERWRITE_TITLE": "Overwrite Server Data?",
- "MESSAGE": "The sync server contains encrypted data, but no encryption password is configured on this device. Please enter your encryption password to decrypt and sync your data.",
- "PASSWORD_LABEL": "Encryption Password",
- "TITLE": "Encryption Password Required"
+ "BTN_FORCE_OVERWRITE": "Folosește datele locale",
+ "BTN_SAVE_AND_SYNC": "Salvează și sincronizează",
+ "FORCE_OVERWRITE_CONFIRM": "Acest lucru va șterge toate datele de pe server și va încărca datele locale criptate cu această parolă. Continui?",
+ "FORCE_OVERWRITE_INFO": "Dacă preferi să păstrezi datele locale, poți suprascrie serverul în schimb.",
+ "FORCE_OVERWRITE_TITLE": "Suprascrii datele de pe server?",
+ "MESSAGE": "Serverul de sincronizare conține date criptate, dar nu este configurată nicio parolă de criptare pe acest dispozitiv. Te rog să introduci parola de criptare pentru a decripta și sincroniza datele.",
+ "PASSWORD_LABEL": "Parolă de criptare",
+ "TITLE": "Parolă de criptare necesară"
},
"D_FRESH_CLIENT_CONFIRM": {
- "MESSAGE": "This appears to be a fresh installation. Remote data with {{count}} changes was found. Do you want to download and overwrite your local data with it?",
- "OK": "Download Remote Data",
- "TITLE": "Initial Sync"
+ "MESSAGE": "Aceasta pare să fie o instalare curată. Datele de la distanță cu {{count}} modificări au fost găsite. Dorești să descarci și să înlocuiești datele locale cu ele?",
+ "OK": "Descarcă datele de la distanță",
+ "TITLE": "Sincronizare inițială"
},
"D_INCOMPLETE_SYNC": {
"BTN_CLOSE_APP": "Închide aplicația",
@@ -1271,13 +1289,17 @@
"SETUP_ENCRYPTION_PASSWORD_WARNING": "Folosește aceeași parolă pe toate dispozitivele. O parolă uitată poate fi resetată prin suprascrierea datelor de la distanță.",
"SETUP_ENCRYPTION_TITLE": "SuperSync: Setare parolă"
},
+ "NEXTCLOUD": {
+ "L_SERVER_URL": "Nextcloud Server URL",
+ "SERVER_URL_DESCRIPTION": "e.g. https://cloud.example.com or https://example.com/nextcloud",
+ "L_APP_PASSWORD": "App Password",
+ "APP_PASSWORD_DESCRIPTION": "Go to Nextcloud → Settings → Security → App passwords to generate one"
+ },
"TITLE": "Sync",
"WEB_DAV": {
- "CONDITIONAL_HEADER_WARNING_MSG": "Your WebDAV server does not support conditional headers (If-Unmodified-Since). This means Super Productivity cannot detect concurrent changes from other devices during upload.
Sync will still work, but there is a small risk of data conflicts if you make changes on multiple devices simultaneously.",
- "CONDITIONAL_HEADER_WARNING_TITLE": "Reduced Sync Safety",
- "CORS_INFO": "Making it work in a web browser: Allow Super Productivity to make CORS requests to your WebDAV server. This can have negative security implications! For Nextcloud, please refer to this GitHub thread for more information. One approach to make this work on mobile is allowing \"https://app.super-productivity.com\" via the Nextcloud app webapppassword. Use at your own risk!",
+ "CORS_INFO": "Making it work in a web browser: You need to allow Super Productivity to make CORS requests to your server. For Nextcloud, one approach is allowing \"https://app.super-productivity.com\" via the webapppassword app. Refer to this GitHub thread for more information.",
"D_SYNC_FOLDER_PATH": "Path relative to the WebDAV server root where sync files will be stored (e.g. '/super-productivity' or '/'). This is NOT your server's internal directory path.",
- "INFO": "Warning: WebDAV implementations differ wildly. Super Productivity is known to work well with Nextcloud, but it may not work with your provider. Test thoroughly before relying on it.",
+ "INFO": "Warning: Generic WebDAV sync is provided as-is without support. If you are using Nextcloud, please use the Nextcloud sync option instead.",
"L_BASE_URL": "Base URL",
"L_PASSWORD": "Password",
"L_SYNC_FOLDER_PATH": "Sync Folder Path",
@@ -1315,6 +1337,8 @@
"ERROR_PERMISSION": "File access denied. Please check your filesystem permissions.",
"ERROR_PERMISSION_FLATPAK": "File access denied. Grant filesystem permission via Flatseal or use a path inside ~/.var/app/",
"ERROR_PERMISSION_SNAP": "File access denied. Run 'snap connect super-productivity:home' or use a path inside ~/snap/super-productivity/common/",
+ "ERROR_REMOTE_FILE_EMPTY": "Remote sync file is empty. This can happen after an interrupted sync. Overwrite remote with your local data?",
+ "ERROR_REMOTE_FILE_LOCKED": "Remote sync file is locked by the server. This may resolve on the next sync attempt.",
"ERROR_UNABLE_TO_READ_REMOTE_DATA": "Error while syncing. Unable to read remote data. Maybe you enabled encryption and your local password does not match the one used to encrypt the remote data?",
"FINISH_DAY_SYNC_ERROR": "Sync error while finishing day. Please try again.",
"FRESH_CLIENT_SYNC_CANCELLED": "Sincronizare inițială anulată. Datele de la distanță nu au fost aplicate.",
@@ -1405,11 +1429,11 @@
"TITLE": "Basic Settings"
},
"S": {
- "ARCHIVE_CLEANUP_FAILED": "Curățarea datelor arhivate pentru eticheta ștearsă a eșuat",
- "UPDATED": "Tag settings were updated"
+ "ARCHIVE_CLEANUP_FAILED": "A eșuat curățarea datelor arhivate pentru eticheta ștearsă",
+ "UPDATED": "Setările etichetei au fost actualizate"
},
"TTL": {
- "ADD_NEW_TAG": "Add new Tag"
+ "ADD_NEW_TAG": "Adaugă etichetă nouă"
}
},
"TAG_FOLDER": {
@@ -1489,47 +1513,47 @@
"ADD_SUB_TASK": "Adaugă subsarcină",
"ADD_TO_MY_DAY": "Adaugă la lista de azi",
"ADD_TO_PROJECT": "Adaugă la proiect",
- "CONVERT_TO_PARENT_TASK": "Convert to parent task",
- "DELETE": "Delete task",
- "DELETE_REPEAT_INSTANCE": "Delete recurring task instance",
- "DROP_ATTACHMENT": "Drop here to attach to \"{{title}}\"",
- "DUPLICATE": "Duplicate",
+ "CONVERT_TO_PARENT_TASK": "Convertește în sarcină părinte",
+ "DELETE": "Șterge sarcina",
+ "DELETE_REPEAT_INSTANCE": "Șterge instanța de sarcină recurentă",
+ "DROP_ATTACHMENT": "Plasează aici pentru a atașa la \"{{title}}\"",
+ "DUPLICATE": "Duplică",
"EDIT_DEADLINE": "Editează termen limită",
- "EDIT_SCHEDULED": "Reschedule",
- "FOCUS_SESSION": "Start focus session",
- "MARK_DONE": "Mark as completed",
- "MOVE_TO_BACKLOG": "Move to backlog",
- "MOVE_TO_OTHER_PROJECT": "Move to project",
- "MOVE_TO_REGULAR": "Move to regular list",
- "MOVE_TO_TOP": "Move to top",
- "OPEN_ATTACH": "Attach file or link",
- "OPEN_ISSUE": "Open in browser",
- "OPEN_TIME": "Time tracking",
+ "EDIT_SCHEDULED": "Planifică din nou",
+ "FOCUS_SESSION": "Începe sesiune de concentrare",
+ "MARK_DONE": "Marchează ca finalizată",
+ "MOVE_TO_BACKLOG": "Mută în backlog",
+ "MOVE_TO_OTHER_PROJECT": "Mută în proiect",
+ "MOVE_TO_REGULAR": "Mută în lista obișnuită",
+ "MOVE_TO_TOP": "Mută în partea de sus",
+ "OPEN_ATTACH": "Atașează fișier sau legătură",
+ "OPEN_ISSUE": "Deschide în navigatorul web",
+ "OPEN_TIME": "Contorizare timp",
"REMOVE_DEADLINE": "Șterge termen limită",
"REMOVE_FROM_MY_DAY": "Elimină din lista de azi",
- "SCHEDULE": "Schedule task",
+ "SCHEDULE": "Planifică sarcina",
"SET_DEADLINE": "Setează termen limită",
- "TOGGLE_ATTACHMENTS": "Show/hide attachments",
- "TOGGLE_DETAIL_PANEL": "Show/hide additional info",
- "TOGGLE_DONE": "Toggle completion status",
- "TOGGLE_SUB_TASK_VISIBILITY": "Toggle subtask visibility",
- "TOGGLE_TAGS": "Toggle tags",
- "TRACK_TIME": "Start tracking time",
- "TRACK_TIME_STOP": "Pause tracking time",
- "UNSCHEDULE_TASK": "Unschedule task",
- "UPDATE_ISSUE_DATA": "Update issue data"
+ "TOGGLE_ATTACHMENTS": "Arată/Ascunde atașamente",
+ "TOGGLE_DETAIL_PANEL": "Arată/Ascunde informații suplimentare",
+ "TOGGLE_DONE": "Comută starea de finalizare",
+ "TOGGLE_SUB_TASK_VISIBILITY": "Comută vizibilitatea subsarcinilor",
+ "TOGGLE_TAGS": "Comută etichetele",
+ "TRACK_TIME": "Începe contorizarea timpului",
+ "TRACK_TIME_STOP": "Pune pe pauză contorizarea timpului",
+ "UNSCHEDULE_TASK": "Șterge planificarea sarcinii",
+ "UPDATE_ISSUE_DATA": "Actualizează datele problemei"
},
"D_CONFIRM_DELETE": {
"MSG": "Dorești să ștergi sarcina \"{{title}}\"?",
- "OK": "Delete"
+ "OK": "Șterge"
},
"D_CONFIRM_SHORT_SYNTAX_NEW_TAG": {
- "MSG": "Do you want to create the new tag {{tagsTxt}}?",
- "OK": "Create tag"
+ "MSG": "DDorești să creezi eticheta nouă {{tagsTxt}}?",
+ "OK": "Creează etichetă"
},
"D_CONFIRM_SHORT_SYNTAX_NEW_TAGS": {
- "MSG": "Do you want to create the new tags {{tagsTxt}}?",
- "OK": "Create tags"
+ "MSG": "Dorești să creezi etichetele noi {{tagsTxt}}?",
+ "OK": "Creează etichete"
},
"D_DEADLINE": {
"ADD_TIME": "Adaugă ora",
@@ -1549,28 +1573,28 @@
"SET_DEADLINE": "Setează termen limită"
},
"D_REMINDER_VIEW": {
- "ADD_ALL_TO_TODAY": "Add all to Today",
- "ADD_TO_TODAY": "Add to Today",
+ "ADD_ALL_TO_TODAY": "Adaugă-le pe toate la Astăzi",
+ "ADD_TO_TODAY": "Adaugă la Astăzi",
"CLEAR_ALL_REMINDERS": "Șterge toate memento-urile",
"CLEAR_REMINDER": "Șterge memento",
- "COMPLETE": "Complete",
- "COMPLETE_ALL": "Complete all",
+ "COMPLETE": "Finalizează",
+ "COMPLETE_ALL": "Finalizează-le pe toate",
"DEADLINE_REMINDER": "Memento termen limită",
"DEADLINE_SECTION": "Termene limită",
- "DISMISS_ALL_REMINDERS_KEEP_TODAY": "Dismiss All Reminders (Keep in Today)",
- "DISMISS_REMINDER_KEEP_TODAY": "Dismiss Reminder (Keep in Today)",
- "DONE": "Done",
+ "DISMISS_ALL_REMINDERS_KEEP_TODAY": "Înlătură toate mementourile (păstrează în Astăzi)",
+ "DISMISS_REMINDER_KEEP_TODAY": "Înlătură memento (păstrează în Astăzi)",
+ "DONE": "Realizat",
"DUE_SECTION": "Planificate",
- "DUE_TASK": "Reminder for planned task",
- "DUE_TASKS": "Reminder for planned tasks",
- "RESCHEDULE_EDIT": "Edit (reschedule)",
- "RESCHEDULE_UNTIL_TOMORROW": "Reschedule for tomorrow",
- "SNOOZE": "Snooze",
- "SNOOZE_ALL": "Snooze all",
- "START": "Start",
- "TASK_REMINDERS": "Memento-uri sarcini",
- "UNSCHEDULE": "Unschedule",
- "UNSCHEDULE_ALL": "Unschedule all"
+ "DUE_TASK": "Memento sarcină planificată",
+ "DUE_TASKS": "Memento sarcini planificate",
+ "RESCHEDULE_EDIT": "Editează (planifică din nou)",
+ "RESCHEDULE_UNTIL_TOMORROW": "Planifică din nou pentru mâine",
+ "SNOOZE": "Amână",
+ "SNOOZE_ALL": "Amână-le pe toate",
+ "START": "Începe",
+ "TASK_REMINDERS": "Mementouri sarcini",
+ "UNSCHEDULE": "Șterge planificarea",
+ "UNSCHEDULE_ALL": "Șterge planificarea tuturor"
},
"D_SCHEDULE_TASK": {
"QA_NEXT_MONTH": "Schedule next month",
@@ -1589,9 +1613,9 @@
"UNSCHEDULE": "Unschedule"
},
"D_SELECT_DATE_AND_TIME": {
- "DATE": "Date",
- "TIME": "Time",
- "TITLE": "Select Date and Time"
+ "DATE": "Dată",
+ "TIME": "Oră",
+ "TITLE": "Alege data și ora"
},
"D_TIME": {
"ADD_FOR_OTHER_DAY": "Add time spent for other day",
@@ -1671,18 +1695,18 @@
"OK": "Remove completely"
},
"D_CONFIRM_UPDATE_INSTANCES": {
- "CANCEL": "Only future tasks",
- "MSG": "There are {{tasksNr}} instances created for this recurring task. Do you want to update all of them with the new defaults or just future tasks?",
- "OK": "Update all instances"
+ "CANCEL": "Doar sarcinile viitoare",
+ "MSG": "Există {{tasksNr}} instanțe create pentru această sarcină recurentă. Dorești să le actualizezi pe toate cu noile valori implicite sau doar să actualizezi sarcinile viitoare?",
+ "OK": "Actualizează toate instanțele"
},
"D_SKIP_INSTANCE": {
- "MSG": "Skip the recurring task on {{date}}? This will prevent the task from being created on this date only.",
+ "MSG": "Omiți sarcina recurentă pe {{date}}? Acest lucru va preveni crearea sarcinii doar pe această dată.",
"OK": "Omite"
},
"D_EDIT": {
- "ADD": "Add Recurring Task Config",
- "ADVANCED_CFG": "Advanced configuration",
- "EDIT": "Edit Recurring Task Config",
+ "ADD": "Adaugă configurație sarcină recurentă",
+ "ADVANCED_CFG": "Configurare avansată",
+ "EDIT": "Editează configurația sarcinii recurente",
"HEATMAP_LABEL": "Activitate",
"TAG_LABEL": "Etichete",
"TIME_SPENT_THIS_MONTH": "Luna aceasta",
@@ -1690,105 +1714,105 @@
"TIME_SPENT_TOTAL": "Total"
},
"F": {
- "C_DAY": "Day",
- "C_MONTH": "Month",
- "C_WEEK": "Week",
- "C_YEAR": "Year",
- "DEFAULT_ESTIMATE": "Default Estimate",
- "DISABLE_AUTO_UPDATE_SUBTASKS": "Disable auto-updating subtasks",
- "DISABLE_AUTO_UPDATE_SUBTASKS_DESCRIPTION": "Do not update inherited subtasks automatically when the newest instance changes",
- "FRIDAY": "Friday",
- "INHERIT_SUBTASKS": "Inherit subtasks",
- "INHERIT_SUBTASKS_DESCRIPTION": "When enabled, subtasks from the most recent task instance will be recreated with the recurring task",
- "MONDAY": "Monday",
- "NOTES": "Default notes",
- "Q_CUSTOM": "Custom recurring config",
- "Q_DAILY": "Every day",
- "Q_MONDAY_TO_FRIDAY": "Every Monday through Friday",
- "Q_MONTHLY_CURRENT_DATE": "Every month on the day {{dateDayStr}}",
+ "C_DAY": "Zi",
+ "C_MONTH": "Lună",
+ "C_WEEK": "Săptămână",
+ "C_YEAR": "An",
+ "DEFAULT_ESTIMATE": "Estimare implicită",
+ "DISABLE_AUTO_UPDATE_SUBTASKS": "Dezactivează auto-actualizarea subsarcinilor",
+ "DISABLE_AUTO_UPDATE_SUBTASKS_DESCRIPTION": "Nu actualiza automat subsarcinile moștenate când instanța cea mai nouă se modifică",
+ "FRIDAY": "vineri",
+ "INHERIT_SUBTASKS": "Moștenește subsarcini",
+ "INHERIT_SUBTASKS_DESCRIPTION": "Când este activat, subsarcinile din instanța cea mai recentă a sarcinii vor fi recreate cu sarcina recurentă",
+ "MONDAY": "luni",
+ "NOTES": "Notițe implicite",
+ "Q_CUSTOM": "Configurare recurentă personalizată",
+ "Q_DAILY": "În fiecare zi",
+ "Q_MONDAY_TO_FRIDAY": "În fiecare zi de luni până vineri",
+ "Q_MONTHLY_CURRENT_DATE": "În fiecare lună în ziua {{dateDayStr}}",
"Q_MONTHLY_FIRST_DAY": "În fiecare lună în prima zi",
"Q_MONTHLY_LAST_DAY": "În fiecare lună în ultima zi",
- "Q_WEEKLY_CURRENT_WEEKDAY": "Every week on {{weekdayStr}}",
- "Q_YEARLY_CURRENT_DATE": "Every year on the {{dayAndMonthStr}}",
- "QUICK_SETTING": "Recurring Config",
- "REMIND_AT": "Remind at",
- "REMIND_AT_PLACEHOLDER": "Select when to remind",
- "SKIP_FOR_DATE": "Skip for {{date}}",
- "SKIP_INSTANCE": "Skip for today",
- "REPEAT_CYCLE": "Recur cycle",
- "REPEAT_EVERY": "Recur every",
- "SATURDAY": "Saturday",
- "SCHEDULE_TYPE_LABEL": "Schedule type",
+ "Q_WEEKLY_CURRENT_WEEKDAY": "În fiecare săptămână {{weekdayStr}}",
+ "Q_YEARLY_CURRENT_DATE": "În fiecare an pe {{dayAndMonthStr}}",
+ "QUICK_SETTING": "Configurație recurentă",
+ "REMIND_AT": "Amintește la",
+ "REMIND_AT_PLACEHOLDER": "Selectează când să fie amintit",
+ "SKIP_FOR_DATE": "Omite pentru {{date}}",
+ "SKIP_INSTANCE": "Omite pentru astăzi",
+ "REPEAT_CYCLE": "Ciclu de repetare",
+ "REPEAT_EVERY": "Repetă la fiecare",
+ "SATURDAY": "sâmbătă",
+ "SCHEDULE_TYPE_LABEL": "Tip programare",
"SKIP_OVERDUE": "Omite instanțele întârziate",
"SKIP_OVERDUE_DESCRIPTION": "Când este activat, dacă aplicația este deschisă după ce a fost omisă o apariție programată (de ex. o sarcină săptămânală în care azi nu este o zi programată), instanța ratată va fi omisă silențios în loc să fie creată ca sarcină întârziată",
- "START_DATE": "Start date",
- "START_TIME": "Scheduled start time",
- "START_TIME_DESCRIPTION": "E.g. 15:00. Leave blank for an all day task",
- "SUNDAY": "Sunday",
- "THURSDAY": "Thursday",
- "TITLE": "Task title",
- "TUESDAY": "Tuesday",
- "WEDNESDAY": "Wednesday"
+ "START_DATE": "Dată de început",
+ "START_TIME": "Timpul programat de început",
+ "START_TIME_DESCRIPTION": "De ex. 15:00. Lasă necompletat pentru o sarcină pe toată ziua",
+ "SUNDAY": "duminică",
+ "THURSDAY": "joi",
+ "TITLE": "Nume sarcină",
+ "TUESDAY": "marți",
+ "WEDNESDAY": "miercuri"
},
"SNACK_REPEAT_DIALOG_FAIL": "Nu s-a putut deschide dialogul de configurare repetare. Puteți configura din meniul sarcinii."
},
"TASK_VIEW": {
"CUSTOMIZER": {
- "DEADLINE_NEXT_MONTH": "Due Next Month",
- "DEADLINE_NEXT_WEEK": "Due Next Week",
- "DEADLINE_THIS_MONTH": "Due This Month",
- "DEADLINE_THIS_WEEK": "Due This Week",
- "DEADLINE_TODAY": "Due Today",
- "DEADLINE_TOMORROW": "Due Tomorrow",
- "ENTER_PROJECT": "Filter Projects",
- "ENTER_TAG": "Filter Tag",
- "ESTIMATED_TIME": "Estimated Time",
- "FILTER_BY": "Filter By",
- "FILTER_DEADLINE": "Deadline",
- "FILTER_DEFAULT": "No Filter",
- "FILTER_ESTIMATED_TIME": "Estimated Time",
- "FILTER_NOT_SPECIFIED": "Not specified",
- "FILTER_PROJECT": "Project",
- "FILTER_SCHEDULED_DATE": "Scheduled Date",
- "FILTER_TAG": "Tag",
- "FILTER_TIME_SPENT": "Time Spent",
- "GROUP_BY": "Group By",
- "GROUP_DEADLINE": "Deadline",
- "GROUP_DEADLINE_NONE": "No deadline",
- "GROUP_DEFAULT": "No Group",
- "GROUP_PROJECT": "Project",
- "GROUP_SCHEDULED_DATE": "Scheduled Date",
- "GROUP_TAG": "Tag",
- "RESET_ALL": "Reset All",
+ "DEADLINE_NEXT_MONTH": "Scadente luna viitoare",
+ "DEADLINE_NEXT_WEEK": "Scadente săptămâna viitoare",
+ "DEADLINE_THIS_MONTH": "Scadente luna aceasta",
+ "DEADLINE_THIS_WEEK": "Scadente săptămâna aceasta",
+ "DEADLINE_TODAY": "Scadente astăzi",
+ "DEADLINE_TOMORROW": "Scadente mâine",
+ "ENTER_PROJECT": "Filtrează proiecte",
+ "ENTER_TAG": "Filtrează etichete",
+ "ESTIMATED_TIME": "Timp estimat",
+ "FILTER_BY": "Filtrează după",
+ "FILTER_DEADLINE": "Scadență",
+ "FILTER_DEFAULT": "Fără filtru",
+ "FILTER_ESTIMATED_TIME": "Timp estimat",
+ "FILTER_NOT_SPECIFIED": "Nu este specificat",
+ "FILTER_PROJECT": "Proiect",
+ "FILTER_SCHEDULED_DATE": "Dată programată",
+ "FILTER_TAG": "Etichetă",
+ "FILTER_TIME_SPENT": "Timp petrecut",
+ "GROUP_BY": "Grupare după",
+ "GROUP_DEADLINE": "Scadență",
+ "GROUP_DEADLINE_NONE": "Fără scadență",
+ "GROUP_DEFAULT": "Fără grup",
+ "GROUP_PROJECT": "Proiect",
+ "GROUP_SCHEDULED_DATE": "Dată programată",
+ "GROUP_TAG": "Etichetă",
+ "RESET_ALL": "Resetează tot",
"SAVE_SORT": "Salvează ca implicit",
- "SCHEDULED_NEXT_MONTH": "Next Month",
- "SCHEDULED_NEXT_WEEK": "Next Week",
- "SCHEDULED_THIS_MONTH": "This Month",
- "SCHEDULED_THIS_WEEK": "This Week",
+ "SCHEDULED_NEXT_MONTH": "Luna viitoare",
+ "SCHEDULED_NEXT_WEEK": "Săptămâna viitoare",
+ "SCHEDULED_THIS_MONTH": "Luna aceasta",
+ "SCHEDULED_THIS_WEEK": "Săptămâna aceasta",
"SCHEDULED_TODAY": "Astăzi",
"SCHEDULED_TOMORROW": "Mâine",
"SORT": "Sortare",
- "SORT_CREATION_DATE": "Creation Date",
- "SORT_DEADLINE": "Deadline",
- "SORT_DEFAULT": "Default",
- "SORT_NAME": "Name",
- "SORT_SCHEDULED_DATE": "Scheduled Date",
- "TIME_1HOUR": "> 1 Hour",
- "TIME_2HOUR": "> 2 Hours",
- "TIME_10MIN": "> 10 Minutes",
- "TIME_30MIN": "> 30 Minutes",
- "TIME_SPENT": "Time Spent"
+ "SORT_CREATION_DATE": "Dată de creare",
+ "SORT_DEADLINE": "Scadență",
+ "SORT_DEFAULT": "Implicit",
+ "SORT_NAME": "Nume",
+ "SORT_SCHEDULED_DATE": "Dată programată",
+ "TIME_1HOUR": "> 1 oră",
+ "TIME_2HOUR": "> 2 ore",
+ "TIME_10MIN": "> 10 minute",
+ "TIME_30MIN": "> 30 minute",
+ "TIME_SPENT": "Timp petrecut"
}
},
"TIME_TRACKING": {
"B": {
- "ALREADY_DID": "I already did",
- "SNOOZE": "Snooze {{time}}"
+ "ALREADY_DID": "Deja am făcut",
+ "SNOOZE": "Amână {{time}}"
},
"B_TTR": {
- "ADD_TO_TASK": "Add to Task",
- "MSG": "You have not been tracking time for {{time}}",
- "MSG_WITHOUT_TIME": "You have not been tracking time"
+ "ADD_TO_TASK": "Adaugă la sarcină",
+ "MSG": "Nu ai ținut evidența timpului pentru {{time}}",
+ "MSG_WITHOUT_TIME": "Nu ai ținut evidența timpului"
},
"D_ARCHIVE_COMPRESS": {
"BTN_COMPRESS": "Comprimă arhiva",
@@ -1928,10 +1952,10 @@
"DO_IT": "Fă-l!",
"DONT_SHOW_AGAIN": "Nu mai afișa din nou",
"DUPLICATE": "Duplică",
- "DURATION_DESCRIPTION": "ex: \"5h 23m\" care rezultă în 5 ore și 23 minute",
+ "DURATION_DESCRIPTION": "d.e. \"5h 23m\" care rezultă în 5 ore și 23 minute",
"EDIT": "Editează",
"ENABLED": "Activat",
- "EXAMPLE_VAL": "ex: 32m",
+ "EXAMPLE_VAL": "d.e. 32m",
"EXTENSION_INFO": "Te rog descarcă extensia Chrome pentru a permite comunicarea cu API-ul Jira și tratarea timpului mort. Acest lucru nu funcționează pe mobil. Fără extensie, aceste funcții nu vor funcționa!",
"HIDE": "Ascunde",
"ICON_INP_DESCRIPTION": "Toate emoji-urile UTF-8 sunt de asemenea suportate!",
@@ -1942,7 +1966,7 @@
"NEXT": "Următorul",
"NO_CON": "Momentan ești offline. Te rog reconectează-te la internet.",
"NONE": "Niciunul",
- "OK": "Ok",
+ "OK": "OK",
"OPEN_IN_BROWSER": "Deschide în navigatorul web",
"OVERDUE": "Restante",
"PASSWORD_STRENGTH_FAIR": "Acceptabilă",
@@ -1962,25 +1986,27 @@
},
"GCF": {
"APP_FEATURES": {
- "BOARDS": "Boards",
- "DONATE_PAGE": "Donate page",
+ "BOARDS": "Table",
+ "DONATE_PAGE": "Pagină de donații",
"FINISH_DAY": "Încheie ziua",
- "FOCUS_MODE": "Focus mode",
+ "FOCUS_MODE": "Mod concentrare",
"HABITS": "Obiceiuri",
- "HELP": "Enable or disable specific app features across the UI.",
- "ISSUES_PANEL": "Issues panel",
- "PLANNER": "Planner",
- "PROJECT_NOTES": "Project notes",
+ "HELP": "Activează sau dezactivează anumite funcții ale aplicației din interfață.",
+ "ISSUES_PANEL": "Panou probleme",
+ "PLANNER": "Planificator",
+ "PROJECT_NOTES": "Notițe proiect",
"SCHEDULE": "Orar",
- "SCHEDULE_DAY_PANEL": "Schedule day panel",
+ "SCHEDULE_DAY_PANEL": "Panou programare zi",
"SEARCH": "Caută",
- "SYNC_BUTTON": "Sync button",
- "TIME_TRACKING": "Stopwatch time tracking",
- "TITLE": "App Features",
- "USER_PROFILES": "User profiles (experimental)",
- "USER_PROFILES_HINT": "Allows you to create and switch between different user profiles, each with separate settings, tasks, and sync configurations. The profile management button will appear in the top-right corner when enabled. Note: Disabling this feature will hide the UI but preserve your profile data. (Experimental feature. No guarantees. Make sure to have a backup!)",
- "USER_PROFILES_WARNING": "Funcție experimentală: Această funcție este încă în dezvoltare și poate fi eliminată sau modificată semnificativ într-o actualizare viitoare. Folosește pe propriul risc și asigură-te că ai backup-uri ale datelor."
- },
+ "SYNC_BUTTON": "Buton sincronizare",
+ "TIME_TRACKING": "Cronometru urmărire timp",
+ "TITLE": "Funcții aplicație",
+ "USER_PROFILES": "Profiluri utilizator (experimental — ar putea fi eliminate)",
+ "USER_PROFILES_HINT": "Îți permite să creezi și să comuti între diferite profiluri de utilizator, fiecare cu setări, sarcini și configurații de sincronizare separate. Butonul de gestionare a profilurilor va apărea în colțul din dreapta sus când este activat. Notă: Dezactivarea acestei funcții va ascunde interfața dar va păstra datele profilului. (Funcție experimentală. Fără garanții. Asigură-te că ai o copie de rezervă!)",
+ "USER_PROFILES_WARNING": "Funcție experimentală: Această funcție este încă în dezvoltare și ar putea fi eliminată sau modificată semnificativ într-o actualizare viitoare. Folosește cu grijă și asigură-te că ai copii de rezervă ale datelor tale.",
+ "EXPERIMENTAL_WARNING_TITLE": "Feature Experimental",
+ "EXPERIMENTAL_WARNING_MSG": "Această funcție este experimentală. Poate conține erori, poate fi modificată semnificativ sau poate fi eliminată într-o actualizare viitoare. Asigură-te că ai copii de rezervă ale datelor tale.
Vrei să o activezi?"
+ },
"AUTO_BACKUPS": {
"HELP": "Automatically save all data to your app folder to have it ready if something goes wrong.",
"LABEL_IS_ENABLED": "Enable automatic backups",
@@ -1995,8 +2021,8 @@
"SHOW_BANNER_THRESHOLD": "Show a notification X before the event (blank for disabled)"
},
"CLIPBOARD_IMAGES": {
- "DELETE": "Delete image",
- "DELETE_ALL": "Delete All",
+ "DELETE": "Șterge imaginea",
+ "DELETE_ALL": "Șterge-le pe toate",
"DELETE_ALL_SUCCESS": "Toate imaginile din clipboard șterse cu succes",
"DELETE_SUCCESS": "Imagine din clipboard ștearsă cu succes",
"ERROR_DELETING": "Ștergerea imaginii din clipboard a eșuat",
@@ -2013,14 +2039,14 @@
"PATH_LABEL": "Cale stocare imagini",
"PATH_PLACEHOLDER": "Lasă gol pentru locația implicită",
"PATH_TITLE": "Locație stocare",
- "SELECT_PATH": "Select Folder",
+ "SELECT_PATH": "Selectează dosarul",
"SELECT_PATH_TITLE": "Selectează dosarul pentru imagini clipboard",
- "SORT_BY": "Sort by",
- "SORT_LARGEST": "Largest first",
- "SORT_NEWEST": "Newest first",
- "SORT_OLDEST": "Oldest first",
- "SORT_SMALLEST": "Smallest first",
- "TITLE": "Imagini clipboard",
+ "SORT_BY": "Sortează după",
+ "SORT_LARGEST": "Cele mai mari primele",
+ "SORT_NEWEST": "Cele mai noi primele",
+ "SORT_OLDEST": "Cele mai vechi primele",
+ "SORT_SMALLEST": "Cele mai mici primele",
+ "TITLE": "Imagini Clipboard",
"TOTAL_IMAGES": "Total imagini",
"TOTAL_SIZE": "Dimensiune totală"
},
@@ -2149,6 +2175,7 @@
"TIME_LOCALE_IT_IT": "Italienesc: 24 de ore, DD/MM/YYYY",
"TIME_LOCALE_JA_JP": "Japonez: 24 de ore, YYYY/MM/DD",
"TIME_LOCALE_KO_KR": "Korean: 12 ore AM/PM, YYYY. MM. DD",
+ "TIME_LOCALE_PL_PL": "Polonez: 24 de ore, DD.MM.YYYY",
"TIME_LOCALE_PT_BR": "Portughez (Brazilia): 24 de ore, DD/MM/YYYY",
"TIME_LOCALE_RO_MD": "Românesc (Moldova): 24 de ore, DD.MM.YYYY",
"TIME_LOCALE_RO_RO": "Românesc: 24 de ore, DD.MM.YYYY",
@@ -2218,10 +2245,10 @@
"L_LUNCH_BREAK_START": "Lunch break start",
"L_WORK_END": "Work Day End",
"L_WORK_START": "Work Day Start",
- "LUNCH_BREAK_START_END_DESCRIPTION": "ex: 13:00",
+ "LUNCH_BREAK_START_END_DESCRIPTION": "d.e. 13:00",
"MONTH": "Lună",
"TITLE": "Programare",
- "WORK_START_END_DESCRIPTION": "ex: 17:00"
+ "WORK_START_END_DESCRIPTION": "d.e. 17:00"
},
"SHORT_SYNTAX": {
"HELP": "
Here you can control short syntax options when creating a task
",
@@ -2235,9 +2262,9 @@
"URL_BEHAVIOR_LABEL": "URL behavior when adding tasks"
},
"SOUND": {
- "BREAK_REMINDER_SOUND": "Take a break reminder sound",
- "DONE_SOUND": "Task completed sound",
- "IS_INCREASE_DONE_PITCH": "Increase pitch for every task completed",
+ "BREAK_REMINDER_SOUND": "Sunet memento de luat o pauză",
+ "DONE_SOUND": "Sunet sarcină finalizată",
+ "IS_INCREASE_DONE_PITCH": "Crește tonul pentru fiecare sarcină finalizată",
"TITLE": "Sunet",
"TRACK_TIME_SOUND": "Sunet memento de urmărire a timpului",
"VOLUME": "Volum"
@@ -2646,18 +2673,19 @@
"CREATE_NEW_PROFILE": "Creează un nou profil",
"CREATED": "Creat:",
"DELETE_PROFILE": "Șterge profilul",
+ "DEPRECATION_WARNING": "Funcția experimentală Profile utilizator va fi eliminată într-o actualizare viitoare. Te rog să îți exporti datele de profil ca backup.",
"DIALOG_TITLE": "Administrează profilurile de utilizator",
"EXISTING_PROFILES": "Profiluri existente",
"EXPORT_PROFILE": "Exportă profilul",
"MANAGE_PROFILES": "Gestionează profilurile...",
"PROFILE_NAME": "Nume profil",
- "PROFILE_NAME_PLACEHOLDER": "ex: Muncă, Personal",
+ "PROFILE_NAME_PLACEHOLDER": "d.e. Muncă, Personal",
"RENAME": "Redenumește",
"SAVE": "Salvează"
},
"V": {
"E_DATETIME": "Valoare introdusă nu este o dată și oră!",
- "E_DURATION": "Te rog introdu o durată validă (ex: 1h)",
+ "E_DURATION": "Te rog introdu o durată validă (d.e. 1h)",
"E_MAX": "Nu trebuie să fie mai mare decât {{val}}",
"E_MAX_LENGTH": "Trebuie să aibă cel mult {{val}} caractere",
"E_MIN": "Nu trebuie să fie mai mică decât {{val}}",