diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md similarity index 75% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/BUG_REPORT.md index d362cd2e9..3e7a36961 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.md @@ -1,5 +1,13 @@ +--- +name: Bug Report +labels: bug +about: Report a bug you encountered +title: '' +assignees: '' +--- + +!!! Please search the issues before creating one !!! --> ### Your Environment @@ -9,12 +17,10 @@ Please search the issues before creating one. --> * Browser Name and version: ### Expected Behavior - - + ### Current Behavior - - + ### Steps to Reproduce (for bugs) diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md new file mode 100644 index 000000000..05ab70677 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md @@ -0,0 +1,32 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: Feature Request +assignees: '' +labels: enhancement +--- + + +## Problem Statement + + +## :grey_question: Possible Solution + +## :arrow_heading_up: Describe alternatives you've considered + + +## :heavy_plus_sign: Additional context + diff --git a/.github/workflows/build-create-windows-store-on-release.yml b/.github/workflows/build-create-windows-store-on-release.yml new file mode 100644 index 000000000..82f6997db --- /dev/null +++ b/.github/workflows/build-create-windows-store-on-release.yml @@ -0,0 +1,53 @@ +name: Win Store File on Release +on: + release: + types: [published] + workflow_dispatch: + inputs: null + +jobs: + windows-store-artifact: + runs-on: windows-latest + + if: "!github.event.release.prerelease" + + steps: + - name: Check out Git repository + uses: actions/checkout@v1 + + - name: Load Electron Builder Windows Store Config + run: echo $WIN_STORE_ELECTRON_BUILDER_YML | base64 --decode > electron-builder.yaml + shell: bash + env: + WIN_STORE_ELECTRON_BUILDER_YML: ${{secrets.WIN_STORE_ELECTRON_BUILDER_YML}} + + - uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install Yarn Packages + if: steps.yarn-cache.outputs.cache-hit != 'true' + run: yarn install + + - name: Lint + run: yarn lint + + - name: Test Unit + run: yarn test + + - name: Build Frontend & Electron + run: yarn build + + - name: Build/Release Electron app + uses: samuelmeuli/action-electron-builder@v1 + with: + build_script_name: empty + release: false + github_token: ${{ secrets.github_token }} + + - name: 'Upload Artifact' + uses: actions/upload-artifact@v2 + with: + name: WinStoreRelease + path: app-builds/*.appx diff --git a/.github/workflows/build-publish-to-mac-store-on-release.yml b/.github/workflows/build-publish-to-mac-store-on-release.yml new file mode 100644 index 000000000..ea7269bce --- /dev/null +++ b/.github/workflows/build-publish-to-mac-store-on-release.yml @@ -0,0 +1,62 @@ +name: Mac Store Release on Release +on: + release: + types: [ published ] + workflow_dispatch: + inputs: null + +jobs: + mac-store-release: + runs-on: macos-latest + + if: "!github.event.release.prerelease" + + steps: + - name: Check out Git repository + uses: actions/checkout@v1 + + - uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install Yarn Packages + if: steps.yarn-cache.outputs.cache-hit != 'true' + run: yarn install + + - run: 'echo "$PROVISION_PROFILE" | base64 --decode > embedded.provisionprofile' + shell: bash + env: + PROVISION_PROFILE: ${{secrets.mas_provision_profile}} + + - name: Lint + run: yarn lint + + - name: Test Unit + run: yarn test + +# - name: Test E2E +# run: yarn e2e + + - name: Build Frontend & Electron + run: yarn build + + - uses: apple-actions/import-codesign-certs@v1 + with: + p12-file-base64: ${{ secrets.mac_certs }} + p12-password: ${{ secrets.mac_certs_password }} + + - name: Build Electron app + run: yarn dist:mac:mas:buildOnly + + - name: Validate App + run: ls app-builds; ls app-builds/mas; xcrun altool --validate-app -f app-builds/mas/Super*.pkg -u ${{secrets.APPLEID}} -p ${{secrets.APPLEIDPASS}} + env: + APPLEID: ${{secrets.APPLEID}} + APPLEIDPASS: ${{secrets.APPLEIDPASS}} + + - name: Push to Store + run: xcrun altool --upload-app -f app-builds/mas/Super*.pkg -u ${{secrets.APPLEID}} -p ${{secrets.APPLEIDPASS}} + env: + APPLEID: ${{secrets.APPLEID}} + APPLEIDPASS: ${{secrets.APPLEIDPASS}} diff --git a/.github/workflows/build-update-web-app-on-release.yml b/.github/workflows/build-update-web-app-on-release.yml new file mode 100644 index 000000000..4f67b9c88 --- /dev/null +++ b/.github/workflows/build-update-web-app-on-release.yml @@ -0,0 +1,53 @@ +name: Web App on Release +on: + release: + types: [published] + workflow_dispatch: + inputs: null + +jobs: + upload-to-app-super-productivity: + runs-on: ubuntu-latest + + if: "!github.event.release.prerelease" + + steps: + - name: Check out Git repository + uses: actions/checkout@v1 + + - name: Install Node.js, NPM and Yarn + uses: actions/setup-node@v1 + with: + node-version: 12 + + - uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install Yarn Packages + if: steps.yarn-cache.outputs.cache-hit != 'true' + run: yarn install + + - name: Lint + run: yarn lint + + - name: Test Unit + run: yarn test + + - name: Test E2E + run: yarn e2e + + - name: Build Frontend & Electron + run: yarn buildFrontend:prodWeb + + - name: Deploy to Web Server + uses: easingthemes/ssh-deploy@v2.1.5 + env: + SSH_PRIVATE_KEY: ${{ secrets.WEB_SERVER_SSH_KEY }} + ARGS: "-rltgoDzvO --delete" + SOURCE: "dist/" + REMOTE_HOST: ${{ secrets.WEB_REMOTE_HOST }} + REMOTE_USER: ${{ secrets.WEB_REMOTE_USER }} + TARGET: ${{ secrets.WEB_REMOTE_TARGET }} + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..6e8524157 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,147 @@ +name: Build All & Release +on: + push: + branches: [ master, test/git-actions ] + tags: + - v* + + +jobs: + linux-bin-and-snap-release: + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v1 + + - uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install Yarn Packages + if: steps.yarn-cache.outputs.cache-hit != 'true' + run: yarn install + + - name: Lint + run: yarn lint + + - name: Test Unit + run: yarn test + + - name: Test E2E + run: yarn e2e + + - name: Build Frontend & Electron + run: yarn build + + - name: Build/Release Electron app + uses: samuelmeuli/action-electron-builder@v1 + with: + build_script_name: empty + github_token: ${{ secrets.github_token }} + release: ${{ startsWith(github.ref, 'refs/tags/v') }} + + - name: Install Snapcraft + uses: samuelmeuli/action-snapcraft@v1 + with: + # Log in to Snap Store + snapcraft_token: ${{ secrets.snapcraft_token }} + + # Release to edge if no tag and to candidate if tag + - run: snapcraft push app-builds/superProductivity*.snap --release edge + if: false == startsWith(github.ref, 'refs/tags/v') + - run: snapcraft push app-builds/superProductivity*.snap --release candidate + if: startsWith(github.ref, 'refs/tags/v') + + + mac-bin: + runs-on: macos-latest + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Echo is Release + run: echo "IS_RELEASE $IS_RELEASE, ${{ startsWith(github.ref, 'refs/tags/v') }}" + env: + IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/v') }} + + - name: Check out Git repository + uses: actions/checkout@v1 + + - uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install Yarn Packages + if: steps.yarn-cache.outputs.cache-hit != 'true' + run: yarn install + + - run: 'echo "$PROVISION_PROFILE" | base64 --decode > embedded.provisionprofile' + shell: bash + env: + PROVISION_PROFILE: ${{secrets.dl_provision_profile}} + + - name: Prepare for app notarization + # Import Apple API key for app notarization on macOS + run: | + mkdir -p ~/private_keys/ + echo '${{ secrets.mac_api_key }}' > ~/private_keys/AuthKey_${{ secrets.mac_api_key_id }}.p8 + + - name: Lint + run: yarn lint + + - name: Test Unit + run: yarn test + + - name: Test E2E + run: yarn e2e + + - name: Build Frontend & Electron + run: yarn build + + - name: Build/Release Electron app + uses: samuelmeuli/action-electron-builder@v1 + with: + build_script_name: empty + github_token: ${{ secrets.github_token }} + mac_certs: ${{ secrets.mac_certs }} + mac_certs_password: ${{ secrets.mac_certs_password }} + release: ${{ startsWith(github.ref, 'refs/tags/v') }} + env: + # macOS notarization API key + API_KEY_ID: ${{ secrets.mac_api_key_id }} + API_KEY_ISSUER_ID: ${{ secrets.mac_api_key_issuer_id }} + + windows-bin: + runs-on: windows-latest + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Check out Git repository + uses: actions/checkout@v1 + + - uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install Yarn Packages + if: steps.yarn-cache.outputs.cache-hit != 'true' + run: yarn install + + - name: Lint + run: yarn lint + + - name: Test Unit + run: yarn test + + - name: Build Frontend & Electron + run: yarn build + + - name: Build/Release Electron app + uses: samuelmeuli/action-electron-builder@v1 + with: + build_script_name: empty + github_token: ${{ secrets.github_token }} + release: ${{ startsWith(github.ref, 'refs/tags/v') }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..8698521ca --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +name: "CodeQL" + +on: + push: + branches: [master] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 3 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['javascript'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/lint-and-test-pr.yml b/.github/workflows/lint-and-test-pr.yml new file mode 100644 index 000000000..f4e24f2c9 --- /dev/null +++ b/.github/workflows/lint-and-test-pr.yml @@ -0,0 +1,31 @@ +name: "Lint & Test PRs" + +on: [pull_request] + +jobs: + test-on-linux: + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v1 + + - name: Install Node.js, NPM and Yarn + uses: actions/setup-node@v1 + with: + node-version: 12 + + - uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install Yarn Packages + if: steps.yarn-cache.outputs.cache-hit != 'true' + run: yarn install + + - run: yarn lint + - run: yarn test + - run: yarn e2e + + diff --git a/.gitignore b/.gitignore index 269267429..d096f8645 100644 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,5 @@ electron/**/*.js.map all-certs.p12 mac-cert.tar build/electron-builder.appx.yaml +electron-builder.win-store.yaml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 873d8d0b8..000000000 --- a/.travis.yml +++ /dev/null @@ -1,116 +0,0 @@ -sudo: required -language: node_js -node_js: '12' - -matrix: - include: - - os: osx - osx_image: xcode10 - addons: - chrome: stable - before_install: - - openssl aes-256-cbc -K $encrypted_527645209bb0_key -iv $encrypted_527645209bb0_iv -in build/mac-cert.tar.enc -out mac-cert.tar -d - - tar xvf mac-cert.tar - - - os: linux - dist: xenial - language: generic - services: - - docker -# before_install: -# - sudo snap install snapcraft --classic -# - export CHROME_BIN=chromium-browser -# - export DISPLAY=:99.0 -# - sh -e /etc/init.d/xvfb start - -addons: - ssh_known_hosts: - - the-front-end.de - - frs.sourceforge.net - apt: - packages: - - sshpass - -env: - global: - - ELECTRON_CACHE=$HOME/.cache/electron - - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder - -cache: - yarn: true - directories: - - node_modules - - $HOME/.cache/electron - - $HOME/.cache/electron-builder - - $HOME/.npm/_prebuilds - -script: - - | - if [ -n "$TRAVIS_TAG" ]; then - PUB=always - else - PUB=never - fi - if [ "$TRAVIS_OS_NAME" == "linux" ]; then - echo '____RUNNING DOCKER____' - bash ${TRAVIS_BUILD_DIR}/build/docker/run-linux-win.sh ${PUB} - else - yarn install && yarn add 7zip-bin-mac - yarn run buildAllElectron:noTests - travis_wait 30 yarn dist:mac:dl -p $PUB - fi - -# NOTE: disabled until next release of electron builder -# if [ -n "$TRAVIS_TAG" ]; then -# yarn dist:mac:mas -# fi - - -deploy: - # Snap Deployment - - on: - condition: $TRAVIS_OS_NAME = linux - tags: false - branch: master - provider: snap - snap: ./app-builds/*.snap - channel: edge - skip_cleanup: true - # we use edge because of this: https://travis-ci.community/t/snap-deployment-stopped-working-out-of-nowhere/6908/2 - edge: true - - on: - condition: $TRAVIS_OS_NAME = linux - tags: true - branch: master - provider: snap - snap: ./app-builds/*.snap - channel: candidate - skip_cleanup: true - # we use edge because of this: https://travis-ci.community/t/snap-deployment-stopped-working-out-of-nowhere/6908/2 - edge: true - # Web deployment - - on: - condition: $TRAVIS_OS_NAME = linux - tags: true - branch: master - provider: script - script: sshpass -p "$DEPLOY_PASS" rsync -avz ./dist $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH - skip_cleanup: true -# NOTE: disabled until next release of electron builder and until electron works with osx again -# - on: -# branch: master -# condition: $TRAVIS_OS_NAME = osx -# tags: true -# provider: script -# script: xcrun altool --upload-app -f app-builds/mas/superProductivity-*.pkg -u "${APPLEID}" -p "${APPLEIDPASS}" -# skip_cleanup: true - - -# NOTE: this would only work, if we could and wanted to auto publish the github draft -# - provider: script -# script: brew update && brew install vitorgalvao/tiny-scripts/cask-repair && openssl aes-256-cbc -K $encrypted_c04542ca1075_key -iv $encrypted_c04542ca1075_iv -in build/hub.enc -out ~/.config/hub -d && cask-repair -b -v $TRAVIS_TAG superproductivity -# skip_cleanup: true -# on: -# branch: master -# condition: $TRAVIS_OS_NAME = osx -# tags: true diff --git a/CHANGELOG.md b/CHANGELOG.md index e68cf8a87..edbdfad39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,125 @@ +## [5.9.15](https://github.com/johannesjo/super-productivity/compare/v5.9.14...v5.9.15) (2020-10-09) + + +### Bug Fixes + +* app not closing on windows for some people [#567](https://github.com/johannesjo/super-productivity/issues/567) ([5e340ad](https://github.com/johannesjo/super-productivity/commit/5e340aded20258533767bc87b54b4405c46ad5d1)) +* make tests work again ([bc227bc](https://github.com/johannesjo/super-productivity/commit/bc227bcae42cd12d2fb250182a88d8df98028b02)) +* prevent adding tags via short syntax for child tasks [#568](https://github.com/johannesjo/super-productivity/issues/568) ([94f8147](https://github.com/johannesjo/super-productivity/commit/94f814716174e97c2b28e481a8e90f778c3eb72b)) +* special case [#568](https://github.com/johannesjo/super-productivity/issues/568) ([c26f62b](https://github.com/johannesjo/super-productivity/commit/c26f62b0ae72c80ca4930073094baebd1c3959e3)) +* text overlap ([39508dc](https://github.com/johannesjo/super-productivity/commit/39508dc2f26f4228b4721d5fcaa255b702585088)) +* throw error for inconsistent sub task data [#568](https://github.com/johannesjo/super-productivity/issues/568) ([786235b](https://github.com/johannesjo/super-productivity/commit/786235b9e5ed936adf43aac7f8d9a995fb3cb558)) + + +### Features + +* write auto repair for wrongly archived sub tasks ([81f076a](https://github.com/johannesjo/super-productivity/commit/81f076aefb543b131cfb87e3e8b7696094b14fd2)) +* write auto repair for wrongly unarchived archived sub tasks [#568](https://github.com/johannesjo/super-productivity/issues/568) ([609941d](https://github.com/johannesjo/super-productivity/commit/609941ddebf83ac4a2040159d8087d75316f0716)) + + + +## [5.9.14](https://github.com/johannesjo/super-productivity/compare/v5.9.13...v5.9.14) (2020-10-08) + + +### Bug Fixes + +* about for mas... ([cdc7687](https://github.com/johannesjo/super-productivity/commit/cdc76876bc4508e9f01e20deb63a2719f1374190)) + + + +## [5.9.13](https://github.com/johannesjo/super-productivity/compare/v5.9.12...v5.9.13) (2020-10-08) + + + +## [5.9.12](https://github.com/johannesjo/super-productivity/compare/v5.9.11...v5.9.12) (2020-10-08) + + + +## [5.9.11](https://github.com/johannesjo/super-productivity/compare/v5.9.10...v5.9.11) (2020-10-08) + + +### Bug Fixes + +* not in project context [#545](https://github.com/johannesjo/super-productivity/issues/545) ([a293f2f](https://github.com/johannesjo/super-productivity/commit/a293f2f736d7a61d87cc792b669864b7c2f5c5e8)) + + +### Features + +* **autoBackupRestore:** add translations [#553](https://github.com/johannesjo/super-productivity/issues/553) ([cfeed8d](https://github.com/johannesjo/super-productivity/commit/cfeed8de1048c26e69b65ec1b7689a9f8eebb720)) +* **autoBackupRestore:** implement most basic variant – circular dependency [#553](https://github.com/johannesjo/super-productivity/issues/553) ([bdd3cbc](https://github.com/johannesjo/super-productivity/commit/bdd3cbc49649a6e8d6e7dee613932d9fd4dbe688)) +* **autoBackupRestore:** make it work [#553](https://github.com/johannesjo/super-productivity/issues/553) ([a54711d](https://github.com/johannesjo/super-productivity/commit/a54711d9651d19a0f15b66f0e2f6b4d97f7a3e83)) +* **autoBackupRestore:** outline [#553](https://github.com/johannesjo/super-productivity/issues/553) ([7d00652](https://github.com/johannesjo/super-productivity/commit/7d00652eb9212a80b4a48c487f91623a41fdd11d)) +* **i18n:** add norwegian ([a512c94](https://github.com/johannesjo/super-productivity/commit/a512c94584ddaa529d0a0a198fc8d9e7a58fc98d)) +* **task:** make startable tasks work better ([ada4c97](https://github.com/johannesjo/super-productivity/commit/ada4c97db9fb7075419f43d845b4e33606dbeba2)) + + + +## [5.9.10](https://github.com/johannesjo/super-productivity/compare/v5.9.2...v5.9.10) (2020-10-04) + + +### Features + +* set first day of the week [#528](https://github.com/johannesjo/super-productivity/issues/528) ([b83bfea](https://github.com/johannesjo/super-productivity/commit/b83bfeafc6fd1c8258da9b7a040815f578d2dbc7)) +* set first day of week in DateAdapter ([6b9c5e7](https://github.com/johannesjo/super-productivity/commit/6b9c5e72692367cbb50f51e0e2a8fd94572be0d4)) + + + +## [5.9.9](https://github.com/johannesjo/super-productivity/compare/v5.9.8...v5.9.9) (2020-10-04) + + + +## [5.9.8](https://github.com/johannesjo/super-productivity/compare/v5.9.7...v5.9.8) (2020-10-04) + + + +## [5.9.7](https://github.com/johannesjo/super-productivity/compare/v5.9.6...v5.9.7) (2020-10-04) + + + +## [5.9.6](https://github.com/johannesjo/super-productivity/compare/v5.9.5...v5.9.6) (2020-10-04) + + + +## [5.9.5](https://github.com/johannesjo/super-productivity/compare/v5.9.4...v5.9.5) (2020-10-03) + + + +## [5.9.4](https://github.com/johannesjo/super-productivity/compare/v5.9.3...v5.9.4) (2020-10-03) + + + +## [5.9.3](https://github.com/johannesjo/super-productivity/compare/v0.0.1...v5.9.3) (2020-10-03) + + + +## [5.9.2](https://github.com/johannesjo/super-productivity/compare/v5.9.1...v5.9.2) (2020-10-02) + + + +## [5.9.1](https://github.com/johannesjo/super-productivity/compare/v5.9.0...v5.9.1) (2020-10-02) + + +### Bug Fixes + +* allow to display images from file system [#549](https://github.com/johannesjo/super-productivity/issues/549) ([2c8255b](https://github.com/johannesjo/super-productivity/commit/2c8255b0814e03623048ac52cd960cf7b3710999)) +* chrome extension link ([58be356](https://github.com/johannesjo/super-productivity/commit/58be3561c4b1a9695f7bc75b01baf747dc54734a)) +* lint ([b1b8796](https://github.com/johannesjo/super-productivity/commit/b1b87960d7b394784d9bafa92cd84021cec64f7c)) +* **jira:** wrong issue link for auto-imported issues [#551](https://github.com/johannesjo/super-productivity/issues/551) ([ca7cb4b](https://github.com/johannesjo/super-productivity/commit/ca7cb4bf5c702664ea83feba9c60265d2ad48b4d)) +* **task:** only start first startable when there is none already running ([c96b846](https://github.com/johannesjo/super-productivity/commit/c96b846377807441500ea99565106190c8a6a8e3)) +* lint error ([a8d371e](https://github.com/johannesjo/super-productivity/commit/a8d371ed53552c3e354d4bfa9bcc839d15f3856b)) + + +### Features + +* **startTrackingReminder:** auto hide on idle and when starting to track on a task manually ([a7e41d9](https://github.com/johannesjo/super-productivity/commit/a7e41d937655671986d080a62f8f202cedf4a138)) +* **startTrackingReminder:** hide on mobile per default and make configurable ([b2b210b](https://github.com/johannesjo/super-productivity/commit/b2b210bfe54c04e92b34b47bc25d1816241d7978)) +* add new productivity tips ([a53878a](https://github.com/johannesjo/super-productivity/commit/a53878ab3176e06617f4665039ec163a0a60d56c)) +* improve broken data handling [#555](https://github.com/johannesjo/super-productivity/issues/555) ([c9c2e0c](https://github.com/johannesjo/super-productivity/commit/c9c2e0cad56383fd69f3414d266aec7f6c44189d)) +* improve data repair handling [#552](https://github.com/johannesjo/super-productivity/issues/552) ([1012edb](https://github.com/johannesjo/super-productivity/commit/1012edbd42f8bb99217437843e8a455d7b729dfb)) +* make AppDataForProjects non optional ([8b1a9cf](https://github.com/johannesjo/super-productivity/commit/8b1a9cf79c01f5e876ec2d972e23ecebdac60c0d)) + + + # [5.9.0](https://github.com/johannesjo/super-productivity/compare/v5.8.2...v5.9.0) (2020-09-24) diff --git a/angular.json b/angular.json index 0b19cdd0f..f682a550b 100644 --- a/angular.json +++ b/angular.json @@ -66,7 +66,7 @@ "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, - "serviceWorker": true + "serviceWorker": false }, "productionWeb": { "baseHref": "", @@ -94,6 +94,30 @@ "serviceWorker": true }, "stage": { + "budgets": [ + { + "type": "anyComponentStyle", + "maximumWarning": "6kb" + } + ], + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": true, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "serviceWorker": false + }, + "stageWeb": { "budgets": [ { "type": "anyComponentStyle", diff --git a/build/docker/Dockerfile b/build/docker/Dockerfile deleted file mode 100644 index a4ba07061..000000000 --- a/build/docker/Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -FROM electronuserland/builder:wine - -ENV DEBIAN_FRONTEND=noninteractive - -# install google chrome -RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ - echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list && \ - apt-get update -y && apt-get install -y --no-install-recommends xvfb google-chrome-stable libgconf-2-4 && \ - # clean - apt-get clean && rm -rf /var/lib/apt/lists/* - -# replace shell with bash so we can source files -RUN rm /bin/sh && ln -s /bin/bash /bin/sh - -# add yarn repo -RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - -RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list - -# install yarn and clean up -RUN apt-get update && apt-get install -y yarn && apt-get clean && rm -rf /var/lib/apt/lists/* - -ENV NVM_DIR /usr/local/nvm -ENV NODE_VERSION 12.14.1 - -RUN mkdir $NVM_DIR -p - -# install nvm with node and npm -RUN curl -sS -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.2/install.sh | bash - -# install node and npm -RUN source $NVM_DIR/nvm.sh \ - && nvm install $NODE_VERSION \ - && nvm alias default $NODE_VERSION \ - && nvm use default - -# add node and npm to path so the commands are available -ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules -ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH - -# confirm installation -RUN node -v -RUN yarn -v diff --git a/build/docker/run-linux-win.sh b/build/docker/run-linux-win.sh deleted file mode 100644 index 30177e302..000000000 --- a/build/docker/run-linux-win.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -PWD2=$PWD -# read as arg -PUB=$1 - -cd "$(dirname "$0")" - -echo PUB $PUB -google-chrome --version - -docker run $ENVS --rm \ - $(env | \ - grep -Eo '^[^\s=]*(_TOKEN|_KEY)[^\s=]*' | \ - sed '/^$/d;s/^/-e /' | \ - paste -sd ' ' \ - ) \ - -v ${PWD2}:/project \ - -v ~/.cache/electron:/root/.cache/electron \ - -v ~/.cache/electron-builder:/root/.cache/electron-builder \ - $(docker image build -q .) \ - /bin/bash -c "echo '____DOCKER_INNER_START____' && echo $PUB && node -v && npm -v && yarn -v && ls -l && yarn --link-duplicates --pure-lockfile && yarn run dist:linuxAndWin -p ${PUB} && ls -l ./dist" diff --git a/build/electron-builder.mas-dev.yaml b/build/electron-builder.mas-dev.yaml index 73b919a7e..1f1cd4d14 100644 --- a/build/electron-builder.mas-dev.yaml +++ b/build/electron-builder.mas-dev.yaml @@ -1,4 +1,5 @@ appId: com.super-productivity.app +productName: Super Productivity files: - electron/**/* - "!electron/**/*.ts" diff --git a/build/electron-builder.mas.yaml b/build/electron-builder.mas.yaml index 56c0e0a14..3d4055526 100644 --- a/build/electron-builder.mas.yaml +++ b/build/electron-builder.mas.yaml @@ -1,5 +1,6 @@ appId: com.super-productivity.app -#afterSign: build/scripts/notarize.js +afterSign: electron-builder-notarize +productName: Super Productivity files: - electron/**/* - "!electron/**/*.ts" diff --git a/build/entitlements.mas.plist b/build/entitlements.mas.plist index 65e40f863..38da9a976 100644 --- a/build/entitlements.mas.plist +++ b/build/entitlements.mas.plist @@ -8,7 +8,5 @@ com.apple.security.files.user-selected.read-write - com.apple.security.cs.allow-unsigned-executable-memory - diff --git a/build/mac-cert.tar.enc b/build/mac-cert.tar.enc deleted file mode 100644 index 368f019ac..000000000 Binary files a/build/mac-cert.tar.enc and /dev/null differ diff --git a/build/scripts/notarize.js b/build/scripts/notarize.js deleted file mode 100644 index 6042d6112..000000000 --- a/build/scripts/notarize.js +++ /dev/null @@ -1,33 +0,0 @@ -require('dotenv').config(); -const {notarize} = require('electron-notarize'); -const fs = require('fs'); - -exports.default = async function notarizing(context) { - const {electronPlatformName, appOutDir} = context; - if (electronPlatformName !== 'darwin') { - return; - } - - const appName = context.packager.appInfo.productFilename; - const appBundleId = context.packager.appInfo.macBundleIdentifier; - const appPath = `${appOutDir}/${appName}.app`; - if (!fs.existsSync(appPath)) { - throw new Error(`Cannot find application at: ${appPath}`); - } - - try { - let envBefore = process.env.DEBUG; - process.env.DEBUG = 'electron-notarize'; - await notarize({ - appBundleId, - appPath, - appleId: process.env.APPLEID, - appleIdPassword: process.env.APPLEIDPASS, - }); - process.env.DEBUG = envBefore; - } catch (e) { - console.error(e); - throw new Error(e); - } - console.log(`Notarizing DONE`); -}; diff --git a/electron-builder.yaml b/electron-builder.yaml index 4fed96d44..d6a92692b 100644 --- a/electron-builder.yaml +++ b/electron-builder.yaml @@ -1,5 +1,5 @@ appId: superProductivity -afterSign: build/scripts/notarize.js +afterSign: electron-builder-notarize files: - electron/**/* - "!electron/**/*.ts" diff --git a/electron/backup.ts b/electron/backup.ts index 61f19fc7e..0a334bf31 100644 --- a/electron/backup.ts +++ b/electron/backup.ts @@ -1,10 +1,49 @@ -import { app } from 'electron'; -import { existsSync, mkdirSync, writeFileSync } from 'fs'; +import { app, ipcMain } from 'electron'; +import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs'; +import { IPC } from './ipc-events.const'; +import { answerRenderer } from './better-ipc'; +import { LocalBackupMeta } from '../src/app/imex/local-backup/local-backup.model'; +import * as path from 'path'; -export const BACKUP_DIR = `${app.getPath('userData')}/backups`; -console.log('Saving backups to', BACKUP_DIR); +let BACKUP_DIR: string = `${app.getPath('userData')}/backups`; -export function backupData(ev, data) { +export function initBackupAdapter(backupDir: string) { + BACKUP_DIR = backupDir; + console.log('Saving backups to', BACKUP_DIR); + + // BACKUP + ipcMain.on(IPC.BACKUP, backupData); + + // IS_BACKUP_AVAILABLE + answerRenderer(IPC.BACKUP_IS_AVAILABLE, (): LocalBackupMeta | false => { + if (!existsSync(BACKUP_DIR)) { + return false; + } + + const files = readdirSync(BACKUP_DIR); + if (!files.length) { + return false; + } + const filesWithMeta: LocalBackupMeta[] = files.map((fileName: string): LocalBackupMeta => ({ + name: fileName, + path: path.join(BACKUP_DIR, fileName), + folder: BACKUP_DIR, + created: statSync(path.join(BACKUP_DIR, fileName)).mtime.getTime() + })); + + filesWithMeta.sort((a: LocalBackupMeta, b: LocalBackupMeta) => a.created - b.created); + console.log('Avilable Backup Files: ', filesWithMeta); + return filesWithMeta.reverse()[0]; + }); + + // RESTORE_BACKUP + answerRenderer(IPC.BACKUP_LOAD_DATA, (backupPath): string => { + console.log('Reading backup file: ', backupPath); + return readFileSync(backupPath, {encoding: 'utf8'}); + }); +} + +function backupData(ev, data) { if (!existsSync(BACKUP_DIR)) { mkdirSync(BACKUP_DIR); } diff --git a/electron/better-ipc.ts b/electron/better-ipc.ts new file mode 100644 index 000000000..921252e71 --- /dev/null +++ b/electron/better-ipc.ts @@ -0,0 +1,55 @@ +import { BrowserWindow, ipcMain, IpcMainEvent } from 'electron'; + +// TODO make available for both +const getSendChannel = channel => `%better-ipc-send-channel-${channel}`; + +// TODO add all typing +export const answerRenderer = (browserWindowOrChannel, channelOrCallback, callbackOrNothing?) => { + let window; + let channel; + let callback; + + if (callbackOrNothing === undefined) { + channel = browserWindowOrChannel; + callback = channelOrCallback; + } else { + window = browserWindowOrChannel; + channel = channelOrCallback; + callback = callbackOrNothing; + + if (!window) { + throw new Error('Browser window required'); + } + } + + const sendChannel = getSendChannel(channel); + + const listener = async (event: IpcMainEvent, data) => { + const browserWindow: BrowserWindow | null = BrowserWindow.fromWebContents(event.sender); + + if (window && window.id !== browserWindow?.id) { + return; + } + + const send = (channelI, dataI) => { + if (!(browserWindow && browserWindow.isDestroyed())) { + event.sender.send(channelI, dataI); + } + }; + + const {dataChannel, errorChannel, userData} = data; + + try { + send(dataChannel, await callback(userData, browserWindow)); + } catch (error) { + // send(errorChannel, serializeError(error)); + send(errorChannel, error); + } + }; + + ipcMain.on(sendChannel, listener); + + return () => { + ipcMain.off(sendChannel, listener); + }; +}; diff --git a/electron/ipc-events.const.ts b/electron/ipc-events.const.ts index 972976845..8d8a1e180 100644 --- a/electron/ipc-events.const.ts +++ b/electron/ipc-events.const.ts @@ -35,6 +35,9 @@ export enum IPC { EXEC = 'EXEC', BACKUP = 'BACKUP_APP_DATA', + BACKUP_IS_AVAILABLE = 'BACKUP_IS_AVAILABLE', + BACKUP_LOAD_DATA = 'BACKUP_LOAD_DATA', + SET_PROGRESS_BAR = 'SET_PROGRESS_BAR', NOTIFY_ON_CLOSE = 'NOTIFY_ON_CLOSE', diff --git a/electron/main-window.ts b/electron/main-window.ts index 79e087887..976da1c3d 100644 --- a/electron/main-window.ts +++ b/electron/main-window.ts @@ -150,7 +150,7 @@ function createMenu(quitApp) { const menuTpl = [{ label: 'Application', submenu: [ - {label: 'About Application', selector: 'orderFrontStandardAboutPanel:'}, + {label: 'About Super Productivity', selector: 'orderFrontStandardAboutPanel:'}, {type: 'separator'}, { label: 'Quit', click: quitApp @@ -207,7 +207,7 @@ const appCloseHandler = ( } }); - mainWin.on('close', (event) => { + app.on('window-all-closed', (event) => { // NOTE: this might not work if we run a second instance of the app console.log('close, isQuiting:', (app as any).isQuiting); if (!(app as any).isQuiting) { diff --git a/electron/main.ts b/electron/main.ts index b30689f50..e6f332441 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,5 +1,5 @@ 'use strict'; -import { App, app, BrowserWindow, globalShortcut, ipcMain, powerMonitor } from 'electron'; +import { App, app, BrowserWindow, globalShortcut, ipcMain, powerMonitor, protocol } from 'electron'; import * as electronDl from 'electron-dl'; import { info } from 'electron-log'; @@ -14,7 +14,7 @@ import { initGoogleAuth } from './google-auth'; import { errorHandler } from './error-handler'; import { initDebug } from './debug'; import { IPC } from './ipc-events.const'; -import { backupData } from './backup'; +import { initBackupAdapter } from './backup'; import { JiraCfg } from '../src/app/features/issue/providers/jira/jira.model'; import { KeyboardConfig } from '../src/app/features/config/global-config.model'; import lockscreen from './lockscreen'; @@ -51,6 +51,7 @@ process.argv.forEach((val) => { isShowDevTools = true; } }); +const BACKUP_DIR = `${app.getPath('userData')}/backups`; interface MyApp extends App { isQuiting?: boolean; @@ -100,6 +101,7 @@ appIN.on('certificate-error', (event, webContents, url, error, certificate, call // ------------------- appIN.on('ready', createMainWin); appIN.on('ready', createIndicator); +appIN.on('ready', () => initBackupAdapter(BACKUP_DIR)); appIN.on('activate', () => { // On OS X it's common to re-create a window in the app when the @@ -156,6 +158,11 @@ appIN.on('ready', () => { sendIdleMsgIfOverMin(Date.now() - suspendStart); mainWin.webContents.send(IPC.RESUME); }); + + protocol.registerFileProtocol('file', (request, callback) => { + const pathname = decodeURI(request.url.replace('file:///', '')); + callback(pathname); + }); }); appIN.on('will-quit', () => { @@ -190,8 +197,6 @@ ipcMain.on(IPC.SHUTDOWN_NOW, quitAppNow); ipcMain.on(IPC.EXEC, exec); -ipcMain.on(IPC.BACKUP, backupData); - ipcMain.on(IPC.LOCK_SCREEN, () => { if (isLocked) { return; diff --git a/package.json b/package.json index 42c6ba080..bf5acc1ef 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "superProductivity", - "version": "5.9.0", + "version": "5.9.15", "description": "Personal Task Management App to help you with your daily struggle with JIRA etc.", "main": "./electron/main.js", - "author": "johannesjo (http://super-productivity.com)", + "author": "Johannes Millan (http://super-productivity.com)", "license": "MIT", "homepage": "http://super-productivity.com", "repository": { @@ -23,12 +23,14 @@ "startFrontend": "ng serve", "serveProd": "node --max_old_space_size=4096 ./node_modules/@angular/cli/bin/ng serve --prod", "buildFrontend:prod": "node --max_old_space_size=4096 ./node_modules/@angular/cli/bin/ng build --aot --prod", + "buildFrontend:prodWeb": "node --max_old_space_size=4096 ./node_modules/@angular/cli/bin/ng build --aot --prod --configuration productionWeb", "buildFrontend:stage": "node --max_old_space_size=4096 ./node_modules/@angular/cli/bin/ng build --aot --prod --configuration stage", + "buildFrontend:stageWeb": "node --max_old_space_size=4096 ./node_modules/@angular/cli/bin/ng build --aot --prod --configuration stageWeb", "buildFrontend:prod:watch": "node --max_old_space_size=4096 ./node_modules/@angular/cli/bin/ng build --aot --prod --watch", "buildAllElectron:prod": "yarn preCheck && yarn buildFrontend:prod && yarn electron:build", "buildAllElectron:stage": "yarn preCheck && yarn buildFrontend:stage && yarn electron:build", - "buildAllElectron:noTests": "yarn lint && yarn buildFrontend:prod && yarn electron:build", - "buildApp": "yarn preCheck && yarn buildAllElectron:prod && yarn electron-builder", + "buildAllElectron:noTests:prod": "yarn lint && yarn buildFrontend:prod && yarn electron:build", + "build": "yarn buildAllElectron:noTests:prod", "test": "ng test --watch=false", "test:watch": "ng test --browsers ChromeHeadless", "lint": "ng lint", @@ -40,21 +42,23 @@ "electron:build": "tsc -p electron/tsconfig.electron.json", "electron:watch": "tsc -p electron/tsconfig.electron.json --watch", "electronBuilderOnly": "electron-builder", + "empty": "echo 'EMPTY YEAH'", "pack": "electron-builder --dir", "localInstall": "sudo echo 'Starting local install' && rm -Rf ./dist/ && rm -Rf ./app-builds/ && yarn buildAllElectron:stage && electron-builder --linux deb && sudo dpkg -i app-builds/superProductivity*.deb", "localInstall:prod": "sudo echo 'Starting local install' && rm -Rf ./dist/ && rm -Rf ./app-builds/ && yarn buildAllElectron:prod && electron-builder --linux deb && sudo dpkg -i app-builds/superProductivity*.deb", "localInstall:quick": "sudo echo 'Starting local install' && rm -Rf ./dist/ && rm -Rf ./app-builds/ && yarn buildFrontend:stage && yarn electron:build && electron-builder --linux deb && sudo dpkg -i app-builds/superProductivity*.deb", - "localInstall:mac": "sudo echo 'Starting local install. Don`t forget APPLEID & APPLEIDPASS !!' && yarn buildAllElectron:prod && sudo echo '' && electron-builder && sudo cp -rf app-builds/mac/superProductivity.app/ /Applications/superProductivity.app", + "localInstall:mac": "sudo echo 'Starting local install. Don`t forget APPLEID & APPLEIDPASS !!' && yarn buildAllElectron:noTests:prod && sudo echo '' && electron-builder && sudo cp -rf app-builds/mac/Super\\ Productivity.app/ /Applications/superProductivity.app", "dist": "yarn buildAllElectron:prod && electron-builder", "dist:only": "electron-builder", "dist:linuxAndWin": "yarn buildAllElectron:prod && electron-builder --linux --win", - "dist:win": "yarn buildAllElectron:noTests && electron-builder --win", + "dist:win": "yarn buildAllElectron:noTests:prod && electron-builder --win", "dist:win:only": "electron-builder --win", "dist:win:appx": "yarn buildAllElectron:prod && electron-builder --win --config=build/electron-builder.appx.yaml", - "dist:win:store": "git stash && git pull && yarn && git stash pop && yarn dist:win", - "dist:mac:dl": "cp dl.provisionprofile embedded.provisionprofile && electron-builder --mac", - "dist:mac:mas": "cp mas.provisionprofile embedded.provisionprofile; electron-builder --mac mas --config=build/electron-builder.mas.yaml", - "dist:mac:mas:dev": "cp mas-dev.provisionprofile embedded.provisionprofile; electron-builder --mac mas-dev --config=build/electron-builder.mas-dev.yaml", + "dist:win:store": "git pull && yarn && copy electron-builder.win-store.yaml electron-builder.yaml && yarn dist:win && git checkout electron-builder.yaml || git checkout electron-builder.yaml", + "dist:mac:dl": "cp tools/mac-profiles/dl.provisionprofile embedded.provisionprofile && electron-builder --mac", + "dist:mac:mas": "cp tools/mac-profiles/mas.provisionprofile embedded.provisionprofile; electron-builder --mac mas --config=build/electron-builder.mas.yaml", + "dist:mac:mas:buildOnly": "electron-builder --mac mas --config=build/electron-builder.mas.yaml", + "dist:mac:mas:dev": "cp tools/mac-profiles/mas-dev.provisionprofile embedded.provisionprofile; electron-builder --mac mas-dev --config=build/electron-builder.mas-dev.yaml", "release": "yarn release.changelog && yarn dist", "release.changelog": "conventional-changelog -i CHANGELOG.md -s -p angular", "version": "yarn release.changelog && git add -A", @@ -133,6 +137,7 @@ "cross-env": "^7.0.2", "electron": "^10.1.2", "electron-builder": "^22.7.0", + "electron-builder-notarize": "^1.2.0", "electron-notarize": "^1.0.0", "electron-reload": "^1.2.5", "file-saver": "^2.0.2", diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ab8719d09..c45a99e5b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -40,6 +40,7 @@ import { environment } from '../environments/environment'; import { RouterOutlet } from '@angular/router'; import { ipcRenderer } from 'electron'; import { TrackingReminderService } from './features/time-tracking/tracking-reminder/tracking-reminder.service'; +import { first } from 'rxjs/operators'; const w = window as any; const productivityTip: string[] = w.productivityTips && w.productivityTips[w.randomIndex]; @@ -160,8 +161,11 @@ export class AppComponent implements OnDestroy { ev.preventDefault(); } - @HostListener('document:paste', ['$event']) onPaste(ev: ClipboardEvent) { - this._bookmarkService.createFromPaste(ev); + @HostListener('document:paste', ['$event']) + async onPaste(ev: ClipboardEvent) { + if (await this.workContextService.isActiveWorkContextProject$.pipe(first()).toPromise()) { + this._bookmarkService.createFromPaste(ev); + } } @HostListener('window:beforeinstallprompt', ['$event']) onBeforeInstallPrompt(e: any) { diff --git a/src/app/app.constants.ts b/src/app/app.constants.ts index 12236dba1..99208be2c 100644 --- a/src/app/app.constants.ts +++ b/src/app/app.constants.ts @@ -52,6 +52,7 @@ export enum LanguageCode { it = 'it', pt = 'pt', nl = 'nl', + nb = 'nb', } export enum LanguageCodeMomentMap { @@ -68,6 +69,7 @@ export enum LanguageCodeMomentMap { it = 'it', pt = 'pt', nl = 'nl', + nb = 'nb', zh = 'zh-cn', } diff --git a/src/app/core/data-init/data-init.service.ts b/src/app/core/data-init/data-init.service.ts index c88c2720c..3c3b79186 100644 --- a/src/app/core/data-init/data-init.service.ts +++ b/src/app/core/data-init/data-init.service.ts @@ -54,7 +54,7 @@ export class DataInitService { if (isValid) { this._store$.dispatch(loadAllData({appDataComplete, isOmitTokens})); } else { - if (this._dataRepairService.isRepairConfirmed()) { + if (this._dataRepairService.isRepairPossibleAndConfirmed(appDataComplete)) { const fixedData = this._dataRepairService.repairData(appDataComplete); this._store$.dispatch(loadAllData({ appDataComplete: fixedData, @@ -63,4 +63,5 @@ export class DataInitService { } } } + } diff --git a/src/app/core/data-repair/data-repair.service.ts b/src/app/core/data-repair/data-repair.service.ts index c236e3b1c..0f27ed53a 100644 --- a/src/app/core/data-repair/data-repair.service.ts +++ b/src/app/core/data-repair/data-repair.service.ts @@ -3,6 +3,7 @@ import { AppDataComplete } from '../../imex/sync/sync.model'; import { T } from '../../t.const'; import { TranslateService } from '@ngx-translate/core'; import { dataRepair } from './data-repair.util'; +import { isDataRepairPossible } from './is-data-repair-possible.util'; @Injectable({ providedIn: 'root' @@ -18,7 +19,11 @@ export class DataRepairService { return dataRepair(dataIn); } - isRepairConfirmed(): boolean { + isRepairPossibleAndConfirmed(dataIn: AppDataComplete): boolean { + if (!isDataRepairPossible(dataIn)) { + alert('Data damaged, repair not possible.'); + return false; + } return confirm(this._translateService.instant(T.CONFIRM.AUTO_FIX)); } } diff --git a/src/app/core/data-repair/data-repair.util.spec.ts b/src/app/core/data-repair/data-repair.util.spec.ts index 5693d5362..94488bba2 100644 --- a/src/app/core/data-repair/data-repair.util.spec.ts +++ b/src/app/core/data-repair/data-repair.util.spec.ts @@ -31,7 +31,7 @@ describe('dataRepair()', () => { id: 'TEST', title: 'TEST', }]), - })).toEqual({ + } as any)).toEqual({ ...mock, task: taskState, taskArchive: { @@ -404,4 +404,125 @@ describe('dataRepair()', () => { } }); }); + + it('should move archived sub tasks back to their unarchived parents', () => { + const taskStateBefore = { + ...mock.task, + ...fakeEntityStateFromArray([{ + ...DEFAULT_TASK, + id: 'subTaskUnarchived', + title: 'subTaskUnarchived', + parentId: 'parent', + }, { + ...DEFAULT_TASK, + id: 'parent', + title: 'parent', + parentId: null, + subTaskIds: ['subTaskUnarchived'] + }]) + } as any; + + const taskArchiveStateBefore = { + ...mock.taskArchive, + ...fakeEntityStateFromArray([{ + ...DEFAULT_TASK, + id: 'subTaskArchived', + title: 'subTaskArchived', + parentId: 'parent', + }]) + } as any; + + expect(dataRepair({ + ...mock, + task: taskStateBefore, + taskArchive: taskArchiveStateBefore, + })).toEqual({ + ...mock, + task: { + ...mock.task, + ...fakeEntityStateFromArray([{ + ...DEFAULT_TASK, + id: 'subTaskUnarchived', + title: 'subTaskUnarchived', + parentId: 'parent', + }, { + ...DEFAULT_TASK, + id: 'parent', + title: 'parent', + parentId: null, + subTaskIds: ['subTaskUnarchived', 'subTaskArchived'], + }, { + ...DEFAULT_TASK, + id: 'subTaskArchived', + title: 'subTaskArchived', + parentId: 'parent', + }]) + } as any, + taskArchive: { + ...mock.taskArchive, + ...fakeEntityStateFromArray([]) + } as any, + }); + }); + + it('should move unarchived sub tasks to their archived parents', () => { + const taskStateBefore = { + ...mock.task, + ...fakeEntityStateFromArray([{ + ...DEFAULT_TASK, + id: 'subTaskUnarchived', + title: 'subTaskUnarchived', + parentId: 'parent', + }]) + } as any; + + const taskArchiveStateBefore = { + ...mock.taskArchive, + ...fakeEntityStateFromArray([{ + ...DEFAULT_TASK, + id: 'subTaskArchived', + title: 'subTaskArchived', + parentId: 'parent', + }, { + ...DEFAULT_TASK, + id: 'parent', + title: 'parent', + parentId: null, + subTaskIds: ['subTaskArchived'] + }]) + } as any; + + expect(dataRepair({ + ...mock, + task: taskStateBefore, + taskArchive: taskArchiveStateBefore, + })).toEqual({ + ...mock, + task: { + ...mock.task, + ...fakeEntityStateFromArray([]) + } as any, + taskArchive: { + ...mock.taskArchive, + ...fakeEntityStateFromArray([{ + ...DEFAULT_TASK, + id: 'subTaskArchived', + title: 'subTaskArchived', + parentId: 'parent', + }, { + ...DEFAULT_TASK, + id: 'parent', + title: 'parent', + parentId: null, + subTaskIds: ['subTaskArchived', 'subTaskUnarchived'] + }, { + ...DEFAULT_TASK, + id: 'subTaskUnarchived', + title: 'subTaskUnarchived', + parentId: 'parent', + }]) + } as any, + }); + }); + }); diff --git a/src/app/core/data-repair/data-repair.util.ts b/src/app/core/data-repair/data-repair.util.ts index c003bd8b5..fda9fa550 100644 --- a/src/app/core/data-repair/data-repair.util.ts +++ b/src/app/core/data-repair/data-repair.util.ts @@ -1,17 +1,27 @@ import { AppBaseDataEntityLikeStates, AppDataComplete } from '../../imex/sync/sync.model'; import { TagCopy } from '../../features/tag/tag.model'; import { ProjectCopy } from '../../features/project/project.model'; +import { isDataRepairPossible } from './is-data-repair-possible.util'; +import { TaskArchive, TaskCopy, TaskState } from '../../features/tasks/task.model'; +import { unique } from '../../util/unique'; const ENTITY_STATE_KEYS: (keyof AppDataComplete)[] = ['task', 'taskArchive', 'taskRepeatCfg', 'tag', 'project', 'simpleCounter']; export const dataRepair = (data: AppDataComplete): AppDataComplete => { + if (!isDataRepairPossible(data)) { + throw new Error('Data repair attempted but not possible'); + } + // console.time('dataRepair'); let dataOut: AppDataComplete = data; // let dataOut: AppDataComplete = dirtyDeepCopy(data); dataOut = _fixEntityStates(dataOut); dataOut = _removeMissingTasksFromListsOrRestoreFromArchive(dataOut); - dataOut = _removeDuplicatesFromArchive(dataOut); dataOut = _addOrphanedTasksToProjectLists(dataOut); + dataOut = _moveArchivedSubTasksToUnarchivedParents(dataOut); + dataOut = _moveUnArchivedSubTasksToArchivedParents(dataOut); + dataOut = _removeDuplicatesFromArchive(dataOut); + // console.timeEnd('dataRepair'); return dataOut; }; @@ -43,6 +53,89 @@ const _removeDuplicatesFromArchive = (data: AppDataComplete): AppDataComplete => return data; }; +const _moveArchivedSubTasksToUnarchivedParents = (data: AppDataComplete): AppDataComplete => { + // to avoid ambiguity + const taskState: TaskState = data.task; + const taskArchiveState: TaskArchive = data.taskArchive; + const orhphanedArchivedSubTasks: TaskCopy[] = taskArchiveState.ids + .map((id: string) => taskArchiveState.entities[id] as TaskCopy) + .filter((t: TaskCopy) => t.parentId && !taskArchiveState.ids.includes(t.parentId)); + + console.log('orhphanedArchivedSubTasks', orhphanedArchivedSubTasks); + orhphanedArchivedSubTasks.forEach((t: TaskCopy) => { + // delete archived if duplicate + if (taskState.ids.includes(t.id as string)) { + taskArchiveState.ids = taskArchiveState.ids.filter(id => t.id !== id); + delete taskArchiveState.entities[t.id]; + // if entity is empty for some reason + if (!taskState.entities[t.id]) { + taskState.entities[t.id] = t; + } + } + // copy to today if parent exists + else if (taskState.ids.includes(t.parentId as string)) { + taskState.ids.push((t.id)); + taskState.entities[t.id] = t; + const par: TaskCopy = taskState.entities[t.parentId as string] as TaskCopy; + + par.subTaskIds = unique([...par.subTaskIds, t.id]); + + // and delete from archive + taskArchiveState.ids = taskArchiveState.ids.filter(id => t.id !== id); + + delete taskArchiveState.entities[t.id]; + } + // make main if it doesn't + else { + // @ts-ignore + t.parentId = null; + } + }); + + return data; +}; + +const _moveUnArchivedSubTasksToArchivedParents = (data: AppDataComplete): AppDataComplete => { + // to avoid ambiguity + const taskState: TaskState = data.task; + const taskArchiveState: TaskArchive = data.taskArchive; + const orhphanedUnArchivedSubTasks: TaskCopy[] = taskState.ids + .map((id: string) => taskState.entities[id] as TaskCopy) + .filter((t: TaskCopy) => t.parentId && !taskState.ids.includes(t.parentId)); + + console.log('orhphanedUnArchivedSubTasks', orhphanedUnArchivedSubTasks); + orhphanedUnArchivedSubTasks.forEach((t: TaskCopy) => { + // delete un-archived if duplicate + if (taskArchiveState.ids.includes(t.id as string)) { + taskState.ids = taskState.ids.filter(id => t.id !== id); + delete taskState.entities[t.id]; + // if entity is empty for some reason + if (!taskArchiveState.entities[t.id]) { + taskArchiveState.entities[t.id] = t; + } + } + // copy to archive if parent exists + else if (taskArchiveState.ids.includes(t.parentId as string)) { + taskArchiveState.ids.push((t.id)); + taskArchiveState.entities[t.id] = t; + + const par: TaskCopy = taskArchiveState.entities[t.parentId as string] as TaskCopy; + par.subTaskIds = unique([...par.subTaskIds, t.id]); + + // and delete from today + taskState.ids = taskState.ids.filter(id => t.id !== id); + delete taskState.entities[t.id]; + } + // make main if it doesn't + else { + // @ts-ignore + t.parentId = null; + } + }); + + return data; +}; + const _removeMissingTasksFromListsOrRestoreFromArchive = (data: AppDataComplete): AppDataComplete => { const {task, project, tag, taskArchive} = data; const taskIds: string[] = task.ids; diff --git a/src/app/core/data-repair/is-data-repair-possible.util.ts b/src/app/core/data-repair/is-data-repair-possible.util.ts new file mode 100644 index 000000000..d8f2e9500 --- /dev/null +++ b/src/app/core/data-repair/is-data-repair-possible.util.ts @@ -0,0 +1,8 @@ +import { AppDataComplete } from '../../imex/sync/sync.model'; + +export const isDataRepairPossible = (data: AppDataComplete): boolean => { + const d: any = data as any; + return typeof d === 'object' && d !== null + && typeof d.task === 'object' && d.task !== null + && typeof d.project === 'object' && d.project !== null; +}; diff --git a/src/app/core/electron/electron.service.ts b/src/app/core/electron/electron.service.ts index 543ed0fcc..21d7ab5ba 100644 --- a/src/app/core/electron/electron.service.ts +++ b/src/app/core/electron/electron.service.ts @@ -6,6 +6,20 @@ import { IS_ELECTRON } from '../../app.constants'; import { getElectron } from '../../util/get-electron'; import * as ElectronRenderer from 'electron/renderer'; +// TODO make available for both +export const getSendChannel = (channel: string) => `%better-ipc-send-channel-${channel}`; +const getUniqueId = () => `${Date.now()}-${Math.random()}`; + +// const getRendererSendChannel = (windowId, channel) => `%better-ipc-send-channel-${windowId}-${channel}`; +const getResponseChannels = (channel: string) => { + const id = getUniqueId(); + return { + sendChannel: getSendChannel(channel), + dataChannel: `%better-ipc-response-data-channel-${channel}-${id}`, + errorChannel: `%better-ipc-response-error-channel-${channel}-${id}` + }; +}; + @Injectable({providedIn: 'root'}) export class ElectronService { ipcRenderer?: typeof ipcRenderer; @@ -57,4 +71,37 @@ export class ElectronService { public get process(): any { return this.remote ? this.remote.process : null; } + + public callMain(channel: string, data: unknown) { + return new Promise((resolve, reject) => { + const {sendChannel, dataChannel, errorChannel} = getResponseChannels(channel); + + const cleanup = () => { + (this.ipcRenderer as typeof ipcRenderer).off(dataChannel, onData); + (this.ipcRenderer as typeof ipcRenderer).off(errorChannel, onError); + }; + + const onData = (event: unknown, result: unknown) => { + cleanup(); + resolve(result); + }; + + const onError = (event: unknown, error: unknown) => { + cleanup(); + // reject(deserializeError(error)); + reject(error); + }; + + (this.ipcRenderer as typeof ipcRenderer).once(dataChannel, onData); + (this.ipcRenderer as typeof ipcRenderer).once(errorChannel, onError); + + const completeData = { + dataChannel, + errorChannel, + userData: data + }; + + (this.ipcRenderer as typeof ipcRenderer).send(sendChannel, completeData); + }); + } } diff --git a/src/app/core/language/language.service.ts b/src/app/core/language/language.service.ts index a546d533b..0cfdcd1f9 100644 --- a/src/app/core/language/language.service.ts +++ b/src/app/core/language/language.service.ts @@ -5,6 +5,9 @@ import { DateAdapter } from '@angular/material/core'; import * as moment from 'moment'; import { AUTO_SWITCH_LNGS, LanguageCode, LanguageCodeMomentMap, RTL_LANGUAGES } from '../../app.constants'; import { BehaviorSubject, Observable } from 'rxjs'; +import { GlobalConfigService } from 'src/app/features/config/global-config.service'; +import { map, startWith } from 'rxjs/operators'; +import { DEFAULT_GLOBAL_CONFIG } from 'src/app/features/config/default-global-config.const'; @Injectable({providedIn: 'root'}) export class LanguageService { @@ -19,6 +22,7 @@ export class LanguageService { private _translateService: TranslateService, private _dateTimeAdapter: DateTimeAdapter, private _dateAdapter: DateAdapter, + private _globalConfigService: GlobalConfigService, ) { } @@ -32,6 +36,15 @@ export class LanguageService { setDefault(lng: LanguageCode) { this._translateService.setDefaultLang(lng); + + let firstDayOfWeek = DEFAULT_GLOBAL_CONFIG.misc.firstDayOfWeek; + this._globalConfigService.misc$.pipe( + map(cfg => cfg.firstDayOfWeek), + startWith(0), + ).subscribe((_firstDayOfWeek: number) => { + firstDayOfWeek = _firstDayOfWeek; + }); + this._dateAdapter.getFirstDayOfWeek = () => firstDayOfWeek; } setFromBrowserLngIfAutoSwitchLng() { diff --git a/src/app/features/config/default-global-config.const.ts b/src/app/features/config/default-global-config.const.ts index 2b5e14a72..f352a6891 100644 --- a/src/app/features/config/default-global-config.const.ts +++ b/src/app/features/config/default-global-config.const.ts @@ -14,7 +14,8 @@ export const DEFAULT_GLOBAL_CONFIG: GlobalConfigState = { isTurnOffMarkdown: false, isAutoAddWorkedOnToToday: false, isDisableInitialDialog: false, - defaultProjectId: null + defaultProjectId: null, + firstDayOfWeek: 0, }, evaluation: { isHideEvaluationSheet: false, @@ -115,6 +116,7 @@ export const DEFAULT_GLOBAL_CONFIG: GlobalConfigState = { }, trackingReminder: { isEnabled: true, + isShowOnMobile: false, minTime: minute * 2, } }; diff --git a/src/app/features/config/form-cfgs/language-selection-form.const.ts b/src/app/features/config/form-cfgs/language-selection-form.const.ts index 55d0449d3..6f55f6b33 100644 --- a/src/app/features/config/form-cfgs/language-selection-form.const.ts +++ b/src/app/features/config/form-cfgs/language-selection-form.const.ts @@ -27,6 +27,7 @@ export const LANGUAGE_SELECTION_FORM_FORM: ConfigFormSection = { {label: T.GCF.LANG.IT, value: LanguageCode.it}, {label: T.GCF.LANG.PT, value: LanguageCode.pt}, {label: T.GCF.LANG.NL, value: LanguageCode.nl}, + {label: T.GCF.LANG.NB, value: LanguageCode.nb}, ], }, }, diff --git a/src/app/features/config/form-cfgs/misc-settings-form.const.ts b/src/app/features/config/form-cfgs/misc-settings-form.const.ts index 1f494f7e0..d63cd2d0c 100644 --- a/src/app/features/config/form-cfgs/misc-settings-form.const.ts +++ b/src/app/features/config/form-cfgs/misc-settings-form.const.ts @@ -70,5 +70,21 @@ export const MISC_SETTINGS_FORM_CFG: ConfigFormSection = { label: T.GCF.MISC.DEFAULT_PROJECT, }, }, + { + key: 'firstDayOfWeek', + type: 'select', + templateOptions: { + label: T.GCF.MISC.FIRST_DAY_OF_WEEK, + options: [ + {label: T.F.TASK_REPEAT.F.SUNDAY, value: 0}, + {label: T.F.TASK_REPEAT.F.MONDAY, value: 1}, + {label: T.F.TASK_REPEAT.F.TUESDAY, value: 2}, + {label: T.F.TASK_REPEAT.F.WEDNESDAY, value: 3}, + {label: T.F.TASK_REPEAT.F.THURSDAY, value: 4}, + {label: T.F.TASK_REPEAT.F.FRIDAY, value: 5}, + {label: T.F.TASK_REPEAT.F.SATURDAY, value: 6}, + ], + }, + }, ] }; diff --git a/src/app/features/config/form-cfgs/tracking-reminder-form.const.ts b/src/app/features/config/form-cfgs/tracking-reminder-form.const.ts index bf082aedd..51a7b1c51 100644 --- a/src/app/features/config/form-cfgs/tracking-reminder-form.const.ts +++ b/src/app/features/config/form-cfgs/tracking-reminder-form.const.ts @@ -14,6 +14,13 @@ export const TRACKING_REMINDER_FORM_CFG: ConfigFormSection; export type EvaluationConfig = Readonly<{ @@ -127,6 +128,7 @@ export type SoundConfig = Readonly<{ export type TrackingReminderConfig = Readonly<{ isEnabled: boolean; + isShowOnMobile: boolean; minTime: number; }>; diff --git a/src/app/features/config/select-project/select-project.component.html b/src/app/features/config/select-project/select-project.component.html index a5a2feb2f..84f514854 100644 --- a/src/app/features/config/select-project/select-project.component.html +++ b/src/app/features/config/select-project/select-project.component.html @@ -4,7 +4,7 @@ [id]="id" [readonly]="to.readonly" matNativeControl> - + diff --git a/src/app/features/issue/providers/jira/jira-issue/jira-issue-map.util.ts b/src/app/features/issue/providers/jira/jira-issue/jira-issue-map.util.ts index 7d2685d53..4d249650c 100644 --- a/src/app/features/issue/providers/jira/jira-issue/jira-issue-map.util.ts +++ b/src/app/features/issue/providers/jira/jira-issue/jira-issue-map.util.ts @@ -23,7 +23,7 @@ export const mapToSearchResults = (res: any): SearchResultItem[] => { issueData: { ...issue, summary: issue.summaryText, - // NOTE THIS + // NOTE: we always use the key, because it allows us to create the right link id: issue.key, }, }; @@ -47,7 +47,8 @@ export const mapIssue = (issue: JiraIssueOriginal, cfg: JiraCfg): JiraIssue => { return { key: issueCopy.key, - id: issueCopy.id, + // NOTE: we always use the key, because it allows us to create the right link + id: issueCopy.key, components: fields.components, timeestimate: fields.timeestimate, timespent: fields.timespent, diff --git a/src/app/features/reminder/reminder.module.ts b/src/app/features/reminder/reminder.module.ts index baf9705fc..b6816d91b 100644 --- a/src/app/features/reminder/reminder.module.ts +++ b/src/app/features/reminder/reminder.module.ts @@ -5,7 +5,7 @@ import { NoteModule } from '../note/note.module'; import { MatDialog } from '@angular/material/dialog'; import { IS_ELECTRON } from '../../app.constants'; import { TasksModule } from '../tasks/tasks.module'; -import { filter } from 'rxjs/operators'; +import { concatMap, filter, first } from 'rxjs/operators'; import { Reminder } from './reminder.model'; import { UiHelperService } from '../ui-helper/ui-helper.service'; import { NotifyService } from '../../core/notify/notify.service'; @@ -13,6 +13,10 @@ import { DialogViewNoteReminderComponent } from '../note/dialog-view-note-remind import { DialogViewTaskRemindersComponent } from '../tasks/dialog-view-task-reminders/dialog-view-task-reminders.component'; import { DataInitService } from '../../core/data-init/data-init.service'; import { throttle } from 'helpful-decorators'; +import { merge, timer } from 'rxjs'; +import { SyncService } from '../../imex/sync/sync.service'; + +const MAX_WAIT_FOR_INITIAL_SYNC = 25000; @NgModule({ declarations: [], @@ -29,10 +33,22 @@ export class ReminderModule { private readonly _uiHelperService: UiHelperService, private readonly _notifyService: NotifyService, private readonly _dataInitService: DataInitService, + private readonly _syncService: SyncService, ) { - this._dataInitService.isAllDataLoadedInitially$.subscribe(() => { - _reminderService.init(); - }); + + this._dataInitService.isAllDataLoadedInitially$.pipe(concatMap(() => + + // we do this to wait for syncing and the like + merge( + this._syncService.afterInitialSyncDoneAndDataLoadedInitially$, + timer(MAX_WAIT_FOR_INITIAL_SYNC), + ).pipe( + first(), + ))) + .subscribe(async () => { + _reminderService.init(); + }); + this._reminderService.onRemindersActive$.pipe( // NOTE: we simply filter for open dialogs, as reminders are re-queried quite often filter((reminder) => this._matDialog.openDialogs.length === 0 && !!reminder && reminder.length > 0), diff --git a/src/app/features/reminder/reminder.service.ts b/src/app/features/reminder/reminder.service.ts index df2067279..60b32a712 100644 --- a/src/app/features/reminder/reminder.service.ts +++ b/src/app/features/reminder/reminder.service.ts @@ -3,7 +3,7 @@ import { PersistenceService } from '../../core/persistence/persistence.service'; import { RecurringConfig, Reminder, ReminderCopy, ReminderType } from './reminder.model'; import { SnackService } from '../../core/snack/snack.service'; import * as shortid from 'shortid'; -import { BehaviorSubject, merge, Observable, ReplaySubject, Subject, timer } from 'rxjs'; +import { BehaviorSubject, Observable, ReplaySubject, Subject } from 'rxjs'; import { dirtyDeepCopy } from '../../util/dirtyDeepCopy'; import { ImexMetaService } from '../../imex/imex-meta/imex-meta.service'; import { TaskService } from '../tasks/task.service'; @@ -11,14 +11,12 @@ import { Note } from '../note/note.model'; import { Task } from '../tasks/task.model'; import { NoteService } from '../note/note.service'; import { T } from '../../t.const'; -import { SyncService } from '../../imex/sync/sync.service'; -import { filter, first, map, skipUntil } from 'rxjs/operators'; +import { filter, map, skipUntil } from 'rxjs/operators'; import { migrateReminders } from './migrate-reminder.util'; import { WorkContextService } from '../work-context/work-context.service'; import { devError } from '../../util/dev-error'; import { WorkContextType } from '../work-context/work-context.model'; -const MAX_WAIT_FOR_INITIAL_SYNC = 25000; @Injectable({ providedIn: 'root', @@ -45,7 +43,6 @@ export class ReminderService { constructor( private readonly _workContextService: WorkContextService, - private readonly _syncService: SyncService, private readonly _persistenceService: PersistenceService, private readonly _snackService: SnackService, private readonly _taskService: TaskService, @@ -67,19 +64,11 @@ export class ReminderService { }); } - init() { - // we do this to wait for syncing and the like - merge( - this._syncService.afterInitialSyncDoneAndDataLoadedInitially$, - timer(MAX_WAIT_FOR_INITIAL_SYNC), - ).pipe( - first(), - ).subscribe(async () => { - this._w.addEventListener('message', this._onReminderActivated.bind(this)); - this._w.addEventListener('error', this._handleError.bind(this)); - await this.reloadFromDatabase(); - this._isRemindersLoaded$.next(true); - }); + async init() { + this._w.addEventListener('message', this._onReminderActivated.bind(this)); + this._w.addEventListener('error', this._handleError.bind(this)); + await this.reloadFromDatabase(); + this._isRemindersLoaded$.next(true); } async reloadFromDatabase() { diff --git a/src/app/features/tasks/short-syntax.spec.ts b/src/app/features/tasks/short-syntax.spec.ts index f48b7f58f..8229ce5fa 100644 --- a/src/app/features/tasks/short-syntax.spec.ts +++ b/src/app/features/tasks/short-syntax.spec.ts @@ -184,6 +184,18 @@ describe('shortSyntax', () => { } }); }); + + it('should not add tags for sub tasks', () => { + const t = { + ...TASK, + parentId: 'SOMEPARENT', + title: 'Fun title #blu #idontexist', + tagIds: [] + }; + const r = shortSyntax(t, ALL_TAGS); + + expect(r).toEqual(undefined); + }); }); describe('should work with all combined', () => { diff --git a/src/app/features/tasks/short-syntax.util.ts b/src/app/features/tasks/short-syntax.util.ts index 2eaeee0ee..6943aa8be 100644 --- a/src/app/features/tasks/short-syntax.util.ts +++ b/src/app/features/tasks/short-syntax.util.ts @@ -112,6 +112,10 @@ const parseProjectChanges = (task: Partial, allProjects?: Project[]): const parseTagChanges = (task: Partial, allTags?: Tag[]): { taskChanges: Partial, newTagTitlesToCreate: string[] } => { const taskChanges: Partial = {}; + if (task.parentId) { + return {taskChanges, newTagTitlesToCreate: []}; + } + const newTagTitlesToCreate: string[] = []; // only exec if previous ones are also passed if (Array.isArray(task.tagIds) && Array.isArray(allTags)) { diff --git a/src/app/features/tasks/task.model.ts b/src/app/features/tasks/task.model.ts index fc53bb9c2..b72518876 100644 --- a/src/app/features/tasks/task.model.ts +++ b/src/app/features/tasks/task.model.ts @@ -22,6 +22,7 @@ export interface TimeSpentOnDayCopy { } export interface TaskArchive extends EntityState { + ids: string[]; // additional entities state properties [MODEL_VERSION_KEY]?: number; } diff --git a/src/app/features/tasks/task.service.ts b/src/app/features/tasks/task.service.ts index d854fa88b..a56d6be55 100644 --- a/src/app/features/tasks/task.service.ts +++ b/src/app/features/tasks/task.service.ts @@ -204,7 +204,7 @@ export class TaskService { startFirstStartable() { this._workContextService.startableTasks$.pipe(take(1)).subscribe(tasks => { - if (tasks[0]) { + if (tasks[0] && !this.currentTaskId) { this.setCurrentId(tasks[0].id); } }); diff --git a/src/app/features/time-tracking/tracking-reminder/tracking-reminder.service.ts b/src/app/features/time-tracking/tracking-reminder/tracking-reminder.service.ts index f871b66b8..2dc4dc18b 100644 --- a/src/app/features/time-tracking/tracking-reminder/tracking-reminder.service.ts +++ b/src/app/features/time-tracking/tracking-reminder/tracking-reminder.service.ts @@ -15,6 +15,7 @@ import { getWorklogStr } from '../../../util/get-work-log-str'; import { T } from '../../../t.const'; import { TranslateService } from '@ngx-translate/core'; import { TrackingReminderConfig } from '../../config/global-config.model'; +import { IS_TOUCH_ONLY } from '../../../util/is-touch'; @Injectable({ providedIn: 'root' @@ -27,14 +28,19 @@ export class TrackingReminderService { _manualReset$: Subject = new Subject(); _resetableCounter$: Observable = merge( - of(true), + of('INITIAL'), this._manualReset$, ).pipe( switchMap(() => this._counter$), ); + _hideTrigger$: Observable = merge( + this._taskService.currentTaskId$.pipe(filter(currentId => !!currentId)), + this._idleService.isIdle$.pipe(filter(isIdle => isIdle)), + ); + remindCounter$: Observable = this._cfg$.pipe( - switchMap((cfg) => !cfg.isEnabled + switchMap((cfg) => !cfg.isEnabled || (!cfg.isShowOnMobile && IS_TOUCH_ONLY) ? EMPTY : combineLatest([ this._taskService.currentTaskId$, @@ -66,6 +72,14 @@ export class TrackingReminderService { this.remindCounter$.subscribe((count) => { this._triggerBanner(count, {}); }); + + this._hideTrigger$.subscribe((v) => { + this._hideBanner(); + }); + } + + private _hideBanner() { + this._bannerService.dismiss(BannerId.StartTrackingReminder); } private _triggerBanner(duration: number, cfg: any) { diff --git a/src/app/features/work-context/work-context.service.ts b/src/app/features/work-context/work-context.service.ts index 4d06c069a..b91ed5a63 100644 --- a/src/app/features/work-context/work-context.service.ts +++ b/src/app/features/work-context/work-context.service.ts @@ -226,28 +226,26 @@ export class WorkContextService { map(([today, backlog]) => [...today, ...backlog]) ); - // TODO make it more efficient startableTasks$: Observable = combineLatest([ this.activeWorkContext$, this._store$.pipe( select(selectTaskEntities), ) ]).pipe( - switchMap(([activeContext, entities]) => { - const taskIds = activeContext.taskIds; - return of( - (Object.keys(entities) - .filter((id) => { - const t = entities[id] as Task; - return !t.isDone && ( - (t.parentId) - ? (taskIds.includes(t.parentId)) - : (taskIds.includes(id) && (!t.subTaskIds || t.subTaskIds.length === 0)) - ); - }) - .map(key => entities[key]) as Task[]) - ); - }) + map(([activeContext, entities]) => { + let startableTasks: Task[] = []; + activeContext.taskIds.forEach(id => { + const task: Task | undefined = entities[id]; + if (!task) throw new Error('Task not found'); + + if (task.subTaskIds && task.subTaskIds.length) { + startableTasks = startableTasks.concat(task.subTaskIds.map(sid => entities[sid] as Task)); + } else { + startableTasks.push(task); + } + }); + return startableTasks.filter(task => !task.isDone); + }), ); workingToday$: Observable = this.getTimeWorkedForDay$(getWorklogStr()); diff --git a/src/app/imex/local-backup/local-backup.effects.spec.ts b/src/app/imex/local-backup/local-backup.effects.spec.ts new file mode 100644 index 000000000..12df6b60c --- /dev/null +++ b/src/app/imex/local-backup/local-backup.effects.spec.ts @@ -0,0 +1,25 @@ +// import { TestBed } from '@angular/core/testing'; +// import { provideMockActions } from '@ngrx/effects/testing'; +// import { Observable } from 'rxjs'; +// +// import { LocalBackupEffects } from './local-backup.effects'; +// +// describe('LocalBackupEffects', () => { +// let actions$: Observable; +// let effects: LocalBackupEffects; +// +// beforeEach(() => { +// TestBed.configureTestingModule({ +// providers: [ +// LocalBackupEffects, +// provideMockActions(() => actions$) +// ] +// }); +// +// effects = TestBed.inject(LocalBackupEffects); +// }); +// +// it('should be created', () => { +// expect(effects).toBeTruthy(); +// }); +// }); diff --git a/src/app/imex/local-backup/local-backup.effects.ts b/src/app/imex/local-backup/local-backup.effects.ts new file mode 100644 index 000000000..b044f5a56 --- /dev/null +++ b/src/app/imex/local-backup/local-backup.effects.ts @@ -0,0 +1,58 @@ +import { Injectable } from '@angular/core'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { take, tap } from 'rxjs/operators'; +import { loadAllData } from '../../root-store/meta/load-all-data.action'; +import { AppDataComplete } from '../sync/sync.model'; +import { IS_ELECTRON } from '../../app.constants'; +import { LocalBackupService } from './local-backup.service'; +import { DataImportService } from '../sync/data-import.service'; +import { TranslateService } from '@ngx-translate/core'; +import { T } from '../../t.const'; +import * as moment from 'moment'; + +@Injectable() +export class LocalBackupEffects { + + checkForBackupIfNoTasks$: any = createEffect(() => this._actions$.pipe( + ofType( + loadAllData + ), + take(1), + tap(({appDataComplete}) => { + console.log(appDataComplete); + this._checkForBackupIfEmpty(appDataComplete); + }) + ), {dispatch: false}); + + constructor( + private _actions$: Actions, + private _localBackupService: LocalBackupService, + private _dataImportService: DataImportService, + private _translateService: TranslateService, + ) { + } + + private async _checkForBackupIfEmpty(appDataComplete: AppDataComplete) { + console.log(IS_ELECTRON, appDataComplete); + if (IS_ELECTRON) { + if (appDataComplete.task.ids.length === 0 && appDataComplete.taskArchive.ids.length === 0) { + const backupMeta = await this._localBackupService.isBackupAvailable(); + if (backupMeta) { + console.log('backupMeta', backupMeta); + if (confirm(this._translateService.instant(T.CONFIRM.RESTORE_FILE_BACKUP, { + dir: backupMeta.folder, + from: this._formatDate(backupMeta.created), + }))) { + const backupData = await this._localBackupService.loadBackup(backupMeta.path); + console.log('backupData', backupData); + await this._dataImportService.importCompleteSyncData(JSON.parse(backupData)); + } + } + } + } + } + + private _formatDate(date: Date | string | number) { + return moment(date).format('DD-MM-YYYY, hh:mm:ss'); + } +} diff --git a/src/app/imex/local-backup/local-backup.model.ts b/src/app/imex/local-backup/local-backup.model.ts new file mode 100644 index 000000000..d4f52a86c --- /dev/null +++ b/src/app/imex/local-backup/local-backup.model.ts @@ -0,0 +1,6 @@ +export interface LocalBackupMeta { + folder: string; + path: string; + name: string; + created: number; +} diff --git a/src/app/imex/local-backup/local-backup.module.ts b/src/app/imex/local-backup/local-backup.module.ts index 6ea334519..4e18b2ef3 100644 --- a/src/app/imex/local-backup/local-backup.module.ts +++ b/src/app/imex/local-backup/local-backup.module.ts @@ -1,9 +1,12 @@ import { NgModule } from '@angular/core'; import { LocalBackupService } from './local-backup.service'; import { IS_ELECTRON } from '../../app.constants'; +import { EffectsModule } from '@ngrx/effects'; +import { LocalBackupEffects } from './local-backup.effects'; @NgModule({ providers: [LocalBackupService], + imports: [EffectsModule.forFeature([LocalBackupEffects])], }) export class LocalBackupModule { constructor(private _localBackupService: LocalBackupService) { @@ -12,4 +15,6 @@ export class LocalBackupModule { } } + + } diff --git a/src/app/imex/local-backup/local-backup.service.ts b/src/app/imex/local-backup/local-backup.service.ts index 4ea6fedcd..25ac7515d 100644 --- a/src/app/imex/local-backup/local-backup.service.ts +++ b/src/app/imex/local-backup/local-backup.service.ts @@ -3,10 +3,11 @@ import { GlobalConfigService } from '../../features/config/global-config.service import { interval, Observable } from 'rxjs'; import { LocalBackupConfig } from '../../features/config/global-config.model'; import { filter, map, switchMap, tap } from 'rxjs/operators'; -import { DataImportService } from '../sync/data-import.service'; import { IPC } from '../../../../electron/ipc-events.const'; import { ElectronService } from '../../core/electron/electron.service'; import { ipcRenderer } from 'electron'; +import { PersistenceService } from '../../core/persistence/persistence.service'; +import { LocalBackupMeta } from './local-backup.model'; const DEFAULT_BACKUP_INTERVAL = 2 * 60 * 1000; @@ -23,7 +24,7 @@ export class LocalBackupService { constructor( private _configService: GlobalConfigService, - private _dataImportService: DataImportService, + private _persistenceService: PersistenceService, private _electronService: ElectronService, ) { } @@ -32,8 +33,16 @@ export class LocalBackupService { this._triggerBackups.subscribe(); } + isBackupAvailable(): Promise { + return this._electronService.callMain(IPC.BACKUP_IS_AVAILABLE, null) as Promise; + } + + loadBackup(backupPath: string): Promise { + return this._electronService.callMain(IPC.BACKUP_LOAD_DATA, backupPath) as Promise; + } + private async _backup() { - const data = await this._dataImportService.getCompleteSyncData(); + const data = await this._persistenceService.loadComplete(); (this._electronService.ipcRenderer as typeof ipcRenderer).send(IPC.BACKUP, data); } } diff --git a/src/app/imex/sync/data-import.service.ts b/src/app/imex/sync/data-import.service.ts index 749e1434b..f47950dd5 100644 --- a/src/app/imex/sync/data-import.service.ts +++ b/src/app/imex/sync/data-import.service.ts @@ -69,7 +69,7 @@ export class DataImportService { await this._importBackup(); this._imexMetaService.setDataImportInProgress(false); } - } else if (this._dataRepairService.isRepairConfirmed()) { + } else if (this._dataRepairService.isRepairPossibleAndConfirmed(data)) { const fixedData = this._dataRepairService.repairData(data); await this.importCompleteSyncData(fixedData, isBackupReload, true); } else { diff --git a/src/app/imex/sync/is-valid-app-data.util.spec.ts b/src/app/imex/sync/is-valid-app-data.util.spec.ts index d8ce0677c..7e08eada5 100644 --- a/src/app/imex/sync/is-valid-app-data.util.spec.ts +++ b/src/app/imex/sync/is-valid-app-data.util.spec.ts @@ -119,6 +119,76 @@ describe('isValidAppData()', () => { })).toThrowError(`Inconsistent Task State: Missing task id goneTag for Project/Tag TEST_TAG`); }); + it('orphaned archived sub tasks', () => { + const taskState = { + ...mock.task, + ...fakeEntityStateFromArray([{ + ...DEFAULT_TASK, + id: 'subTaskUnarchived', + title: 'subTaskUnarchived', + parentId: 'parent', + }, { + ...DEFAULT_TASK, + id: 'parent', + title: 'parent', + parentId: null, + subTaskIds: ['subTaskUnarchived'] + }]) + } as any; + + const taskArchiveState = { + ...mock.taskArchive, + ...fakeEntityStateFromArray([{ + ...DEFAULT_TASK, + id: 'subTaskArchived', + title: 'subTaskArchived', + parentId: 'parent', + }]) + } as any; + + expect(() => isValidAppData({ + ...mock, + // NOTE: it's empty + task: taskState, + taskArchive: taskArchiveState, + })).toThrowError(`Inconsistent Task State: Lonely Sub Task in Archive`); + }); + + it('orphaned today sub tasks', () => { + const taskState = { + ...mock.task, + ...fakeEntityStateFromArray([{ + ...DEFAULT_TASK, + id: 'subTaskUnarchived', + title: 'subTaskUnarchived', + parentId: 'parent', + }]) + } as any; + + const taskArchiveState = { + ...mock.taskArchive, + ...fakeEntityStateFromArray([{ + ...DEFAULT_TASK, + id: 'subTaskArchived', + title: 'subTaskArchived', + parentId: 'parent', + }, { + ...DEFAULT_TASK, + id: 'parent', + title: 'parent', + parentId: null, + subTaskIds: ['subTaskArchived'] + }]) + } as any; + + expect(() => isValidAppData({ + ...mock, + // NOTE: it's empty + task: taskState, + taskArchive: taskArchiveState, + })).toThrowError(`Inconsistent Task State: Lonely Sub Task in Today`); + }); + xit('missing tag for task', () => { expect(() => isValidAppData({ ...mock, diff --git a/src/app/imex/sync/is-valid-app-data.util.ts b/src/app/imex/sync/is-valid-app-data.util.ts index ab084bb41..afaccacd1 100644 --- a/src/app/imex/sync/is-valid-app-data.util.ts +++ b/src/app/imex/sync/is-valid-app-data.util.ts @@ -4,6 +4,7 @@ import { isEntityStateConsistent } from '../../util/check-fix-entity-state-consi import { devError } from '../../util/dev-error'; import { Tag } from '../../features/tag/tag.model'; import { Project } from '../../features/project/project.model'; +import { Task } from '../../features/tasks/task.model'; export const isValidAppData = (d: AppDataComplete, isSkipInconsistentTaskStateError = false): boolean => { const dAny: any = d; @@ -31,7 +32,10 @@ export const isValidAppData = (d: AppDataComplete, isSkipInconsistentTaskStateEr && typeof dAny.project === 'object' && dAny.project !== null && Array.isArray(d.reminders) && _isEntityStatesConsistent(d) - && (isSkipInconsistentTaskStateError || _isAllTasksAvailable(d)) + && (isSkipInconsistentTaskStateError || + _isAllTasksAvailable(d) + && _isNoLonelySubTasks(d) + ) : typeof dAny === 'object' ; @@ -103,7 +107,7 @@ const _isEntityStatesConsistent = (data: AppDataComplete): boolean => { || projectStateKeys.find(projectModelKey => { const dataForProjects = data[projectModelKey]; - if (typeof dataForProjects !== 'object') { + if (typeof (dataForProjects as any) !== 'object') { throw new Error('No dataForProjects'); } return Object.keys(dataForProjects).find(projectId => @@ -116,3 +120,26 @@ const _isEntityStatesConsistent = (data: AppDataComplete): boolean => { return !brokenItem; }; + +const _isNoLonelySubTasks = (data: AppDataComplete): boolean => { + let isValid: boolean = true; + data.task.ids.forEach((id: string) => { + const t: Task = data.task.entities[id] as Task; + if (t.parentId && !data.task.ids.includes(t.parentId)) { + console.log(t); + devError(`Inconsistent Task State: Lonely Sub Task in Today`); + isValid = false; + } + }); + + data.taskArchive.ids.forEach((id: string) => { + const t: Task = data.taskArchive.entities[id] as Task; + if (t.parentId && !data.taskArchive.ids.includes(t.parentId)) { + console.log(t); + devError(`Inconsistent Task State: Lonely Sub Task in Archive`); + isValid = false; + } + }); + + return isValid; +}; diff --git a/src/app/t.const.ts b/src/app/t.const.ts index 00398bf9b..d9134154e 100644 --- a/src/app/t.const.ts +++ b/src/app/t.const.ts @@ -19,7 +19,8 @@ export const T = { 'CONFIRM': { 'AUTO_FIX': 'CONFIRM.AUTO_FIX', 'DELETE_STRAY_BACKUP': 'CONFIRM.DELETE_STRAY_BACKUP', - 'RESTORE_STRAY_BACKUP': 'CONFIRM.RESTORE_STRAY_BACKUP' + 'RESTORE_STRAY_BACKUP': 'CONFIRM.RESTORE_STRAY_BACKUP', + 'RESTORE_FILE_BACKUP': 'CONFIRM.RESTORE_FILE_BACKUP' }, 'DATETIME_INPUT': { 'IN': 'DATETIME_INPUT.IN', @@ -938,6 +939,7 @@ export const T = { 'JA': 'GCF.LANG.JA', 'KO': 'GCF.LANG.KO', 'LABEL': 'GCF.LANG.LABEL', + 'NB': 'GCF.LANG.NB', 'NL': 'GCF.LANG.NL', 'PT': 'GCF.LANG.PT', 'RU': 'GCF.LANG.RU', @@ -957,7 +959,8 @@ export const T = { 'IS_HIDE_NAV': 'GCF.MISC.IS_HIDE_NAV', 'IS_NOTIFY_WHEN_TIME_ESTIMATE_EXCEEDED': 'GCF.MISC.IS_NOTIFY_WHEN_TIME_ESTIMATE_EXCEEDED', 'IS_TURN_OFF_MARKDOWN': 'GCF.MISC.IS_TURN_OFF_MARKDOWN', - 'TITLE': 'GCF.MISC.TITLE' + 'TITLE': 'GCF.MISC.TITLE', + 'FIRST_DAY_OF_WEEK': 'GCF.MISC.FIRST_DAY_OF_WEEK' }, 'POMODORO': { 'BREAK_DURATION': 'GCF.POMODORO.BREAK_DURATION', @@ -994,6 +997,7 @@ export const T = { 'HELP': 'GCF.TRACKING_REMINDER.HELP', 'TITLE': 'GCF.TRACKING_REMINDER.TITLE', 'L_IS_ENABLED': 'GCF.TRACKING_REMINDER.L_IS_ENABLED', + 'L_IS_SHOW_ON_MOBILE': 'GCF.TRACKING_REMINDER.L_IS_SHOW_ON_MOBILE', 'L_MIN_TIME': 'GCF.TRACKING_REMINDER.L_MIN_TIME' } }, diff --git a/src/app/ui/owl-wrapper/owl-wrapper.component.html b/src/app/ui/owl-wrapper/owl-wrapper.component.html index 94456f7fc..0a9f57ad2 100644 --- a/src/app/ui/owl-wrapper/owl-wrapper.component.html +++ b/src/app/ui/owl-wrapper/owl-wrapper.component.html @@ -10,5 +10,6 @@ [sPlaceholder]="T.DATETIME_SCHEDULE.PLACEHOLDER|translate" [sPressEnterToSubmit]="T.DATETIME_SCHEDULE.PRESS_ENTER_AGAIN|translate" [sTomorrow]="T.DATETIME_SCHEDULE.TOMORROW|translate" + [firstDayOfWeek]="(firstDayOfWeek$|async)" name="owlDate" ngDefaultControl> diff --git a/src/app/ui/owl-wrapper/owl-wrapper.component.ts b/src/app/ui/owl-wrapper/owl-wrapper.component.ts index ab6279362..6431b9b4a 100644 --- a/src/app/ui/owl-wrapper/owl-wrapper.component.ts +++ b/src/app/ui/owl-wrapper/owl-wrapper.component.ts @@ -1,4 +1,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map, startWith } from 'rxjs/operators'; +import { GlobalConfigService } from 'src/app/features/config/global-config.service'; import { T } from 'src/app/t.const'; @Component({ @@ -31,7 +34,12 @@ export class OwlWrapperComponent { '23:30', ]; - constructor() { + firstDayOfWeek$: Observable = this._globalConfigService.misc$.pipe( + map(cfg => cfg.firstDayOfWeek), + startWith(0), + ); + + constructor(private _globalConfigService: GlobalConfigService) { } @Input('dateTime') diff --git a/src/assets/i18n/ar.json b/src/assets/i18n/ar.json index 19d393e34..526d66a02 100644 --- a/src/assets/i18n/ar.json +++ b/src/assets/i18n/ar.json @@ -922,6 +922,7 @@ "JA": "اليابانية", "KO": "الكورية", "LABEL": "يرجى تحديد لغة", + "NB": "النرويجية بوكمال", "NL": "هولندي", "PT": "البرتغالية", "RU": "الروسية", diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index a6c37c187..86ae82b10 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -826,7 +826,7 @@ "DISMISS": "Verwerfen", "DO_IT": "TU es!", "EDIT": "Bearbeiten", - "EXTENSION_INFO": "Bitte laden Sie die Chrome-Erweiterung herunter, um die Kommunikation mit dem Jira Api und Idle Time Handling zu ermöglichen. Beachten Sie, dass dies für Handys nicht funktioniert.", + "EXTENSION_INFO": "Bitte laden Sie die Chrome-Erweiterung herunter, um die Kommunikation mit dem Jira Api und Idle Time Handling zu ermöglichen. Beachten Sie, dass dies für Handys nicht funktioniert.", "LOGIN": "Anmeldung", "LOGOUT": "Ausloggen", "MINUTES": "{{m}} Minuten", @@ -922,6 +922,7 @@ "JA": "Japanisch", "KO": "Koreanisch", "LABEL": "Bitte wähle eine Sprache", + "NB": "Norwegischer Bokmål", "NL": "Niederländisch", "PT": "Portugiesisch", "RU": "Russisch", @@ -941,7 +942,8 @@ "IS_HIDE_NAV": "Navigation verbergen, bis die Hauptüberschrift angezeigt wird (nur Desktop)", "IS_NOTIFY_WHEN_TIME_ESTIMATE_EXCEEDED": "Benachrichtigen, wenn die geschätzte Zeit überschritten wurde", "IS_TURN_OFF_MARKDOWN": "Deaktivieren Sie das Markdown-Parsing für Notizen", - "TITLE": "Verschiedene Einstellungen" + "TITLE": "Verschiedene Einstellungen", + "FIRST_DAY_OF_WEEK": "Erster Tag der Woche" }, "POMODORO": { "BREAK_DURATION": "Dauer der kurzen Pausen", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index bff8a6e35..24f99af1f 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -19,7 +19,8 @@ "CONFIRM": { "AUTO_FIX": "Your data seems to be damaged. Do you want to try to automatically fix it? This might result in partial data loss.", "DELETE_STRAY_BACKUP": "Do you want to delete the back to avoid seeing this dialog?", - "RESTORE_STRAY_BACKUP": "During last sync there might have been some error. Do you want to restore the last backup?" + "RESTORE_STRAY_BACKUP": "During last sync there might have been some error. Do you want to restore the last backup?", + "RESTORE_FILE_BACKUP": "There seems to be NO DATA, but there are backups available at \"{{dir}}\". Do you want to restore the latest backup from {{from}}?" }, "DATETIME_INPUT": { "IN": "in {{time}}", @@ -842,7 +843,7 @@ "DISMISS": "Dismiss", "DO_IT": "Do it!", "EDIT": "Edit", - "EXTENSION_INFO": "Please download the chrome extension in order to allow communication with the Jira Api and Idle Time Handling. Note that this doesn't work for mobile.", + "EXTENSION_INFO": "Please download the chrome extension in order to allow communication with the Jira Api and Idle Time Handling. Note that this doesn't work for mobile.", "LOGIN": "Login", "LOGOUT": "Logout", "MINUTES": "{{m}} minutes", @@ -938,6 +939,7 @@ "JA": "Japanese", "KO": "Korean", "LABEL": "Please select a language", + "NB": "Norwegian Bokmål", "NL": "Dutch", "PT": "Portuguese", "RU": "Russian", @@ -957,7 +959,8 @@ "IS_HIDE_NAV": "Hide navigation until main header is hovered (desktop only)", "IS_NOTIFY_WHEN_TIME_ESTIMATE_EXCEEDED": "Notify when time estimate was exceeded", "IS_TURN_OFF_MARKDOWN": "Turn off markdown parsing for notes", - "TITLE": "Misc Settings" + "TITLE": "Misc Settings", + "FIRST_DAY_OF_WEEK": "First day of the week" }, "POMODORO": { "BREAK_DURATION": "Duration of short breaks", @@ -994,6 +997,7 @@ "HELP": "Configure a banner to show up in case you forgot to start time tracking.", "TITLE": "Time Tracking Reminder", "L_IS_ENABLED": "Enabled", + "L_IS_SHOW_ON_MOBILE": "Show on mobile app", "L_MIN_TIME": "Time to wait before showing Banner" } }, diff --git a/src/assets/i18n/es.json b/src/assets/i18n/es.json index 12c3082cb..aaca2745c 100644 --- a/src/assets/i18n/es.json +++ b/src/assets/i18n/es.json @@ -2,15 +2,15 @@ "APP": { "B_INSTALL": { "IGNORE": "Ignorar", - "INSTALL": "instalar", - "MSG": "¿Desea instalar Super Productividad como PWA?" + "INSTALL": "Instalar", + "MSG": "¿Deseas instalar Super Productivity como una PWA?" }, - "B_OFFLINE": "Estás desconectado de internet. Sincronizar y solicitar datos del proveedor de problemas no funcionará.", + "B_OFFLINE": "Estás desconectado de internet. La sincronización y solicitud de datos del proveedor no funcionarán.", "D_INITIAL": { "TITLE": "Bienvenido a v{{nr}}" }, - "UPDATE_MAIN_MODEL": "¡Super Productivity ha recibido una actualización importante! Se requieren algunas migraciones para sus datos. Tenga en cuenta que esto hace que sus datos sean incompatibles con versiones anteriores de la aplicación.", - "UPDATE_MAIN_MODEL_NO_UPDATE": "No se eligió ninguna actualización del modelo. Tenga en cuenta que, si no desea realizar la actualización del modelo, debe bajar a la última versión.", + "UPDATE_MAIN_MODEL": "¡Super Productivity ha recibido una actualización importante! Se requieren algunas migraciones de tus datos. Ten en cuenta que esto hace que tus datos sean incompatibles con versiones anteriores de la aplicación.", + "UPDATE_MAIN_MODEL_NO_UPDATE": "No se eligió ninguna actualización del modelo. Ten en cuenta que, si no deseas realizar la actualización del modelo, debes descargar la última versión.", "UPDATE_WEB_APP": "Nueva versión disponible. ¿Cargar nueva versión?" }, "BL": { @@ -23,21 +23,21 @@ "DATETIME_SCHEDULE": { "LATER_TODAY": "Mas tarde, hoy", "NEXT_WEEK": "La próxima semana", - "PLACEHOLDER": "Por favor seleccione una fecha", - "PRESS_ENTER_AGAIN": "Presione enter nuevamente para guardar", + "PLACEHOLDER": "Selecciona una fecha", + "PRESS_ENTER_AGAIN": "Presiona enter nuevamente para guardar", "TOMORROW": "mañana" }, "F": { "ATTACHMENT": { "DIALOG_EDIT": { - "ADD_ATTACHMENT": "Añadir un adjunto", + "ADD_ATTACHMENT": "Agregar un adjunto", "EDIT_ATTACHMENT": "Editar adjunto", "LABELS": { "FILE": "Ruta de archivo", "IMG": "Imagen", "LINK": "Url" }, - "SELECT_TYPE": "Seleccione un tipo", + "SELECT_TYPE": "Selecciona un tipo", "TYPES": { "FILE": "Archivo (abierto por la aplicación predeterminada del sistema)", "IMG": "Imagen (mostrada como miniatura)", @@ -47,13 +47,13 @@ }, "BOOKMARK": { "BAR": { - "ADD": "Añadir marcador", + "ADD": "Agregar marcador", "DROP": "Suelta aquí para agregar un marcador", "EDIT": "Editar marcadores", "NO_BOOKMARKS": "No tienes marcadores de proyectos. Agregue una a través de arrastrar y soltar o haciendo clic en el ícono ''." }, "DIALOG_EDIT": { - "ADD_BOOKMARK": "Añadir marcador", + "ADD_BOOKMARK": "Agregar marcador", "EDIT_BOOKMARK": "Editar marcador", "LABELS": { "COMMAND": "Comando", @@ -61,8 +61,8 @@ "IMG": "Imagen", "LINK": "Url" }, - "SELECT_ICON": "Seleccione un ícono", - "SELECT_TYPE": "Seleccione un tipo", + "SELECT_ICON": "Selecciona un ícono", + "SELECT_TYPE": "Selecciona un tipo", "TYPES": { "COMMAND": "Comando (comando shell personalizado)", "FILE": "Archivo (abierto por la aplicación predeterminada del sistema)", @@ -97,7 +97,7 @@ }, "FORM": { "B_GENERATE_TOKEN": "Generar token", - "FOLLOW_LINK": "Abra el siguiente enlace y copie el código de autenticación proporcionado allí en el campo de entrada.", + "FOLLOW_LINK": "Abre el siguiente enlace y copia el código de autenticación proporcionado allí en el campo de entrada.", "L_ACCESS_TOKEN": "Token de acceso (generado a partir del código de autenticación)", "L_AUTH_CODE": "código de autenticación", "L_ENABLE_SYNCING": "Habilitar sincronización de Dropbox", @@ -232,7 +232,7 @@ "ERROR": "Google Drive - Error:", "ERROR_INITIAL_IMPORT": "Google Drive: error al intentar importar datos inicialmente", "LOCAL_UP_TO_DATE": "Google Drive: datos locales ya actualizados", - "MULTIPLE_SYNC_FILES_WITH_SAME_NAME": "Se encontraron varios archivos con el nombre \"{{newFileName}}\". Por favor, elimine todos menos uno o elija un nombre diferente.", + "MULTIPLE_SYNC_FILES_WITH_SAME_NAME": "Se encontraron varios archivos con el nombre \"{{newFileName}}\". Elimine todos menos uno o elija un nombre diferente.", "NO_UPDATE_REQUIRED": "Google Drive: No se requiere actualización", "REMOTE_UP_TO_DATE": "Google Drive: datos remotos ya actualizados", "SUCCESS": "Google Drive: Copia de seguridad guardada con éxito", @@ -260,7 +260,7 @@ }, "JIRA": { "BANNER": { - "BLOCK_ACCESS_MSG": "Jira: Para evitar el cierre de api, Super Productividad ha bloqueado el acceso. ¡Probablemente deberías revisar tu configuración de jira!", + "BLOCK_ACCESS_MSG": "Jira: Para evitar el cierre de api, Super Productivity ha bloqueado el acceso. ¡Probablemente deberías revisar tu configuración de jira!", "BLOCK_ACCESS_UNBLOCK": "Desbloquear" }, "CFG_CMP": { @@ -285,7 +285,7 @@ "TITLE": "Configurar Jira para Proyecto" }, "DIALOG_TRANSITION": { - "CHOOSE_STATUS": "Elija estado para asignar", + "CHOOSE_STATUS": "Elija estado a asignar", "CURRENT_ASSIGNEE": "Asignado actual:", "CURRENT_STATUS": "Estado actual:", "TITLE": "Jira: Estado de actualización", @@ -293,7 +293,7 @@ }, "DIALOG_WORKLOG": { "CURRENTLY_LOGGED": "Tiempo registrado actualmente:", - "INVALID_DATE": "El valor introducido no es una fecha!", + "INVALID_DATE": "¡El valor introducido no es una fecha!", "SAVE_WORKLOG": "Guardar registro de trabajo", "STARTED": "Empezado", "SUBMIT_WORKLOG_FOR": "Enviar un registro de trabajo a Jira para", @@ -304,12 +304,12 @@ "IS_AUTO_ADD_TO_BACKLOG": "Agregar automáticamente problemas sin resolver desde Github al registro de trabajo", "IS_AUTO_POLL": "Comprobar automáticamente los problemas de git importados por cambios", "IS_SEARCH_ISSUES_FROM_GITHUB": "Mostrar problemas de git como sugerencias al agregar nuevas tareas", - "REPO": "\"username / repositoryName\" para el repositorio de git que desee seguir" + "REPO": "\"username/repositoryName\" para el repositorio de git que desee seguir" }, "FORM_ADV": { "AUTO_ADD_BACKLOG_JQL_QUERY": "JQL usado para agregar tareas automáticamente al registro de trabajo", "IS_ADD_WORKLOG_ON_SUB_TASK_DONE": "Abrir el cuadro de diálogo para enviar el registro de trabajo a jira cuando se realiza la subtarea", - "IS_AUTO_ADD_TO_BACKLOG": "Añadir automáticamente problemas al registro de trabajo de Jira", + "IS_AUTO_ADD_TO_BACKLOG": "Agregar automáticamente problemas al registro de trabajo de Jira", "IS_AUTO_POLL_TICKETS": "Verifica automáticamente los problemas importados por cambios y notifica", "IS_CHECK_TO_RE_ASSIGN_TICKET_ON_TASK_START": "Compruebe si el problema actualmente trabajado está asignado al usuario actual", "IS_WORKLOG_ENABLED": "Abrir el cuadro de diálogo para enviar el registro de trabajo a jira cuando se realiza la tarea", @@ -358,7 +358,7 @@ }, "S": { "ADDED_WORKLOG_FOR": "Jira: Registro de trabajo agregado para {{issueKey}}", - "EXTENSION_NOT_LOADED": "Extensión de Super Productividad no cargada. Recargar la página podría ayudar", + "EXTENSION_NOT_LOADED": "Extensión de Super Productivity no cargada. Recargar la página podría ayudar", "IMPORTED_MULTIPLE_ISSUES": "Jira: Importó {{issuesLength}} problemas nuevos de jira al registro de trabajo", "IMPORTED_SINGLE_ISSUE": "Jira: problema importado \"{{issueText}}\" de jira al registro de trabajo", "INSUFFICIENT_SETTINGS": "Ajustes insuficientes proporcionados para Jira", @@ -373,14 +373,14 @@ "TRANSITION": "Jira: establece el problema \"{{issueKey}}\" en \"{{name}}\"", "TRANSITIONS_LOADED": "Jira: Transiciones cargadas. Usa las selecciones de abajo para asignarlas.", "TRANSITION_SUCCESS": "Jira: establece el problema {{issueKey}} a {{chosenTransition}}", - "UNABLE_TO_REASSIGN": "Jira: No se puede reasignar el ticket a ti mismo, porque no especificaste un nombre de usuario. Por favor, visite los ajustes." + "UNABLE_TO_REASSIGN": "Jira: No se puede reasignar el ticket a ti mismo, porque no especificaste un nombre de usuario. Visite los ajustes." }, "STEPPER": { "CREDENTIALS": "Credenciales", "DONE": "Ya has terminado.", "LOGIN_SUCCESS": "¡Inicio de sesión exitoso!", "TEST_CREDENTIALS": "Credenciales de prueba", - "WELCOME_USER": "Bienvenido {{user}}!" + "WELCOME_USER": "¡Bienvenido {{user}}!" } }, "METRIC": { @@ -399,12 +399,12 @@ "MOOD_PRODUCTIVITY_OVER_TIME": "Estado de ánimo y productividad a lo largo del tiempo.", "NO_ADDITIONAL_DATA_YET": "No se han recopilado datos adicionales todavía. Use el formulario en el resumen diario \"Evaluación\" del panel para hacerlo.", "OBSTRUCTION_SELECTION_COUNT": "Número de veces que se seleccionó un factor de obstrucción", - "TASKS_DONE_CREATED": "Tareas (realizada / creada)", + "TASKS_DONE_CREATED": "Tareas (realizada/creada)", "TIME_ESTIMATED": "Tiempo estimado", "TIME_SPENT": "Tiempo empleado" }, "EVAL_FORM": { - "ADD_NOTE_FOR_TOMORROW": "Añadir nota para mañana", + "ADD_NOTE_FOR_TOMORROW": "Agregar nota para mañana", "DISABLE_REPEAT_EVERY_DAY": "Desactivar repetir todos los días", "ENABLE_REPEAT_EVERY_DAY": "Repite todos los dias", "HELP_H1": "¿Por qué debería importarme?", @@ -442,7 +442,7 @@ "EDIT_FULLSCREEN": "Editar en pantalla completa", "EDIT_REMINDER": "Editar recordatorio", "NOTES_CMP": { - "ADD_BTN": "Añadir nueva nota", + "ADD_BTN": "Agregar nueva nota", "DROP_TO_ADD": "Suelta aquí para agregar una nueva nota" }, "NOTE_CMP": { @@ -459,7 +459,7 @@ }, "POMODORO": { "BACK_TO_WORK": "¡Volver al trabajo!", - "BREAK_IS_DONE": "Tu descanso ha terminado!", + "BREAK_IS_DONE": "!Tu descanso ha terminado!", "ENJOY_YOURSELF": "Diviértete, muévete, vuelve en:", "FINISH_SESSION_X": "¡Has completado exitosamente la sesión {{nr}}!", "NOTIFICATION": { @@ -492,7 +492,7 @@ "TITLE": "Curiosidad" }, "H1": "¡Relájate un poco!", - "P1": "Primero que nada, a relajarse! Todo el mundo lo hace de vez en cuando. Y si no estás haciendo lo que debes, ¡al menos deberías disfrutarlo! Entonces revisa las secciones de abajo para algo útil.", + "P1": "!Primero que nada, a relajarse! Todo el mundo lo hace de vez en cuando. Y si no estás haciendo lo que debes, ¡al menos deberías disfrutarlo! Entonces revisa las secciones de abajo para algo útil.", "P2": "Si desea saber más sobre la ciencia detrás de todo esto, puedo recomendar este artículo , en el cual se encuentran algunos de los ejercicios.", "P3": "Recuerde: la procrastinación es un problema de regulación emocional, no un problema de gestión del tiempo.", "REFRAME": { @@ -560,7 +560,7 @@ "TITLE": "Editar contador simple" }, "FORM": { - "ADD_NEW": "Añadir contador simple", + "ADD_NEW": "Agregar contador simple", "HELP": "Aquí puede configurar botones simples que aparecerán en la esquina superior derecha. Pueden ser temporizadores o simplemente un contador simple, que se cuenta, haciendo clic en él.", "L_AUTO_COUNT_UP": "El gatillo automático cuenta para", "L_AUTO_SWITCH_OFF": "Disparador automático apagado para", @@ -600,8 +600,8 @@ }, "TASK": { "ADDITIONAL_INFO": { - "ADD_ATTACHMENT": "Añadir un adjunto", - "ADD_SUB_TASK": "Añadir sub tarea", + "ADD_ATTACHMENT": "Agregar un adjunto", + "ADD_SUB_TASK": "Agregar subtarea", "ATTACHMENTS": "Adjuntos {{nr}}", "FROM_PARENT": "(del padre)", "LOCAL_ATTACHMENTS": "Adjuntos locales", @@ -619,15 +619,15 @@ "ADD_TASK": "Agregar tarea", "ADD_TASK_TO_BACKLOG": "Agregar tarea al registro de trabajo", "CREATE_TASK": "Crear nueva tarea", - "EXAMPLE": "Ejemplo: \"Un título de tarea + nombre de proyecto # alguna etiqueta # alguna otra etiqueta 10m / 3h\"", + "EXAMPLE": "Ejemplo: \"Título de tarea + nombre de proyecto #una etiqueta #otra etiqueta 10m/3h\"", "START": "Presiona entrar una vez más para comenzar" }, "B": { - "ADD_HALF_HOUR": "Añadir 1/2 hora", + "ADD_HALF_HOUR": "Agregar 1/2 hora", "ESTIMATE_EXCEEDED": "Tiempo estimado superado para \"{{title}}\"" }, "CMP": { - "ADD_SUB_TASK": "Añadir subtarea", + "ADD_SUB_TASK": "Agregar subtarea", "ADD_TO_MY_DAY": "Agregar a mi día", "ADD_TO_PROJECT": "Agregar a un proyecto", "CONVERT_TO_PARENT_TASK": "Convertir a tarea principal", @@ -647,8 +647,8 @@ "REPEAT_EDIT": "Editar Repetir Tarea Config.", "SCHEDULE": "Programar tarea", "SHOW_UPDATES": "Mostrar actualizaciones", - "TOGGLE_ADDITIONAL": "Mostrar / Ocultar información adicional", - "TOGGLE_ATTACHMENTS": "Mostrar / Ocultar archivos adjuntos", + "TOGGLE_ADDITIONAL": "Mostrar/Ocultar información adicional", + "TOGGLE_ATTACHMENTS": "Mostrar/Ocultar archivos adjuntos", "TOGGLE_DONE": "Marcar como realizada / no realizada", "TOGGLE_SUB_TASK_VISIBILITY": "Alternar visibilidad de subtareas", "TRACK_TIME": "Comenzar el tiempo de seguimiento", @@ -663,8 +663,8 @@ "UNSCHEDULE": "Desprogramar" }, "D_REMINDER_VIEW": { - "ADD_ALL_TO_TODAY": "Agrega todo a hoy", - "ADD_TO_TODAY": "Añadir a hoy", + "ADD_ALL_TO_TODAY": "Agregar todo a hoy", + "ADD_TO_TODAY": "Agregar a hoy", "DISMISS": "Descartar recordatorio", "DISMISS_ALL": "Descartar todo", "DUE_TASK": "Tarea debida", @@ -680,7 +680,7 @@ "SWITCH_CONTEXT_START": "Cambiar contexto e inicio" }, "D_TIME": { - "ADD_FOR_OTHER_DAY": "Añadir tiempo empleado para otro día", + "ADD_FOR_OTHER_DAY": "Agregar tiempo empleado para otro día", "DELETE_FOR": "Borrar entrada por dia", "ESTIMATE": "Estimado", "TIME_SPENT": "Tiempo empleado", @@ -688,14 +688,14 @@ "TITLE": "Tiempo empleado / Estimaciones" }, "D_TIME_FOR_DAY": { - "ADD_ENTRY_FOR": "Añadir nueva entrada para {{date}}", + "ADD_ENTRY_FOR": "Agregar nueva entrada para {{date}}", "DATE": "Fecha para la nueva entrada", "HELP": "Ejemplos:
30m => 30 minutos
2h => 2 horas
2h 30m => 2 horas y 30 minutos", "TINE_SPENT": "Tiempo empleado", - "TITLE": "Añadir para el día" + "TITLE": "Agregar para el día" }, "N": { - "ESTIMATE_EXCEEDED": "Tiempo estimado superado!", + "ESTIMATE_EXCEEDED": "!Tiempo estimado superado!", "ESTIMATE_EXCEEDED_BODY": "Has excedido tu tiempo estimado para \"{{title}}\"." }, "S": { @@ -724,7 +724,7 @@ "OK": "Quitar completamente" }, "D_EDIT": { - "ADD": "Añadir Repetir configuración de tarea", + "ADD": "Agregar Repetir configuración de tarea", "EDIT": "Editar Repetir Tarea Config.", "HELP1": "Las tareas repetitivas están destinadas a las tareas diarias, por ejemplo, \"Organización\", \"Reunión diaria\", \"Revisión de código\", \"Comprobación de correos electrónicos\" o tareas similares que pueden ocurrir una y otra vez.", "HELP2": "Una vez configurada, se volverá a crear una tarea repetitiva en cada día seleccionado a continuación tan pronto como abra su proyecto y se marcará automáticamente como finalizada al final del día. Serán manejados como diferentes instancias. Así que puedes agregar libremente subtareas, etc.", @@ -774,7 +774,7 @@ "D_EXPORT_TITLE": "Exportar registro de trabajo {{start}}-{{end}}", "D_EXPORT_TITLE_SINGLE": "Exportar registro de trabajo {{day}}", "EXPORT": { - "ADD_COL": "Añadir columna", + "ADD_COL": "Agregar columna", "COPY_TO_CLIPBOARD": "Copiar al portapapeles", "DONT_ROUND": "no redondear", "EDIT_COL": "Editar columna", @@ -826,7 +826,7 @@ "DISMISS": "Descartar", "DO_IT": "¡Hazlo!", "EDIT": "Editar", - "EXTENSION_INFO": "Por favor descargue la extensión de Chrome para permitir la comunicación con Jira Api y Idle Time Handling. Tenga en cuenta que esto no funciona para móviles.", + "EXTENSION_INFO": "Por favor descargue la extensión de Chrome para permitir la comunicación con Jira Api y Idle Time Handling. Tenga en cuenta que esto no funciona para móviles.", "LOGIN": "Iniciar sesión", "LOGOUT": "Cerrar sesión", "MINUTES": "{{m}} minutos", @@ -871,42 +871,42 @@ "TITLE": "Importación y exportación" }, "KEYBOARD": { - "ADD_NEW_NOTE": "Añadir nueva nota", + "ADD_NEW_NOTE": "Agregar nueva nota", "ADD_NEW_TASK": "Agregar nueva tarea", - "APP_WIDE_SHORTCUTS": "Accesos directos globales (en toda la aplicación)", + "APP_WIDE_SHORTCUTS": "Atajos globales (en toda la aplicación)", "COLLAPSE_SUB_TASKS": "Contraer subtareas", "EXPAND_SUB_TASKS": "Expandir subtareas", - "GLOBAL_ADD_NOTE": "Añadir nueva nota", + "GLOBAL_ADD_NOTE": "Agregar nueva nota", "GLOBAL_ADD_TASK": "Agregar nueva tarea", - "GLOBAL_SHOW_HIDE": "Mostrar / Ocultar Super Productividad", + "GLOBAL_SHOW_HIDE": "Mostrar/Ocultar Super Productivity", "GLOBAL_TOGGLE_TASK_START": "Alternar el seguimiento de tiempo para la última tarea activa", "GO_TO_DAILY_AGENDA": "Ir a Agenda", "GO_TO_FOCUS_MODE": "Ir al Modo de Enfoque", "GO_TO_SETTINGS": "Ir a Ajustes", "GO_TO_WORK_VIEW": "Ir a la Vista de Trabajo", - "HELP": "

Aquí puedes configurar todos los atajos del teclado.

Haga clic en la entrada de texto e ingrese la combinación de teclas deseada. Pulsa Enter para guardar y Escape para abortar.

Hay tres tipos de accesos directos:

  • Atajos globales: Cuando la aplicación se está ejecutando, se activará la acción desde cualquier otra aplicación.
  • Atajos de nivel de aplicación: Se activará desde todas las pantallas de la aplicación, pero no si está editando un campo de texto.
  • Atajos de nivel de tarea: Solo se activarán si ha seleccionado una tarea mediante el mouse o el teclado y, por lo general, activará una acción relacionada específicamente con esa tarea.
", + "HELP": "

Aquí puedes configurar todos los atajos del teclado.

Haga clic en la entrada de texto e ingrese la combinación de teclas deseada. Pulsa Enter para guardar y Escape para abortar.

Hay tres tipos de atajos:

  • Atajos globales: Cuando la aplicación se está ejecutando, se activará la acción desde cualquier otra aplicación.
  • Atajos de nivel de aplicación: Se activará desde todas las pantallas de la aplicación, pero no si está editando un campo de texto.
  • Atajos de nivel de tarea: Solo se activarán si ha seleccionado una tarea mediante el mouse o el teclado y, por lo general, activará una acción relacionada específicamente con esa tarea.
", "MOVE_TASK_DOWN": "Mueve la tarea hacia abajo en la lista", "MOVE_TASK_UP": "Mover la tarea hacia arriba en la lista", "MOVE_TO_BACKLOG": "Mover la tarea al registro de trabajo", "MOVE_TO_TODAYS_TASKS": "Mueve la tarea a la lista de tareas de hoy", - "OPEN_PROJECT_NOTES": "Mostrar / Ocultar Notas del Proyecto", + "OPEN_PROJECT_NOTES": "Mostrar/Ocultar Notas del Proyecto", "SELECT_NEXT_TASK": "Seleccionar tarea siguiente", "SELECT_PREVIOUS_TASK": "Seleccionar tarea previa", "SYSTEM_SHORTCUTS": "Atajos globales (en todo el sistema)", - "TASK_ADD_SUB_TASK": "Añadir subtarea", + "TASK_ADD_SUB_TASK": "Agregar subtarea", "TASK_DELETE": "Eliminar tarea", "TASK_EDIT_TITLE": "Editar Título", "TASK_MOVE_TO_PROJECT": "Abrir mover tarea al menú del proyecto", "TASK_OPEN_ESTIMATION_DIALOG": "Editar estimación / tiempo empleado", "TASK_SCHEDULE": "Programar tarea", "TASK_SHORTCUTS": "Tareas", - "TASK_SHORTCUTS_INFO": "Los siguientes atajos se aplican a la tarea seleccionada actualmente (seleccionada mediante una pestaña o un mouse).", - "TASK_TOGGLE_ADDITIONAL_INFO_OPEN": "Mostrar / Ocultar información adicional de la tarea", + "TASK_SHORTCUTS_INFO": "Los siguientes atajos se aplican a la tarea seleccionada actual (seleccionada a través de una pestaña o con el ratón).", + "TASK_TOGGLE_ADDITIONAL_INFO_OPEN": "Mostrar/Ocultar información adicional de la tarea", "TASK_TOGGLE_DONE": "Altenado realizado", "TITLE": "Atajos de teclado", - "TOGGLE_BACKLOG": "Mostrar / Ocultar registro de la tarea", - "TOGGLE_BOOKMARKS": "Mostrar / Ocultar barra de marcadores", - "TOGGLE_PLAY": "Iniciar / detener tarea", + "TOGGLE_BACKLOG": "Mostrar/Ocultar registro de la tarea", + "TOGGLE_BOOKMARKS": "Mostrar/Ocultar barra de marcadores", + "TOGGLE_PLAY": "Iniciar/Detener tarea", "ZOOM_DEFAULT": "Valor predeterminado de zoom (solo para escritorio)", "ZOOM_IN": "Acercar (solo para escritorio)", "ZOOM_OUT": "Alejar (solo para escritorio)" @@ -918,30 +918,31 @@ "ES": "Español", "FA": "Farsi", "FR": "Francés", - "IT": "italiano", + "IT": "Italiano", "JA": "Japonés", "KO": "Coreano", - "LABEL": "Por favor, seleccione un idioma", + "LABEL": "Selecciona un idioma", + "NB": "Noruego bokmål", "NL": "Holandés", - "PT": "portugués", + "PT": "Portugués", "RU": "Ruso", "TITLE": "Idioma", "TR": "Turco", "ZH": "Chino" }, "MISC": { - "DEFAULT_PROJECT": "Proyecto predeterminado para usar para tareas si no se especifica ninguno", - "HELP": "

¿No ve las notificaciones de escritorio? Para Windows, es posible que desee revisar Sistema> Notificaciones y acciones y verificar si se han habilitado las notificaciones requeridas.

", - "IS_AUTO_ADD_WORKED_ON_TO_TODAY": "Agregue automáticamente la etiqueta de hoy a las tareas trabajadas", - "IS_AUTO_MARK_PARENT_AS_DONE": "Marque la tarea principal como realizada, cuando todas las subtareas estén listas", - "IS_AUTO_START_NEXT_TASK": "Comience a rastrear la siguiente tarea cuando marque actual como hecha", + "DEFAULT_PROJECT": "Proyecto predeterminado a usar para las tareas si no se especifica ninguno", + "HELP": "

¿No se ven las notificaciones de escritorio? En Windows, puedes revisar en Sistema> Notificaciones y acciones, y verificar si se han habilitado las notificaciones requeridas.

", + "IS_AUTO_ADD_WORKED_ON_TO_TODAY": "Agregar automáticamente la etiqueta de hoy a las tareas completadas", + "IS_AUTO_MARK_PARENT_AS_DONE": "Marcar la tarea principal como realizada, cuando se completen todas las subtareas", + "IS_AUTO_START_NEXT_TASK": "Comenzar a rastrear la siguiente tarea cuando se marque la tarea actual como completa", "IS_CONFIRM_BEFORE_EXIT": "Confirmar antes de salir de la aplicación", - "IS_DARK_MODE": "Modo oscuro", - "IS_DISABLE_INITIAL_DIALOG": "Deshabilite el diálogo de información inicial (¡podría perderse actualizaciones importantes!)", + "IS_DARK_MODE": "Modo Oscuro", + "IS_DISABLE_INITIAL_DIALOG": "Deshabilitar el diálogo de información inicial (¡podrías perderte actualizaciones importantes!)", "IS_HIDE_NAV": "Ocultar la navegación hasta que se desplace sobre el encabezado principal (solo para escritorio)", - "IS_NOTIFY_WHEN_TIME_ESTIMATE_EXCEEDED": "Notificar cuando se excedió el tiempo estimado", - "IS_TURN_OFF_MARKDOWN": "Desactiva el análisis de descuento para notas", - "TITLE": "Ajustes varios" + "IS_NOTIFY_WHEN_TIME_ESTIMATE_EXCEEDED": "Notificar cuando se excede el tiempo estimado", + "IS_TURN_OFF_MARKDOWN": "Desactivar la sintaxis markdown para las notas", + "TITLE": "Ajustes Variados" }, "POMODORO": { "BREAK_DURATION": "Duración de las pausas breves", @@ -952,16 +953,16 @@ "IS_MANUAL_CONTINUE": "Confirmar manualmente el inicio de la siguiente sesión de pomodoro", "IS_PLAY_SOUND": "Reproducir sonido cuando la sesión haya terminado", "IS_PLAY_SOUND_AFTER_BREAK": "Reproducir sonido cuando el descanso haya terminado", - "IS_PLAY_TICK": "Reproduce el sonido de tick cada segundo", + "IS_PLAY_TICK": "Reproducir el sonido de tick cada segundo", "IS_STOP_TRACKING_ON_BREAK": "Detener el tiempo de seguimiento para la tarea en descanso", "LONGER_BREAK_DURATION": "Duración de los descansos más largos", - "TITLE": "Pomodoro Timer" + "TITLE": "Temporizador Pomodoro" }, "SOUND": { "DONE_SOUND": "Sonido de tarea realizada", "IS_INCREASE_DONE_PITCH": "Aumente el tono para cada tarea realizada", "IS_PLAY_DONE_SOUND": "Reproducir sonido cuando la tarea esté marcada como terminada", - "TITLE": "Sonar", + "TITLE": "Sonido", "VOLUME": "Volumen" }, "TAKE_A_BREAK": { @@ -973,6 +974,12 @@ "MIN_WORKING_TIME": "Mostrar una notificación de descanso después de X tiempo trabajando sin una", "MOTIVATIONAL_IMG": "Imagen motivacional (url web)", "TITLE": "Recordatorio de descanso" + }, + "TRACKING_REMINDER": { + "HELP": "Establecer un recordatorio para que aparezca en caso de que haya olvidado iniciar el seguimiento del tiempo.", + "TITLE": "Recordatorio de Seguimiento de Tiempo", + "L_IS_ENABLED": "Activado", + "L_MIN_TIME": "Tiempo a esperar antes de que se mueste el recordatorio" } }, "GLOBAL_SNACK": { @@ -980,8 +987,8 @@ "ERR_COMPRESSION": "Error para la interfaz de compresión", "PERSISTENCE_DISALLOWED": "Los datos no se conservarán de forma permanente. ¡Tenga en cuenta que esto puede conducir a la pérdida de datos!", "RUNNING_X": "Ejecutando \"{{str}}\".", - "SHORTCUT_WARN_OPEN_BOOKMARKS_FROM_TAG": "{{keyCombo}} presionado, pero el acceso directo a marcadores abiertos solo está disponible en el contexto del proyecto.", - "SHORTCUT_WARN_OPEN_NOTES_FROM_TAG": "{{keyCombo}} presionado, pero el acceso directo de notas abiertas solo está disponible en el contexto del proyecto." + "SHORTCUT_WARN_OPEN_BOOKMARKS_FROM_TAG": "{{keyCombo}} presionado, pero el atajo a marcadores abiertos solo está disponible en el contexto del proyecto.", + "SHORTCUT_WARN_OPEN_NOTES_FROM_TAG": "{{keyCombo}} presionado, pero el atajo de notas abiertas solo está disponible en el contexto del proyecto." }, "GPB": { "ASSETS": "Cargando activos ...", @@ -1012,14 +1019,14 @@ "TAGS": "Etiquetas", "TASKS": "Tareas", "TASK_LIST": "Lista de tareas", - "TOGGLE_SHOW_BOOKMARKS": "Mostrar / Ocultar Marcadores", - "TOGGLE_SHOW_NOTES": "Mostrar / Ocultar Notas del Proyecto", + "TOGGLE_SHOW_BOOKMARKS": "Mostrar/Ocultar Marcadores", + "TOGGLE_SHOW_NOTES": "Mostrar/Ocultar Notas del Proyecto", "TOGGLE_TRACK_TIME": "Iniciar / Detener el tiempo de seguimiento", "WORKLOG": "Registro de trabajo" }, "PDS": { - "BACK": "Espera me olvidé de algo!", - "BREAK_LABEL": "Descansos (nr / tiempo)", + "BACK": "!Espera me olvidé de algo!", + "BREAK_LABEL": "Descansos (nr/tiempo)", "CELEBRATE": "¡Tómate un momento para celebrar !", "CLEAR_ALL_CONTINUE": "Borrar todo listo y continuar", "D_CONFIRM_APP_CLOSE": { @@ -1063,11 +1070,11 @@ "OK": "Archivar" }, "D_CONFIRM_DELETE": { - "MSG": "¿Estás seguro de que quieres eliminar este proyecto? Se recomienda hacer una copia de seguridad de sus datos antes de hacer esto.", + "MSG": "¿Seguro que quieres eliminar este proyecto? Se recomienda hacer una copia de seguridad de sus datos antes de hacer esto.", "OK": "Borrar" }, "D_CONFIRM_UNARCHIVE": { - "MSG": "¿Estás seguro de que quieres desarchivar este proyecto?", + "MSG": "¿Seguro que quieres desarchivar este proyecto?", "OK": "Desarchivar" }, "EDIT_PROJECT": "Editar proyecto", @@ -1084,11 +1091,11 @@ "GLOBAL_SETTINGS": "Ajustes globales", "ISSUE_INTEGRATION": "Integración de problemas", "PRIVATE_POLICY": "Política privada", - "PRODUCTIVITY_HELPER": "Ayudante de productividad", + "PRODUCTIVITY_HELPER": "Ayudante de Productividad", "PROJECT_SETTINGS": "Ajustes Específicos del Proyecto", "SYNC_EXPORT": "Sincronizar y Exportar", "TAG_SETTINGS": "Configuraciones específicas de etiqueta", - "TOGGLE_DARK_MODE": "Cambiar modo oscuro" + "TOGGLE_DARK_MODE": "Cambiar a modo oscuro" }, "S": { "SYNC": { @@ -1103,7 +1110,7 @@ "START_TASK": "Inicie la tarea ahora y elimine el recordatorio" }, "THEMES": { - "SELECT_THEME": "Seleccione el tema", + "SELECT_THEME": "Selecciona el tema", "amber": "Ámbar", "blue": "Azul", "blue-grey": "Gris azulado", @@ -1121,7 +1128,7 @@ "yellow": "Amarillo" }, "V": { - "E_1TO10": "Por favor, introduzca un valor entre 1 y 10", + "E_1TO10": "Ingresa un valor entre 1 y 10", "E_DATETIME": "El valor ingresado no es una fecha y hora!", "E_MAX": "No debe ser más grande que {{val}}", "E_MAX_LENGTH": "Debe tener una longitud máxima de {{val}} caracteres", @@ -1131,12 +1138,12 @@ "E_REQUIRED": "Este campo es requerido" }, "WW": { - "ADD_MORE": "Añadir más", + "ADD_MORE": "Agregar más", "ADD_SOME_TASKS": "¡Agrega algunas tareas para planificar tu día!", "COMPLETED_TASKS": "Tareas completadas", "ESTIMATE_REMAINING": "Estimación restante:", "FINISH_DAY": "Fin de dia", - "HELP_PROCRASTINATION": "Ayuda, estoy procrastinando!", + "HELP_PROCRASTINATION": "Ayuda, !estoy procrastinando!", "NO_COMPLETED_TASKS": "Actualmente no hay tareas completadas.", "NO_PLANNED_TASKS": "No hay tareas planificadas", "READY_TO_WORK": "¡Listo para trabajar!", diff --git a/src/assets/i18n/fa.json b/src/assets/i18n/fa.json index f338e6fed..df81ccb82 100644 --- a/src/assets/i18n/fa.json +++ b/src/assets/i18n/fa.json @@ -826,7 +826,7 @@ "DISMISS": "رد کردن", "DO_IT": "انجام بدهید!", "EDIT": "ویرایش", - "EXTENSION_INFO": "لطفا برای ارتباط با جیرا و مدیریت زمان افزونه گوگل کروم را دانلود کنید. توجه این افزونه برای نسخه موبایل کروم کار نمی کند.", + "EXTENSION_INFO": "لطفا برای ارتباط با جیرا و مدیریت زمان افزونه گوگل کروم را دانلود کنید. توجه این افزونه برای نسخه موبایل کروم کار نمی کند.", "LOGIN": "وارد شدن", "LOGOUT": "خارج شدن", "MINUTES": "{{m}} دقیقه", @@ -922,6 +922,7 @@ "JA": "ژاپنی", "KO": "کره ای", "LABEL": "لطفا یک زبان انتخاب کنید", + "NB": "نروژی", "NL": "هلندی", "PT": "پرتغالی", "RU": "روسی", diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json index 178611a2d..307bc12ee 100644 --- a/src/assets/i18n/fr.json +++ b/src/assets/i18n/fr.json @@ -826,7 +826,7 @@ "DISMISS": "Rejeter", "DO_IT": "Fais le !", "EDIT": "modifier", - "EXTENSION_INFO": "Veuillez télécharger l'extension chrome afin de permettre la communication avec l'Api Jira et la gestion du temps d'inactivité. Notez que cela ne fonctionne pas sur mobile.", + "EXTENSION_INFO": "Veuillez télécharger l'extension chrome afin de permettre la communication avec l'Api Jira et la gestion du temps d'inactivité. Notez que cela ne fonctionne pas sur mobile.", "LOGIN": "S'identifier", "LOGOUT": "Se déconnecter", "MINUTES": "{{m}} minutes", @@ -922,6 +922,7 @@ "JA": "japonais", "KO": "coréen", "LABEL": "Veuillez sélectionner une langue", + "NB": "Norvégien bokmål", "NL": "Néerlandais", "PT": "Portugais", "RU": "russe", diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json index 6484a93c3..679906e67 100644 --- a/src/assets/i18n/it.json +++ b/src/assets/i18n/it.json @@ -826,7 +826,7 @@ "DISMISS": "Rimuovi", "DO_IT": "Fallo!", "EDIT": "Modifica", - "EXTENSION_INFO": "Per favore scarica l'estensione di chrome per permettere la comunicazione con le api di Jira e la gestione del tempo inattivo. Nota che non funziona su dispositivi mobile.", + "EXTENSION_INFO": "Per favore scarica l'estensione di chrome per permettere la comunicazione con le api di Jira e la gestione del tempo inattivo. Nota che non funziona su dispositivi mobile.", "LOGIN": "Login", "LOGOUT": "Logout", "MINUTES": "{{m}} minuti", @@ -922,6 +922,7 @@ "JA": "Giapponese", "KO": "Coreano", "LABEL": "Seleziona la lingua", + "NB": "Norvegese Bokmål", "NL": "Olandese", "PT": "Portoguese", "RU": "Russo", diff --git a/src/assets/i18n/ja.json b/src/assets/i18n/ja.json index 89b6e8141..841031b19 100644 --- a/src/assets/i18n/ja.json +++ b/src/assets/i18n/ja.json @@ -826,7 +826,7 @@ "DISMISS": "やめる", "DO_IT": "やれ!", "EDIT": "編集", - "EXTENSION_INFO": "Jira ApiおよびIdle Time Handlingとの通信を可能にするために、クロム拡張 をダウンロード してください。これはモバイルでは機能しないことに注意してください。", + "EXTENSION_INFO": "Jira ApiおよびIdle Time Handlingとの通信を可能にするために、クロム拡張 をダウンロード してください。これはモバイルでは機能しないことに注意してください。", "LOGIN": "ログイン", "LOGOUT": "ログアウト", "MINUTES": "{{m}} 分", @@ -922,6 +922,7 @@ "JA": "日本人", "KO": "韓国語", "LABEL": "言語を選択してください", + "NB": "ノルウェーのブークモール", "NL": "蘭語", "PT": "ポルトガル", "RU": "ロシア", diff --git a/src/assets/i18n/ko.json b/src/assets/i18n/ko.json index 5101d147f..814fa7eb7 100644 --- a/src/assets/i18n/ko.json +++ b/src/assets/i18n/ko.json @@ -826,7 +826,7 @@ "DISMISS": "버리다", "DO_IT": "해!", "EDIT": "편집하다", - "EXTENSION_INFO": "Jira Api 및 Idle Time Handling과의 통신을 허용하려면 크롬 확장 을 다운로드하십시오. 이 기능은 모바일에서 작동하지 않습니다.", + "EXTENSION_INFO": "Jira Api 및 Idle Time Handling과의 통신을 허용하려면 크롬 확장 을 다운로드하십시오. 이 기능은 모바일에서 작동하지 않습니다.", "LOGIN": "로그인", "LOGOUT": "로그 아웃", "MINUTES": "{{m}} 분", @@ -922,6 +922,7 @@ "JA": "일본어", "KO": "한국어", "LABEL": "언어를 선택하십시오.", + "NB": "노르웨이 보크 몰", "NL": "네덜란드 사람", "PT": "포르투갈 인", "RU": "러시아인", diff --git a/src/assets/i18n/nb.json b/src/assets/i18n/nb.json new file mode 100644 index 000000000..d5b90d69f --- /dev/null +++ b/src/assets/i18n/nb.json @@ -0,0 +1,1174 @@ +{ + "APP": { + "B_INSTALL": { + "IGNORE": "Overse", + "INSTALL": "Installere", + "MSG": "Vil du installere Super Productivity som en PWA?" + }, + "B_OFFLINE": "Du er koblet fra internett. Synkronisering og å be om data fra leverandører fungerer ikke.", + "D_INITIAL": { + "TITLE": "Velkommen til v {{nr}}" + }, + "UPDATE_MAIN_MODEL": "Super Productivity har fått en stor oppdatering! Noen migrasjoner for dataene dine er påkrevd. Vær oppmerksom på at dette gjør dataene dine uforenlige med eldre versjoner av appen.", + "UPDATE_MAIN_MODEL_NO_UPDATE": "Ingen modelloppdatering valgt. Vær oppmerksom på at du må nedgradere til den siste versjonen, hvis du ikke vil utføre modelloppgraderingen.", + "UPDATE_WEB_APP": "Ny versjon tilgjengelig. Last inn ny versjon?" + }, + "BL": { + "NO_TASKS": "Det er for øyeblikket ingen oppgaver i backloggen" + }, + "CONFIRM": { + "AUTO_FIX": "Dataene dine ser ut til å være skadet. Vil du prøve å fikse det automatisk? Dette kan føre til delvis tap av data.", + "DELETE_STRAY_BACKUP": "Vil du slette sikkerhetskopien for å unngå å se denne dialogen?", + "RESTORE_STRAY_BACKUP": "Under siste synkronisering kan det ha oppstått en feil. Vil du gjenopprette den siste sikkerhetskopien?", + "RESTORE_FILE_BACKUP": "Det ser ut til å være INGEN DATA, men det er sikkerhetskopier tilgjengelig på \"{{dir}}\". Vil du gjenopprette den siste sikkerhetskopien fra {{from}}?" + }, + "DATETIME_INPUT": { + "IN": "om {{time}}", + "TOMORROW": "i morgen {{time}}" + }, + "DATETIME_SCHEDULE": { + "LATER_TODAY": "Senere i dag", + "NEXT_WEEK": "Neste uke", + "PLACEHOLDER": "Velg en dato", + "PRESS_ENTER_AGAIN": "Trykk på Enter igjen for å lagre", + "TOMORROW": "I morgen" + }, + "F": { + "ATTACHMENT": { + "DIALOG_EDIT": { + "ADD_ATTACHMENT": "Legg til vedlegg", + "EDIT_ATTACHMENT": "Rediger vedlegg", + "LABELS": { + "FILE": "Filbane", + "IMG": "Bilde", + "LINK": "Url" + }, + "SELECT_TYPE": "Velg en type", + "TYPES": { + "FILE": "Fil (åpnet av standard systemapp)", + "IMG": "Bilde (vist som miniatyrbilde)", + "LINK": "Link (åpnes i nettleser)" + } + } + }, + "BOOKMARK": { + "BAR": { + "ADD": "Legg til bokmerke", + "DROP": "Slipp her for å legge til et bokmerke", + "EDIT": "Rediger bokmerker", + "NO_BOOKMARKS": "Du har ingen prosjektbokmerker. Legg til en med dra og slipp eller ved å klikke på 'pluss'-knappen." + }, + "DIALOG_EDIT": { + "ADD_BOOKMARK": "Legg til bokmerke", + "EDIT_BOOKMARK": "Rediger bokmerke", + "LABELS": { + "COMMAND": "Kommando", + "FILE": "Filbane", + "IMG": "Bilde", + "LINK": "Url" + }, + "SELECT_ICON": "Velg et ikon", + "SELECT_TYPE": "Velg en type", + "TYPES": { + "COMMAND": "Kommando (tilpasset skallkommando)", + "FILE": "Fil (åpnet som standard systemapp)", + "IMG": "Bilde (vist som miniatyrbilde)", + "LINK": "Link (åpnes i nettleser)" + } + } + }, + "CONFIG": { + "S": { + "UPDATE_SECTION": "Oppdaterte innstillinger for {{sectionKey}}" + } + }, + "DROPBOX": { + "C": { + "EMPTY_SYNC": "Du prøver å synkronisere et tomt dataobjekt. Er dette din aller første synkronisering av en (nesten) ubrukt app?", + "FORCE_IMPORT": "Importere eksterne data uansett?", + "FORCE_UPLOAD": "Last opp lokale data uansett?", + "FORCE_UPLOAD_AFTER_ERROR": "Det oppsto en feil under opplasting av lokale data. Prøve å tvinge oppdateringen?", + "TRY_LOAD_REMOTE_AGAIN": "Prøv å laste inn data fra ekstern igjen?" + }, + "D_CONFLICT": { + "LAST_CHANGE": "siste endring:", + "LAST_SYNC": "siste synkronisering:", + "LOCAL": "lokal", + "LOCAL_REMOTE": "lokal -> ekstern", + "REMOTE": "fjernkontroll", + "TEXT": "

Oppdatering fra Dropbox. Både lokale og eksterne data ser ut til å være endret.

", + "TITLE": "Dropbox: motstridende data", + "USE_LOCAL": "Bruk lokal", + "USE_REMOTE": "Bruk ekstern" + }, + "FORM": { + "B_GENERATE_TOKEN": "Generer token", + "FOLLOW_LINK": "Vennligst åpne følgende lenke og kopier autentiseringskoden som er oppgitt der, inn i inndatafeltet.", + "L_ACCESS_TOKEN": "Access Token (generert fra Auth Code)", + "L_AUTH_CODE": "Autentiseringskode", + "L_ENABLE_SYNCING": "Aktiver Dropbox-synkronisering", + "L_SYNC_INTERVAL": "Synkroniseringsintervall", + "TITLE": "Synkroniser via Dropbox (anbefalt)" + }, + "S": { + "ACCESS_TOKEN_ERROR": "Dropbox: Kan ikke generere Access Token fra Auth Code", + "ACCESS_TOKEN_GENERATED": "Dropbox: Access Token generert fra Auth Code", + "AUTH_ERROR": "Dropbox: Ugyldig tilgangstoken gitt", + "OFFLINE": "Dropbox: Kan ikke synkroniseres, fordi offline", + "SYNC_ERROR": "Dropbox: Feil under synkronisering", + "UNKNOWN_ERROR": "Dropbox: Ukjent feil: {{errorStr}}" + } + }, + "GITHUB": { + "DIALOG_INITIAL": { + "TITLE": "Sett opp Github for Prosjekt" + }, + "FORM": { + "FILTER_USER": "Brukernavn (f.eks. for å filtrere ut endringer selv)", + "IS_AUTO_ADD_TO_BACKLOG": "Legg automatisk uløste problemer fra Github til backloggen", + "IS_AUTO_POLL": "Poll automatisk importerte gitproblemer for endringer", + "IS_SEARCH_ISSUES_FROM_GITHUB": "Vis problemer fra git som forslag når du legger til nye oppgaver", + "REPO": "\"brukernavn / repositoryName\" for git-depotet du vil spore", + "TOKEN": "Tilgangstoken" + }, + "FORM_SECTION": { + "HELP": "

Her kan du konfigurere SuperProductivity til å liste åpne GitHub-problemer for et bestemt repo i oppgavelagerpanelet i den daglige planvisningen. De vil bli oppført som forslag og vil gi en lenke til problemet, samt mer informasjon om det.

I tillegg kan du automatisk legge til og synkronisere alle åpne problemer i oppgaven din.

", + "TITLE": "Github" + }, + "ISSUE_CONTENT": { + "ASSIGNEE": "Oppdragsgiver", + "AT": "på", + "DESCRIPTION": "Beskrivelse", + "LABELS": "Etiketter", + "MARK_AS_CHECKED": "Merk oppdateringer som merket", + "STATUS": "Status", + "SUMMARY": "Sammendrag", + "WRITE_A_COMMENT": "Skriv en kommentar" + }, + "S": { + "ERR_NETWORK": "Github: Forespørselen mislyktes på grunn av en nettverksfeil på klientsiden", + "ERR_NOT_CONFIGURED": "Github: Er ikke riktig konfigurert", + "ERR_UNKNOWN": "Github: Ukjent feil {{statusCode}} {{errorMsg}}. Grensen for api-rate overskredet?", + "IMPORTED_MULTIPLE_ISSUES": "Github: Importerte {{issuesLength}} nye problemer fra git til backlog", + "IMPORTED_SINGLE_ISSUE": "Github: Importert problem \"{{issueText}}\" fra git til backlog", + "ISSUE_DELETED_OR_CLOSED": "Github: Utgaven \"{{issueText}}\" ser ut til å bli slettet eller lukket på git", + "ISSUE_NO_UPDATE_REQUIRED": "Github: Ingen oppdatering nødvendig", + "ISSUE_UPDATE": "Github: Oppdaterte data for \"{{issueText}}\"", + "MANUAL_UPDATE_ISSUE_SUCCESS": "Github: Oppdaterte data for \"{{issueText}}\"", + "MISSING_ISSUE_DATA": "Github: Oppgaver med manglende problemdata funnet. Laster om.", + "NEW_COMMENT": "Github: Ny kommentar til \"{{issueText}}\"", + "POLLING": "Github: Avstemningsendringer for problemer", + "SHOW_ISSUE_BTN": "Vis meg" + } + }, + "GITLAB": { + "DIALOG_INITIAL": { + "TITLE": "Sett opp Gitlab for Project" + }, + "FORM": { + "FILTER_USER": "Brukernavn (f.eks. For å filtrere ut endringer selv)", + "IS_AUTO_ADD_TO_BACKLOG": "Legg automatisk uløste problemer fra Gitlab til backloggen", + "IS_AUTO_POLL": "Poll automatisk importerte gitproblemer for endringer", + "IS_SEARCH_ISSUES_FROM_GITLAB": "Vis problemer fra git som forslag når du legger til nye oppgaver", + "PROJECT": "Full sti eller brukernavn / prosjekt", + "TOKEN": "Tilgangstoken" + }, + "FORM_SECTION": { + "HELP": "

Her kan du konfigurere SuperProductivity til å liste åpne Gitlab-problemer (enten den online-versjonen eller en selvstyrt forekomst) for et bestemt prosjekt i oppgavelagerpanelet i den daglige planvisningen. De vil bli oppført som forslag og vil gi en lenke til problemet, samt mer informasjon om det.

I tillegg kan du automatisk legge til og synkronisere alle åpne problemer i oppgaven din.

", + "TITLE": "Gitlab" + }, + "ISSUE_CONTENT": { + "ASSIGNEE": "Oppdragsgiver", + "AT": "på", + "DESCRIPTION": "Beskrivelse", + "LABELS": "Etiketter", + "MARK_AS_CHECKED": "Merk oppdateringer som merket", + "STATUS": "Status", + "SUMMARY": "Sammendrag", + "WRITE_A_COMMENT": "Skriv en kommentar" + }, + "S": { + "ERR_NETWORK": "Gitlab: Forespørselen mislyktes på grunn av en nettverksfeil på klientsiden", + "ERR_NOT_CONFIGURED": "Gitlab: Er ikke riktig konfigurert", + "ERR_UNKNOWN": "Gitlab: Ukjent feil {{statusCode}} {{errorMsg}}", + "IMPORTED_MULTIPLE_ISSUES": "Gitlab: Importerte {{issuesLength}} nye problemer fra git til backlog", + "IMPORTED_SINGLE_ISSUE": "Gitlab: Importert problem \"{{issueText}}\" fra git til backlog", + "ISSUE_DELETED_OR_CLOSED": "Gitlab: Utgaven \"{{issueText}}\" ser ut til å være slettet eller lukket på git", + "ISSUE_NO_UPDATE_REQUIRED": "Gitlab: Ingen oppdatering nødvendig", + "ISSUE_UPDATE": "Gitlab: Oppdaterte data for \"{{issueText}}\"", + "MANUAL_UPDATE_ISSUE_SUCCESS": "Gitlab: Oppdaterte data for \"{{issueText}}\"", + "MISSING_ISSUE_DATA": "Gitlab: Oppgaver med manglende problemdata funnet. Laster om.", + "NEW_COMMENT": "Gitlab: Ny kommentar til \"{{issueText}}\"", + "POLLING": "Gitlab: Henter endringer for problemer", + "SHOW_ISSUE_BTN": "Vis meg" + } + }, + "GOOGLE": { + "BANNER": { + "AUTH_FAIL": "GoogleApi: Kunne ikke autentisere, prøv å logge på igjen!" + }, + "DIALOG": { + "CREATE_SYNC_FILE": "Google Disk: Ingen fil med navnet "{{fileName}}" ble funnet. Vil du opprette den som synkroniseringsfil på Google Disk?", + "USE_EXISTING_SYNC_FILE": "Google Drive: Bruk eksisterende fil "{{fileName}}" som synkroniseringsfil? Hvis ikke, kan du endre synkroniseringsfilnavnet." + }, + "D_CONFIRM_LOAD": { + "LAST_MOD": "siste modifikasjon:", + "LAST_SYNC": "siste synkronisering", + "LOCAL": "lokal", + "LOCAL_REMOTE": "lokal <=> ekstern", + "OVERWRITE_LOCAL": "Overskriv lokal", + "OVERWRITE_REMOTE": "Overskriv ekstern", + "REMOTE": "ekstern", + "TEXT": "

Oppdatering fra Google Drive Backup. Både lokale og eksterne data ser ut til å være endret. Vil du overskrive ikke-lagrede lokale endringer? All data vil gå tapt for alltid.

", + "TITLE": "Overskrive lokale data med GDrive Update?" + }, + "D_CONFIRM_SAVE": { + "LAST_MOD": "siste modifikasjon:", + "LAST_SYNC": "siste synkronisering", + "LOCAL": "lokal", + "LOCAL_REMOTE": "lokal <=> ekstern", + "OVERWRITE_LOCAL": "Overskriv lokal", + "OVERWRITE_REMOTE": "Overskriv ekstern", + "REMOTE": "ekstern", + "TEXT": "

Det ser ut til å være noen endringer på Google Drive som du ikke har lokalt. Vil du overskrive dem uansett?

", + "TITLE": "Vil du overskrive ikke lagrede data på Google Disk?" + }, + "S": { + "DOWNLOADING_UPDATE": "Google Drive: Det er en ekstern oppdatering! Laster ned ...", + "ERROR": "Google Disk - Feil: {{errTxt}}", + "ERROR_INITIAL_IMPORT": "Google Disk: Feil under forsøk på å importere data i utgangspunktet", + "LOCAL_UP_TO_DATE": "Google Drive: Lokale data er allerede oppdatert", + "MULTIPLE_SYNC_FILES_WITH_SAME_NAME": "Flere filer med navnet \"{{newFileName}}\" funnet. Slett alle unntatt en, eller velg et annet navn.", + "NO_UPDATE_REQUIRED": "Google Drive: Ingen oppdatering nødvendig", + "REMOTE_UP_TO_DATE": "Google Drive: Eksterne data er allerede oppdatert", + "SUCCESS": "Google Drive: Lagret sikkerhetskopi", + "SYNCING": "Google Drive: Synkronisering" + }, + "SYNC_CFG": { + "AUTO_LOGIN": "Automatisk pålogging når du starter appen", + "AUTO_SYNC": "Automatisk synkronisering av data til ekstern", + "BACKUP_NOW": "Sikkerhetskopier nå", + "COMPRESS": "Komprimer data", + "ENABLE": "Aktiver synkronisering via Google Disk", + "LOAD_FROM": "Last fra GDrive", + "LOAD_ON_STARTUP": "Last inn eksterne data ved oppstart", + "NOTIFY": "Varsle når du synkroniserer", + "SYNC_FILE_NAME": "Synkroniser filnavn", + "SYNC_INTERVAL": "Synkroniseringsintervall", + "UPDATE_SYNC_FILE": "Oppdater synkroniseringsfil" + }, + "S_API": { + "ERR": "GoogleApi-feil: {{errStr}}", + "ERR_NO_FILE_ID": "GoogleApi: Ingen fil-ID er spesifisert", + "ERR_NO_FILE_NAME": "GoogleApi: Ingen filnavn er spesifisert", + "SUCCESS_LOGIN": "GoogleApi: Innlogging vellykket" + } + }, + "JIRA": { + "BANNER": { + "BLOCK_ACCESS_MSG": "Jira: For å forhindre at api er stengt, har tilgang blitt blokkert av Super Productivity. Du bør sannsynligvis sjekke Jira-innstillingene dine!", + "BLOCK_ACCESS_UNBLOCK": "Fjern blokkering" + }, + "CFG_CMP": { + "ALWAYS_ASK": "Åpne alltid dialogboksen", + "DONE": "Status for å fullføre oppgaven", + "DO_NOT": "Ikke overgang", + "ENABLE": "Aktiver Jira-integrering", + "ENABLE_TRANSITIONS": "Aktiver overgangshåndtering", + "IN_PROGRESS": "Status for startoppgave", + "LOAD_SUGGESTIONS": "Lastforslag", + "MAP_CUSTOM_FIELDS": "Kartlegg tilpassede felt", + "MAP_CUSTOM_FIELDS_INFO": "Dessverre lagres noen av Jiras data under egendefinerte felt som er forskjellige for hver installasjon. Hvis du vil inkludere disse dataene, må du velge riktig tilpasset felt for det.", + "OPEN": "Status for pauseoppgave", + "SELECT_ISSUE_FOR_TRANSITIONS": "Velg utgave for å laste inn tilgjengelige overganger", + "STORY_POINTS": "Historiepoeng" + }, + "DIALOG_CONFIRM_ASSIGNMENT": { + "MSG": "{{summary}} er for tiden tilordnet {{assignee}} . Vil du tildele det til deg selv?", + "OK": "Gjør det!" + }, + "DIALOG_INITIAL": { + "TITLE": "Sett opp Jira for Project" + }, + "DIALOG_TRANSITION": { + "CHOOSE_STATUS": "Velg status du vil tildele", + "CURRENT_ASSIGNEE": "Nåværende mottaker:", + "CURRENT_STATUS": "Nåværende status:", + "TITLE": "Jira: Oppdater status", + "UPDATE_STATUS": "Oppdater status" + }, + "DIALOG_WORKLOG": { + "CURRENTLY_LOGGED": "Nåværende logget tid:", + "INVALID_DATE": "Den angitte verdien er ikke en dato!", + "SAVE_WORKLOG": "Lagre arbeidsloggen", + "STARTED": "Startet", + "SUBMIT_WORKLOG_FOR": "Send inn en arbeidslogg til Jira for", + "TIME_SPENT": "Tid brukt", + "TITLE": "Jira: Send inn arbeidslogg" + }, + "FORM": { + "IS_AUTO_ADD_TO_BACKLOG": "Legg automatisk uløste problemer fra Github til backloggen", + "IS_AUTO_POLL": "Hent automatisk importerte gitproblemer for endringer", + "IS_SEARCH_ISSUES_FROM_GITHUB": "Vis problemer fra git som forslag når du legger til nye oppgaver", + "REPO": "\"brukernavn / repositoryName\" for git-depotet du vil spore" + }, + "FORM_ADV": { + "AUTO_ADD_BACKLOG_JQL_QUERY": "JQL brukes til å legge til oppgaver automatisk i backloggen", + "IS_ADD_WORKLOG_ON_SUB_TASK_DONE": "Åpne dialogboksen for å sende arbeidsloggen til jira når underoppgaven er fullført", + "IS_AUTO_ADD_TO_BACKLOG": "Legg automatisk til problemer i Jira-backloggen", + "IS_AUTO_POLL_TICKETS": "Sjekk importerte problemer for endringer automatisk og varsle", + "IS_CHECK_TO_RE_ASSIGN_TICKET_ON_TASK_START": "Sjekk om det nåværende arbeidet med problemet er tildelt den nåværende brukeren", + "IS_WORKLOG_ENABLED": "Åpne dialogboksen for å sende arbeidsloggen til Jira når oppgaven er fullført", + "SEARCH_JQL_QUERY": "JQL Query for å begrense søkeoppgaver" + }, + "FORM_CRED": { + "ALLOW_SELF_SIGNED": "Tillat selvsignert sertifikat", + "HOST": "Vert (for eksempel: http://my-host.de:1234)", + "PASSWORD": "Token / passord", + "USER_NAME": "E-post / brukernavn", + "WONKY_COOKIE_MODE": "Wonky Cookie Fallback Authentication (kun desktop-app)" + }, + "FORM_SECTION": { + "ADV_CFG": "Avansert konfigurasjon", + "CREDENTIALS": "Legitimasjonserklæring", + "HELP_ARR": { + "H1": "Grunnleggende konfigurasjon", + "H2": "Innstillinger for arbeidslogg", + "H3": "Standard overganger", + "P1_1": "Oppgi et påloggingsnavn (finner du på profilsiden din) og et API-token eller passord hvis du av en eller annen grunn ikke kan generere et. Vær ikke oppmerksom på at nyere versjoner av Jira noen ganger bare fungerer med tokenet.", + "P1_2": "Du må også spesifisere et JQL-spørsmål som brukes til forslagene for å legge til oppgaver fra Jira. Hvis du trenger hjelp, kan du sjekke ut denne lenken https://confluence.atlassian.com/jirasoftwarecloud/advanced-searching-764478330.html .", + "P1_3": "Du kan også konfigurere, hvis du vil automatisk (f.eks. hver gang du besøker planleggingsvisningen), for å legge til alle nye oppgaver som er spesifisert av et tilpasset JQL-spørsmål i backloggen.", + "P1_4": "Et annet alternativ er \"Sjekk om gjeldende billett er tildelt nåværende bruker\". Hvis aktivert og du starter, vil det bli gjort en sjekk om du for øyeblikket er tildelt den billetten på Jira, hvis ikke en dialogboks vises der du kan velge å tildele billetten til deg selv.", + "P2_1": "Det er flere alternativer for å bestemme når og hvordan du vil sende inn en arbeidslogg. Aktivering av 'Åpne dialogbok for arbeidslogg for å legge til en arbeidslogg til Jira når oppgaven er fullført' åpner en dialogboks for å legge til en arbeidslogg hver gang du merker en Jira-oppgave som ferdig. Så husk at arbeidslogger vil bli lagt til på toppen av alt som er sporet så langt. Så hvis du merker en oppgave som ferdig for andre gang, vil du kanskje ikke sende inn den totale arbeidstiden for oppgaven igjen.", + "P2_2": "'Åpne arbeidslogg-dialogboksen når underoppgaven er ferdig og ikke for oppgaver med underoppgavene selv' åpner en arbeidslogg-dialog hver gang du merker en underoppgave for et Jira-problem som ferdig. Fordi du allerede sporer tiden din via underoppgavene, åpnes ingen dialog når du merker selve Jira-oppgaven.", + "P2_3": "'Send oppdateringer til arbeidsloggen automatisk uten dialog' gjør hva det står. Fordi det å merke en oppgave som utført flere ganger fører til at hele arbeidet spores to ganger, anbefales ikke dette.", + "P3_1": "Her kan du konfigurere standardovergangene dine på nytt. Jira muliggjør en bred konfigurasjon av overganger som vanligvis kommer til handling som forskjellige kolonner på Jira agile-tavle. Vi kan ikke gjøre antakelser om hvor og når du skal overføre oppgavene dine, og du må angi det manuelt." + } + }, + "ISSUE_CONTENT": { + "ASSIGNEE": "Oppdragsgiver", + "AT": "på", + "ATTACHMENTS": "Vedlegg", + "CHANGED": "endret", + "COMMENTS": "Kommentarer", + "COMPONENTS": "Komponenter", + "DESCRIPTION": "Beskrivelse", + "LIST_OF_CHANGES": "Liste over endringer", + "MARK_AS_CHECKED": "Merk oppdateringer som merket", + "ON": "på", + "STATUS": "Status", + "STORY_POINTS": "Historiepoeng", + "SUMMARY": "Sammendrag", + "WORKLOG": "Arbeidslogg", + "WRITE_A_COMMENT": "Skriv en kommentar" + }, + "S": { + "ADDED_WORKLOG_FOR": "Jira: Lagt til arbeidslogg for {{issueKey}}", + "EXTENSION_NOT_LOADED": "Super Productivity Extension ikke lastet inn. Det kan hjelpe å laste siden inn på nytt", + "IMPORTED_MULTIPLE_ISSUES": "Jira: Importerte {{issuesLength}} nye problemer fra Jira til backlog", + "IMPORTED_SINGLE_ISSUE": "Jira: Importert problem \"{{issueText}}\" fra Jira til backlog", + "INSUFFICIENT_SETTINGS": "Utilstrekkelige innstillinger gitt for Jira", + "ISSUE_NO_UPDATE_REQUIRED": "Jira: \"{{issueText}}\" allerede oppdatert", + "ISSUE_UPDATE": "Jira: Oppdaterte data for \"{{issueText}}\"", + "MANUAL_UPDATE_ISSUE_SUCCESS": "Jira: Oppdaterte data for \"{{issueText}}\"", + "MISSING_ISSUE_DATA": "Jira: Oppgaver med manglende problemdata funnet. Laster om.", + "NO_AUTO_IMPORT_JQL": "Jira: Ingen søkeord definert for automatisk import", + "NO_VALID_TRANSITION": "Jira: Ingen gyldig overgang konfigurert", + "POLLING": "Jira: Avstemningsendringer for problemer", + "TIMED_OUT": "Jira: Forespørsel ble tidsavbrutt", + "TRANSITION": "Jira: Sett problemet \"{{issueKey}}\" til \"{{name}}\"", + "TRANSITIONS_LOADED": "Jira: Overganger lastet. Bruk valgene nedenfor for å tildele dem", + "TRANSITION_SUCCESS": "Jira: Sett problemet {{issueKey}} til {{chosenTransition}}", + "UNABLE_TO_REASSIGN": "Jira: Kunne ikke overføre billetten til deg selv, fordi du ikke spesifiserte et brukernavn. Vennligst besøk innstillingene." + }, + "STEPPER": { + "CREDENTIALS": "Innloggingsdetaljer", + "DONE": "Du er nå ferdig.", + "LOGIN_SUCCESS": "Vellykket innlogging!", + "TEST_CREDENTIALS": "Test innlogging", + "WELCOME_USER": "Velkommen {{user}}!" + } + }, + "METRIC": { + "BANNER": { + "CHECK": "Jeg gjorde det!" + }, + "CMP": { + "AVG_BREAKS_PER_DAY": "Gj.sn. pauser per dag", + "AVG_TASKS_PER_DAY_WORKED": "Gj.sn. oppgaver per utført dag", + "AVG_TIME_SPENT_ON_BREAKS": "Gj.sn. tid brukt på pauser", + "AVG_TIME_SPENT_PER_DAY": "Gj.sn. tid brukt per dag", + "AVG_TIME_SPENT_PER_TASK": "Gj.sn. tidsbruk per oppgave", + "COUNTING_SUBTASKS": "(teller deloppgaver)", + "DAYS_WORKED": "Dager jobbet", + "IMPROVEMENT_SELECTION_COUNT": "Antall ganger en forbedringsfaktor ble valgt", + "MOOD_PRODUCTIVITY_OVER_TIME": "Humør og produktivitet over tid", + "NO_ADDITIONAL_DATA_YET": "Ingen ytterligere data samlet inn ennå. Bruk skjemaet i det daglige sammendraget \"Evaluering\" -panel for å gjøre det.", + "OBSTRUCTION_SELECTION_COUNT": "Antall ganger en hindrende faktor ble valgt", + "TASKS_DONE_CREATED": "Oppgaver (ferdig / opprettet)", + "TIME_ESTIMATED": "Anslått tid", + "TIME_SPENT": "Tid brukt" + }, + "EVAL_FORM": { + "ADD_NOTE_FOR_TOMORROW": "Legg til notat for i morgen", + "DISABLE_REPEAT_EVERY_DAY": "Deaktiver gjenta hver dag", + "ENABLE_REPEAT_EVERY_DAY": "Gjenta hver dag", + "HELP_H1": "Hvorfor skal jeg bry meg?", + "HELP_LINK_TXT": "Gå til beregningsdelen", + "HELP_P1": "På tide med litt egenevaluering! Svarene dine her lagres og gir deg litt statistikk om hvordan du jobber i beregningsdelen. Videre vil forslagene til i morgen vises over oppgavelisten din neste dag.", + "HELP_P2": "Dette er ment å være mindre om å beregne nøyaktige beregninger eller bli så effektiv som mulig i alt du gjør enn om å forbedre hvordan du føler deg om arbeidet ditt. Det kan være nyttig å evaluere smertepunkter i den daglige rutinen, så vel som å finne faktorer som hjelper deg. Å være litt systematisk om det hjelper forhåpentligvis å få bedre grep om disse og forbedre det du kan.", + "IMPROVEMENTS": "Hva forbedret produktiviteten din?", + "IMPROVEMENTS_TOMORROW": "Hva kan du gjøre for å forbedre i morgen?", + "MOOD": "Hvordan føler du deg?", + "MOOD_HINT": "1: Forferdelig - 10: Fantastisk", + "NOTES": "Merknader for i morgen", + "OBSTRUCTIONS": "Hva hindret produktiviteten din?", + "PRODUCTIVITY": "Hvor effektiv jobbet du?", + "PRODUCTIVITY_HINT": "1: Har ikke en gang startet - 10: Enormt effektiv" + }, + "S": { + "SAVE_METRIC": "Måling vellykket" + } + }, + "NOTE": { + "ADD_REMINDER": "Legg til påminnelse", + "D_ADD": { + "DATETIME_LABEL": "Datetid for påminnelse (valgfritt)", + "NOTE_LABEL": "Skriv inn litt tekst for å lagre som notat ..." + }, + "D_ADD_REMINDER": { + "E_ENTER_TITLE": "Du må oppgi en tittel", + "L_DATETIME": "Datotid for påminnelse", + "L_TITLE": "Tittel for varsling" + }, + "D_VIEW_REMINDER": { + "SNOOZE": "Slumre", + "TITLE": "Merk" + }, + "EDIT_FULLSCREEN": "Rediger i fullskjerm", + "EDIT_REMINDER": "Rediger påminnelse", + "NOTES_CMP": { + "ADD_BTN": "Legg til en ny kommentar", + "DROP_TO_ADD": "Slipp her for å legge til et nytt notat" + }, + "NOTE_CMP": { + "DISABLE_PARSE": "Deaktiver analysering av markdown", + "ENABLE_PARSE": "Aktiver markdown-analyse" + }, + "REMOVE_REMINDER": "Fjern påminnelse", + "S": { + "ADDED_REMINDER": "Lagt til påminnelse for merknad", + "DELETED_REMINDER": "Slettet påminnelse for notat", + "UPDATED_REMINDER": "Oppdatert påminnelse for merknad" + }, + "UPDATE_REMINDER": "Oppdateringspåminnelse" + }, + "POMODORO": { + "BACK_TO_WORK": "Tilbake til arbeid!", + "BREAK_IS_DONE": "Pausen din er ferdig!", + "ENJOY_YOURSELF": "Kos deg, få deg i bevegelse, kom inn igjen:", + "FINISH_SESSION_X": "Du har fullført økten {{nr}} !", + "NOTIFICATION": { + "BREAK_X_START": "Pomodoro: Pause {{nr}} startet!", + "SESSION_X_START": "Pomodoro: Økten {{nr}} startet!" + }, + "S": { + "SESSION_X_START": "Pomodoro: Økten {{nr}} startet!" + }, + "SKIP_BREAK": "Hopp over pause" + }, + "PROCRASTINATION": { + "BACK_TO_WORK": "Tilbake til arbeid!", + "COMP": { + "INTRO": "Å vise deg selv litt medfølelse er alltid en god idé. Det forbedrer følelsen av egenverd, fremmer positive følelser og kan selvfølgelig hjelpe deg med å overvinne utsettelse. Prøv en liten øvelse:", + "L1": "Sett deg litt og strekk deg, hvis du vil, ro deg litt ned", + "L2": "Prøv å lytte til tankene og følelsene som oppstår", + "L3": "Svarer du på deg selv på en måte som du vil svare på en venn?", + "L4": "Hvis svaret er nei, forestill deg vennen din i din situasjon. Hva vil du si til dem? Hva ville du gjøre for dem?", + "OUTRO": "Flere øvelser finner du her eller på google .", + "TITLE": "Selvmedfølelse" + }, + "CUR": { + "INTRO": "Utsettelse er interessant, ikke sant? Det gir ikke mening å gjøre det. Ikke i din langsiktige interesse i det hele tatt. Men fortsatt gjør alle det. Kos deg og utforsk!", + "L1": "Hvilke følelser vekker fristelsen din til å utsette?", + "L2": "Hvor føler du dem i kroppen din?", + "L3": "Hva minner de deg om?", + "L4": "Hva skjer med tanken på å utsette mens du observerer det? Forsterker det seg? Forsvinne? Får andre følelser til å oppstå?", + "L5": "Hvordan skifter følelsene i kroppen din når du fortsetter å hvile bevisstheten din på dem?", + "TITLE": "Nysgjerrighet" + }, + "H1": "Slapp av litt!", + "P1": "Først av alt slapp av! Alle gjør det en gang i blant. Og hvis du ikke gjør det du burde, bør du i det minste nyte det! Ta en titt på delene nedenfor for å finne noe nyttig.", + "P2": "Hvis du vil vite mer om vitenskapen bak alt her, kan jeg anbefale denne artikkelen som er noen av øvelsene.", + "P3": "Husk: Utsettelse er et følelsesreguleringsproblem, ikke et tidsstyringsproblem.", + "REFRAME": { + "INTRO": "Tenk på hva som kan være positivt med oppgaven til tross for manglene.", + "TITLE": "Omramning", + "TL1": "Hva kan være interessant med det?", + "TL2": "Hva kan du tjene hvis du fullfører det?", + "TL3": "Hvordan vil du ha det med det hvis du fullfører det?" + }, + "SPLIT_UP": { + "INTRO": "Del oppgaven i så mange små biter du kan.", + "OUTRO": "Ferdig? Tenk så på det. Hva ville være - strengt teoretisk - det første du ville gjort hvis du skulle begynne å jobbe med oppgaven? Bare tenk på det...", + "TITLE": "Del den opp!" + } + }, + "PROJECT": { + "D_CREATE": { + "CREATE": "Lag prosjekt", + "EDIT": "Rediger prosjekt", + "SETUP_GIT": "Konfigurer Git-integrasjon", + "SETUP_GITLAB": "Konfigurer Gitlab-integrasjon", + "SETUP_JIRA": "Oppsett Jira Integration" + }, + "FORM_BASIC": { + "L_TITLE": "Prosjektnavn", + "TITLE": "Grunnleggende innstillinger" + }, + "FORM_THEME": { + "D_IS_DARK_THEME": "Vil ikke brukes hvis systemet støtter global mørk modus.", + "HELP": "Temainnstillinger for prosjektet ditt.", + "L_COLOR_ACCENT": "Aksentfarge", + "L_COLOR_PRIMARY": "Primærfarge", + "L_COLOR_WARN": "Advarsel / feilfarge", + "L_HUE_ACCENT": "Terskel for mørk tekst på aksentfargebakgrunn", + "L_HUE_PRIMARY": "Terskel for mørk tekst på primærfargebakgrunn", + "L_HUE_WARN": "Terskel for mørk tekst på advarselfargebakgrunn", + "L_IS_AUTO_CONTRAST": "Sett inn tekstfarger automatisk for best lesbarhet", + "L_IS_DISABLE_BACKGROUND_GRADIENT": "Deaktiver farget bakgrunnsforløpning", + "L_IS_REDUCED_THEME": "Bruk redusert brukergrensesnitt (ingen ruter rundt oppgaver)", + "L_THEME_COLOR": "Temafarge", + "L_TITLE": "Tittel", + "TITLE": "Tema" + }, + "S": { + "ARCHIVED": "Arkivert prosjekt", + "CREATED": "Opprettet prosjekt {{title}} . Du kan velge det fra menyen øverst til venstre.", + "DELETED": "Slettet prosjekt", + "E_EXISTS": "Prosjektet \"{{title}}\" eksisterer allerede", + "E_INVALID_FILE": "Ugyldige data for prosjektfilen", + "ISSUE_PROVIDER_UPDATED": "Oppdaterte prosjektinnstillinger for {{issueProviderKey}}", + "UNARCHIVED": "Ikke arkivert prosjekt", + "UPDATED": "Oppdaterte prosjektinnstillinger" + } + }, + "REMINDER": { + "S_REMINDER_ERR": "Feil for påminnelsesgrensesnitt" + }, + "SIMPLE_COUNTER": { + "D_CONFIRM_REMOVE": { + "MSG": "Slette en enkel teller vil også slette alle tidligere data som er sporet på den. Er du sikker på at du vil fortsette?", + "OK": "Gjør det!" + }, + "D_EDIT": { + "L_COUNTER": "Telle", + "TITLE": "Rediger enkel teller" + }, + "FORM": { + "ADD_NEW": "Legg til enkel teller", + "HELP": "Her kan du konfigurere enkle knapper som vises øverst til høyre. De kan enten være tidtakere eller bare en enkel teller, som telles opp, ved å klikke på den.", + "L_AUTO_COUNT_UP": "Automatisk utløsertelling opp for", + "L_AUTO_SWITCH_OFF": "Automatisk utkobling for", + "L_AUTO_SWITCH_ON": "Automatisk utløserbryter på for", + "L_ICON": "Ikon", + "L_ICON_ON": "Ikon når det er slått på", + "L_IS_ENABLED": "Aktivert", + "L_TITLE": "Tittel", + "L_TYPE": "Type", + "TITLE": "Enkle tellere", + "TYPE_CLICK_COUNTER": "Klikk på teller", + "TYPE_STOPWATCH": "Stoppeklokke" + } + }, + "TAG": { + "D_CREATE": { + "CREATE": "Opprett tag", + "EDIT": "Rediger tag" + }, + "D_DELETE": { + "CONFIRM_MSG": "Vil du virkelig slette koden \"{{tagName}}\"? Det vil bli fjernet fra alle oppgaver. Dette kan ikke angres." + }, + "D_EDIT": { + "ADD": "Legg til koder for \"{{title}}\"", + "EDIT": "Rediger tagger for \"{{title}}\"", + "LABEL": "Merker" + }, + "FORM_BASIC": { + "L_COLOR": "Farge (hvis udefinert primær temafarge brukes)", + "L_ICON": "Ikon", + "L_TITLE": "Merkets navn", + "TITLE": "Grunnleggende innstillinger" + }, + "S": { + "UPDATED": "Tag-innstillinger ble oppdatert" + } + }, + "TASK": { + "ADDITIONAL_INFO": { + "ADD_ATTACHMENT": "Legg til vedlegg", + "ADD_SUB_TASK": "Legg til underoppgave", + "ATTACHMENTS": "Vedlegg ({{nr}})", + "FROM_PARENT": "(fra forelder)", + "LOCAL_ATTACHMENTS": "Lokale vedlegg", + "NOTES": "Merknader", + "PARENT": "Foreldre", + "REMINDER": "Påminnelse", + "REPEAT": "Gjenta", + "SCHEDULE_TASK": "Planlegg oppgave", + "SUB_TASKS": "Underoppgaver ({{nr}})", + "TIME": "Tid" + }, + "ADD_TASK_BAR": { + "ADD_EXISTING_TASK": "Legg til eksisterende oppgave \"{{taskTitle}}\"", + "ADD_ISSUE_TASK": "Legg til utgave nr. {{issueNr}} fra {{issueType}}", + "ADD_TASK": "Legg til oppgave", + "ADD_TASK_TO_BACKLOG": "Legg til oppgave i backloggen", + "CREATE_TASK": "Lag ny oppgave", + "EXAMPLE": "Eksempel: \"Noen oppgavetittel + prosjektnavn #en tag #en annen tag 10m / 3h\"", + "START": "Trykk enter en gang til for å starte" + }, + "B": { + "ADD_HALF_HOUR": "Tilsett 1/2 time", + "ESTIMATE_EXCEEDED": "Tidsestimatet overskredet for \"{{title}}\"" + }, + "CMP": { + "ADD_SUB_TASK": "Legg til underoppgave", + "ADD_TO_MY_DAY": "Legg til dagen min", + "ADD_TO_PROJECT": "Legg til et prosjekt", + "CONVERT_TO_PARENT_TASK": "Konverter til foreldreoppgave", + "DELETE": "Slett oppgave", + "DROP_ATTACHMENT": "Slipp her for å legge ved \"{{title}}\"", + "EDIT_REMINDER": "Rediger påminnelse", + "EDIT_TAGS": "Rediger koder", + "MARK_DONE": "Marker som ferdig", + "MARK_UNDONE": "Merk som ugjort", + "MOVE_TO_BACKLOG": "Gå til backlog", + "MOVE_TO_OTHER_PROJECT": "Gå til et annet prosjekt", + "MOVE_TO_TODAY": "Gå til dagens liste", + "OPEN_ATTACH": "Legg ved fil eller lenke", + "OPEN_ISSUE": "Åpne i nettleseren", + "OPEN_TIME": "Tidssporing", + "REMOVE_FROM_MY_DAY": "Fjern fra Min dag", + "REPEAT_EDIT": "Rediger konfigurasjon for gjentatt oppgave", + "SCHEDULE": "Planlegg oppgaven", + "SHOW_UPDATES": "Vis oppdateringer", + "TOGGLE_ADDITIONAL": "Vis / skjul tilleggsinformasjon", + "TOGGLE_ATTACHMENTS": "Vis / skjul vedlegg", + "TOGGLE_DONE": "Merk som ferdig / angre", + "TOGGLE_SUB_TASK_VISIBILITY": "Bytt synlighet av underoppgave", + "TRACK_TIME": "Begynn å spore tid", + "TRACK_TIME_STOP": "Sett sporingstid på pause", + "UPDATE_ISSUE_DATA": "Oppdater data om problemet" + }, + "D_REMINDER_ADD": { + "DATETIME_FOR": "Datotid for påminnelse", + "EDIT": "Rediger påminnelse", + "MOVE_TO_BACKLOG": "Flytt oppgaven til backlogg helt til planlagt", + "SCHEDULE": "Legg til planlagt", + "UNSCHEDULE": "Fjern fra planlagt" + }, + "D_REMINDER_VIEW": { + "ADD_ALL_TO_TODAY": "Legg alt til i dag", + "ADD_TO_TODAY": "Legg til i dag", + "DISMISS": "Avvis påminnelse", + "DISMISS_ALL": "Avvis alle", + "DUE_TASK": "Forfalt oppgave", + "DUE_TASKS": "Forfalte oppgaver", + "FOR_CURRENT": "Oppgaven skal utføres. Vil du begynne å jobbe med det?", + "FOR_OTHER": "Oppgaven skal utføres. Vil du begynne å jobbe med det?", + "FROM_PROJECT": "Fra prosjekt: \"{{title}}\"", + "FROM_TAG": "Fra tag: \"{{title}}\"", + "SNOOZE": "Slumre", + "SNOOZE_ALL": "Utsett alle", + "SNOOZE_UNTIL_TOMORROW": "Til i morgen", + "START": "Start", + "SWITCH_CONTEXT_START": "Bytt kontekst og start" + }, + "D_TIME": { + "ADD_FOR_OTHER_DAY": "Legg til tid brukt den andre dagen", + "DELETE_FOR": "Slett oppføringen for dagen", + "ESTIMATE": "Anslag", + "TIME_SPENT": "Tid brukt", + "TIME_SPENT_ON": "Tidsbruk {{date}}", + "TITLE": "Tidsbruk / estimater" + }, + "D_TIME_FOR_DAY": { + "ADD_ENTRY_FOR": "Legg til ny oppføring for {{date}}", + "DATE": "Dato for nyoppføring", + "HELP": "Eksempler:
30m => 30 minutter
2t => 2 timer
2t 30m => 2 timer og 30 minutter", + "TINE_SPENT": "Tid brukt", + "TITLE": "Legg til for dagen" + }, + "N": { + "ESTIMATE_EXCEEDED": "Tidsoverslag overskredet!", + "ESTIMATE_EXCEEDED_BODY": "Du overskred beregnet tid for \"{{title}}\"." + }, + "S": { + "DELETED": "Slettet oppgave \"{{title}}\"", + "FOUND_MOVE_FROM_BACKLOG": "Flyttet oppgave {{title}} fra backlog til dagens oppgaveliste", + "FOUND_MOVE_FROM_OTHER_LIST": "Lagt til oppgave {{title}} fra {{contextTitle}} til gjeldende liste", + "FOUND_RESTORE_FROM_ARCHIVE": "Gjenopprettet oppgave {{title}} relatert til utgave fra arkivet", + "LAST_TAG_DELETION_WARNING": "Du prøver å fjerne den siste taggen for en ikke-prosjektoppgave. Dette er ikke tillatt!", + "REMINDER_ADDED": "Planlagt oppgave \"{{title}}\"", + "REMINDER_DELETED": "Slettet påminnelse for oppgaven", + "REMINDER_UPDATED": "Oppdatert påminnelse for oppgaven \"{{title}}\"", + "TASK_CREATED": "Opprettet oppgaven \"{{title}}\"" + }, + "SELECT_OR_CREATE": "Velg eller opprett oppgave", + "SUMMARY_TABLE": { + "ESTIMATE": "anslag", + "SPENT_TODAY": "Brukte i dag", + "SPENT_TOTAL": "Brukte totalt", + "TASK": "Oppgave", + "TOGGLE_DONE": "av- / merk som ferdig" + } + }, + "TASK_REPEAT": { + "D_CONFIRM_REMOVE": { + "MSG": "Hvis du fjerner gjenta konfigurasjonen, konverteres alle tidligere forekomster av denne oppgaven til bare vanlige oppgaver. Er du sikker på at du vil fortsette", + "OK": "Fjern helt" + }, + "D_EDIT": { + "ADD": "Legg til gjenta oppgavekonfigurasjon", + "EDIT": "Rediger gjenta oppgavekonfigurasjon", + "HELP1": "Gjentatte oppgaver er ment for daglige gjøremål, for eksempel: \"Organisasjon\", \"Daglig møte\", \"Kodegjennomgang\", \"Kontrollerer e-post\" eller lignende oppgaver som sannsynligvis vil oppstå igjen og igjen.", + "HELP2": "Når den er konfigurert, blir en gjentatt oppgave gjenskapt hver dag valgt nedenfor så snart du åpner prosjektet, og vil automatisk bli merket som fullført på slutten av dagen. De vil bli håndtert som forskjellige forekomster. Så du kan fritt legge til underoppgaver etc.", + "HELP3": "Oppgaver importert fra Jira eller Git Issues kan ikke gjentas. Alle påminnelser vil også bli slettet på en gjentatt oppgave.", + "TAG_LABEL": "Merker å legge til" + }, + "F": { + "DEFAULT_ESTIMATE": "Standard estimat", + "FRIDAY": "fredag", + "IS_ADD_TO_BOTTOM": "Flytt oppgaven til bunnen av listen", + "MONDAY": "mandag", + "SATURDAY": "lørdag", + "SUNDAY": "søndag", + "THURSDAY": "Torsdag", + "TITLE": "Tittel for oppgave", + "TUESDAY": "tirsdag", + "WEDNESDAY": "onsdag" + } + }, + "TIME_TRACKING": { + "B": { + "ALREADY_DID": "Det gjorde jeg allerede", + "SNOOZE": "Utsett {{time}}" + }, + "B_TTR": { + "ADD_TO_TASK": "Legg til oppgave", + "MSG": "Du har ikke sporet tid for {{time}}" + }, + "D_IDLE": { + "BREAK": "Pause", + "CREATE_AND_TRACK": "Opprette og spore til", + "IDLE_FOR": "Du har vært inaktiv for:", + "SKIP": "Hopp over", + "TASK": "Oppgave", + "TASK_BREAK": "Oppgave + pause", + "TRACK_TO": "Spor til" + }, + "D_TRACKING_REMINDER": { + "UNTRACKED_TIME": "Usporet tid:", + "TRACK_TO": "Spor til:", + "CREATE_AND_TRACK": "Opprette og spore til", + "IDLE_FOR": "Du har vært inaktiv for:", + "TASK": "Oppgave" + } + }, + "WORKLOG": { + "CMP": { + "DAYS_WORKED": "Dager, jobbet:", + "MONTH_WORKED": "Arbeidet måned:", + "REPEATING_TASK": "Gjenta oppgaven", + "RESTORE_TASK_FROM_ARCHIVE": "Gjenopprett oppgave fra arkiv", + "TASKS": "Oppgaver", + "TOTAL_TIME": "Totalt brukt tid:", + "WEEK_NR": "Uke {{nr}}", + "WORKED": "Jobbet" + }, + "D_CONFIRM_RESTORE": "Er du sikker på at du vil flytte oppgaven "{{title}}" til dagens oppgaveliste?", + "D_EXPORT_TITLE": "Eksporter arbeidslogg {{start}} - {{end}}", + "D_EXPORT_TITLE_SINGLE": "Eksporter arbeidslogg {{day}}", + "EXPORT": { + "ADD_COL": "Legg til kolonne", + "COPY_TO_CLIPBOARD": "Kopiere til utklippstavle", + "DONT_ROUND": "ikke rund av", + "EDIT_COL": "Rediger kolonne", + "GROUP_BY": "Gruppe av", + "O": { + "DATE": "Dato", + "ENDED_WORKING": "Sluttet å jobbe", + "ESTIMATE_AS_CLOCK": "Beregn som klokke (f.eks. 5:23)", + "ESTIMATE_AS_MILLISECONDS": "Beregn som millisekunder", + "ESTIMATE_AS_STRING": "Anslag som streng (f.eks. 5t 23m)", + "FULL_HALF_HOURS": "hele halvtimen", + "FULL_HOURS": "hele timer", + "FULL_QUARTERS": "hele kvartalet", + "PARENT_TASK": "Foreldreoppgave", + "PARENT_TASK_TITLES_ONLY": "Bare foreldreoppgavetitler", + "STARTED_WORKING": "Begynte å jobbe", + "TASK_SUBTASK": "Oppgave / deloppgave", + "TIME_AS_CLOCK": "Tid som klokke (f.eks. 5:23)", + "TIME_AS_MILLISECONDS": "Tid som millisekunder", + "TIME_AS_STRING": "Tid som streng (f.eks. 5t 23m)", + "TITLES_AND_SUB_TASK_TITLES": "Titler og underoppgavetitler", + "WORKLOG": "Arbeidslogg" + }, + "OPTIONS": "Alternativer", + "ROUND_END_TIME_TO": "Rund sluttid til", + "ROUND_START_TIME_TO": "Rund starttid til", + "ROUND_TIME_WORKED_TO": "Rundtiden jobbet til", + "SAVE_TO_FILE": "Lagre som fil", + "SEPARATE_TASKS_BY": "Skille oppgaver med", + "SHOW_AS_TEXT": "Vis som tekst" + }, + "WEEK": { + "EXPORT": "Eksporter ukedata", + "NO_DATA": "Ingen oppgaver denne uken ennå.", + "TITLE": "Tittel" + } + } + }, + "FILE_IMEX": { + "EXPORT_DATA": "Eksporter data", + "IMPORT_FROM_FILE": "Importer fra fil", + "S_ERR_INVALID_DATA": "Import mislyktes: Ugyldig JSON" + }, + "G": { + "CANCEL": "Avbryt", + "CLICK_TO_EDIT": "klikk for å redigere", + "CLOSE": "Lukk", + "DELETE": "Slett", + "DISMISS": "Avvis", + "DO_IT": "Gjør det!", + "EDIT": "Redigere", + "EXTENSION_INFO": "Last ned chrome-utvidelsen for å tillate kommunikasjon med Jira Api og Idle Time Handling. Merk at dette ikke fungerer for mobil.", + "LOGIN": "Logg Inn", + "LOGOUT": "Logg ut", + "MINUTES": "{{m}} minutter", + "NEXT": "Neste", + "NONE": "Ingen", + "NO_CON": "Du er for øyeblikket frakoblet. Koble til Internett på nytt.", + "OK": "Ok", + "PREVIOUS": "Tidligere", + "REMOVE": "Ta vekk", + "RESET": "Nullstille", + "SAVE": "Lagre", + "TITLE": "Tittel", + "UNDO": "Angre", + "UPDATE": "Oppdater", + "WITHOUT_PROJECT": "Uten prosjekt" + }, + "GCF": { + "AUTO_BACKUPS": { + "HELP": "Lagre alle data automatisk i appmappen din for å ha den klar i tilfelle noe går galt.", + "LABEL_IS_ENABLED": "Aktiver automatiske sikkerhetskopier", + "LOCATION_INFO": "Sikkerhetskopier lagres i:", + "TITLE": "Automatiske sikkerhetskopier" + }, + "EVALUATION": { + "IS_HIDE_EVALUATION_SHEET": "Skjul evalueringsark på daglig sammendrag", + "TITLE": "Evaluering og beregninger" + }, + "GOOGLE_DRIVE_SYNC": { + "HELP": "Her kan du konfigurere appen din til automatisk å synkronisere til og fra en enkelt Google Drive-fil. Alle data lagres ukryptert, så pass på at du ikke ved en feiltakelse deler denne filen med noen.", + "TITLE": "Synkroniser via Google Disk" + }, + "IDLE": { + "HELP": "

Når håndtering av inaktiv tid er aktivert, åpnes en dialog etter en spesifisert tid for å sjekke om og på hvilken oppgave du vil spore tiden din, når du har vært inaktiv.

", + "IS_ENABLE_IDLE_TIME_TRACKING": "Aktiver håndtering av inaktiv tid", + "IS_ONLY_OPEN_IDLE_WHEN_CURRENT_TASK": "Bare utløs tomgangsdialog når en aktuell oppgave er valgt", + "IS_UN_TRACKED_IDLE_RESETS_BREAK_TIMER": "Usporet inaktiv tid tilbakestiller 'ta en pause'-timer", + "MIN_IDLE_TIME": "Utløser tomgang etter X", + "TITLE": "Tomgangshåndtering" + }, + "IMEX": { + "HELP": "

Her kan du eksportere alle dataene dine som en JSON for sikkerhetskopiering, men også for å bruke dem i en annen kontekst (f.eks. Vil du kanskje eksportere prosjektene dine i nettleseren og importere dem til desktopversjonen).

Importen forventer at gyldig JSON kopieres til tekstområdet. MERKNAD: Når du trykker på importknappen, overskrives alle dine nåværende innstillinger og data!

", + "TITLE": "Import Eksport" + }, + "KEYBOARD": { + "ADD_NEW_NOTE": "Legg til et nytt notat", + "ADD_NEW_TASK": "Legg til ny oppgave", + "APP_WIDE_SHORTCUTS": "Globale snarveier (applikasjonsbredt)", + "COLLAPSE_SUB_TASKS": "Skjul underoppgaver", + "EXPAND_SUB_TASKS": "Utvid underoppgaver", + "GLOBAL_ADD_NOTE": "Legg til et nytt notat", + "GLOBAL_ADD_TASK": "Legg til en ny oppgave", + "GLOBAL_SHOW_HIDE": "Vis / skjul Super Productivity", + "GLOBAL_TOGGLE_TASK_START": "Bytt tidssporing for siste aktive oppgave", + "GO_TO_DAILY_AGENDA": "Gå til Agenda", + "GO_TO_FOCUS_MODE": "Gå til Fokusmodus", + "GO_TO_SETTINGS": "Gå til Innstillinger", + "GO_TO_WORK_VIEW": "Gå til arbeidsvisning", + "HELP": "

Her kan du konfigurere alle hurtigtaster.

Klikk på tekstinntastingen og skriv inn ønsket tastaturkombinasjon. Trykk på Enter for å lagre og Escape for å avbryte.

Det er tre typer snarveier:

  • Globale snarveier: Når appen kjører, vil den utløse handlingen fra alle andre applikasjoner.
  • Snarveier på applikasjonsnivå: Utløses fra alle skjermbilder i applikasjonen, men ikke hvis du for øyeblikket redigerer et tekstfelt.
  • Snarveier på oppgavennivå: De utløses bare hvis du har valgt en oppgave via mus eller tastatur og vanligvis utløser en handling spesifikt relatert til den ene oppgaven.
", + "MOVE_TASK_DOWN": "Flytt Oppgave ned i Liste", + "MOVE_TASK_UP": "Flytt oppgaven oppover i listen", + "MOVE_TO_BACKLOG": "Flytt oppgave til oppgavestandard", + "MOVE_TO_TODAYS_TASKS": "Flytt oppgave til dagens oppgaveliste", + "OPEN_PROJECT_NOTES": "Vis / skjul prosjektnotater", + "SELECT_NEXT_TASK": "Velg neste oppgave", + "SELECT_PREVIOUS_TASK": "Velg forrige oppgave", + "SYSTEM_SHORTCUTS": "Globale snarveier (hele systemet)", + "TASK_ADD_SUB_TASK": "Legg til underoppgave", + "TASK_DELETE": "Slett oppgave", + "TASK_EDIT_TITLE": "Rediger tittel", + "TASK_MOVE_TO_PROJECT": "Åpne flytt oppgave til prosjektmeny", + "TASK_OPEN_ESTIMATION_DIALOG": "Rediger estimering / tid brukt", + "TASK_SCHEDULE": "Planlegg oppgave", + "TASK_SHORTCUTS": "Oppgaver", + "TASK_SHORTCUTS_INFO": "Følgende snarveier gjelder for den valgte oppgaven (valgt via fane eller mus).", + "TASK_TOGGLE_ADDITIONAL_INFO_OPEN": "Vis / skjul tilleggsinformasjon", + "TASK_TOGGLE_DONE": "Slå på Ferdig", + "TITLE": "Tastatursnarveier", + "TOGGLE_BACKLOG": "Vis / skjul oppgavestand", + "TOGGLE_BOOKMARKS": "Vis / skjul bokmerkefelt", + "TOGGLE_PLAY": "Start / stopp oppgave", + "ZOOM_DEFAULT": "Zoomstandard (bare skrivebord)", + "ZOOM_IN": "Zoom inn (bare skrivebordet)", + "ZOOM_OUT": "Zoom ut (bare skrivebordet)" + }, + "LANG": { + "AR": "Arabisk", + "DE": "Tysk", + "EN": "Engelsk", + "ES": "Spansk", + "FA": "Farsi", + "FR": "Fransk", + "IT": "Italiensk", + "JA": "Japansk", + "KO": "Koreansk", + "LABEL": "Velg et språk", + "NB":"Norsk Bokmål", + "NL": "Nederlandsk", + "PT": "Portugisisk", + "RU": "Russisk", + "TITLE": "Språk", + "TR": "Tyrkisk", + "ZH": "Kinesisk" + }, + "MISC": { + "DEFAULT_PROJECT": "Standard prosjekt som skal brukes til oppgaver hvis ingen er spesifisert", + "HELP": "

Ser du ikke skrivebordsvarsler? For Windows vil du kanskje sjekke System> Varsler og handlinger og sjekke om de nødvendige varslene er aktivert.

", + "IS_AUTO_ADD_WORKED_ON_TO_TODAY": "Legg til dagens tag automatisk for å jobbe med oppgaver", + "IS_AUTO_MARK_PARENT_AS_DONE": "Merk foreldreoppgaven som ferdig når alle underoppgaver er utført", + "IS_AUTO_START_NEXT_TASK": "Begynn å spore neste oppgave når du merker nåværende som ferdig", + "IS_CONFIRM_BEFORE_EXIT": "Bekreft før du avslutter appen", + "IS_DARK_MODE": "Mørk modus", + "IS_DISABLE_INITIAL_DIALOG": "Deaktiver innledende infodialog (du kan gå glipp av viktige oppdateringer!)", + "IS_HIDE_NAV": "Skjul navigasjonen til hovedoverskriften er svevet (kun på skrivebordet)", + "IS_NOTIFY_WHEN_TIME_ESTIMATE_EXCEEDED": "Gi beskjed når tidsestimatet ble overskredet", + "IS_TURN_OFF_MARKDOWN": "Slå av Markdown-parsering for notater", + "TITLE": "Diverse innstillinger", + "FIRST_DAY_OF_WEEK": "Første ukedag" + }, + "POMODORO": { + "BREAK_DURATION": "Varighet av korte pauser", + "CYCLES_BEFORE_LONGER_BREAK": "Start lengre pause etter X-arbeidsøkter", + "DURATION": "Varigheten av arbeidsøktene", + "HELP": "

Pomodoro-timeren kan konfigureres via et par innstillinger. Varigheten av hver arbeidsøkt, varigheten av normale pauser, antall arbeidsøkter som skal kjøres før en lengre pause startes og varigheten av denne lengre pausen.

Du kan også angi om du vil vise distraksjoner i pomodoro-pausene dine.

Innstilling av "Pause tidssporing på pomodoro pause" vil også spore pausene dine som arbeidstid brukt på en oppgave.

Aktivering av "Pause pomodoro-økt midlertidig når ingen aktiv oppgave er aktivert" vil også pomodoro-økten pause når du setter en oppgave på pause.

", + "IS_ENABLED": "Aktiver pomodoro", + "IS_MANUAL_CONTINUE": "Bekreft manuelt start av neste pomodoro-økt", + "IS_PLAY_SOUND": "Spill lyd når økten er ferdig", + "IS_PLAY_SOUND_AFTER_BREAK": "Spill lyd når pause er ferdig", + "IS_PLAY_TICK": "Spill av krysslyd hvert sekund", + "IS_STOP_TRACKING_ON_BREAK": "Stopp tidsporing for oppgave på pause", + "LONGER_BREAK_DURATION": "Varighet av lengre pauser", + "TITLE": "Pomodoro Timer" + }, + "SOUND": { + "DONE_SOUND": "Oppgavelyd", + "IS_INCREASE_DONE_PITCH": "Øk tonehøyde for hver oppgave", + "IS_PLAY_DONE_SOUND": "Spill lyd når oppgaven er merket som ferdig", + "TITLE": "Lyd", + "VOLUME": "Volum" + }, + "TAKE_A_BREAK": { + "HELP": "

Lar deg konfigurere en påminnelse som kommer igjen når du har jobbet i en spesifisert tid uten å ta en pause.

Du kan endre meldingen som vises. $ {duration} blir erstattet med tiden brukt uten pause.

", + "IS_ENABLED": "Aktiver påminnelse om å ta en pause", + "IS_FOCUS_WINDOW": "Fokuser appvinduet når påminnelsen er aktiv (kun på skrivebordet)", + "IS_LOCK_SCREEN": "Låseskjerm når en pause er forfalt (kun på skrivebordet)", + "MESSAGE": "Ta en pause-melding", + "MIN_WORKING_TIME": "Trigger ta en pause varsel etter X arbeider uten en", + "MOTIVATIONAL_IMG": "Motiverende bilde (nettadresse)", + "TITLE": "Pausepåminnelse" + }, + "TRACKING_REMINDER": { + "HELP": "Konfigurer et banner for å vises i tilfelle du glemte å starte tidssporing.", + "TITLE": "Påminnelse om tidssporing", + "L_IS_ENABLED": "Aktivert", + "L_IS_SHOW_ON_MOBILE": "Vis på mobilappen", + "L_MIN_TIME": "Tid til å vente før du viser Banner" + } + }, + "GLOBAL_SNACK": { + "COPY_TO_CLIPPBOARD": "Kopiert til utklippstavlen", + "ERR_COMPRESSION": "Feil for kompresjonsgrensesnitt", + "PERSISTENCE_DISALLOWED": "Data vil ikke vedvares permanent. Vær oppmerksom på at dette kan føre til tap av data !!", + "RUNNING_X": "Kjører \"{{str}}\".", + "SHORTCUT_WARN_OPEN_BOOKMARKS_FROM_TAG": "{{keyCombo}} ble trykket, men åpne bokmerkesnarvei er bare tilgjengelig i prosjektsammenheng.", + "SHORTCUT_WARN_OPEN_NOTES_FROM_TAG": "{{keyCombo}} ble trykket, men snarveien til åpne notater er bare tilgjengelig i prosjektsammenheng." + }, + "GPB": { + "ASSETS": "Laster inn ressurser ...", + "DBX_DOWNLOAD": "Dropbox: Last ned fil ...", + "DBX_GEN_TOKEN": "Dropbox: Generer token ...", + "DBX_META": "Dropbox: Få metadata om fil ...", + "DBX_UPLOAD": "Dropbox: Last opp fil ...", + "GDRIVE_DOWNLOAD": "Google Drive: Last ned fil ...", + "GDRIVE_UPLOAD": "Google Drive: Last opp fil ...", + "GITHUB_LOAD_ISSUE": "Github: Last problemdata ...", + "JIRA_LOAD_ISSUE": "Jira: Last inn data ..." + }, + "MH": { + "ADD_NEW_TASK": "Legg til ny oppgave", + "CREATE_PROJECT": "Lag prosjekt", + "CREATE_TAG": "Opprett tag", + "DELETE_TAG": "Slett tag", + "GO_TO_TASK_LIST": "Gå til oppgavelisten", + "MANAGE_PROJECTS": "Administrer prosjekter", + "METRICS": "Beregninger", + "NOTES": "Merknader", + "PROCRASTINATE": "Utsette", + "PROJECTS": "Prosjekter", + "PROJECT_MENU": "Prosjektmeny", + "PROJECT_SETTINGS": "Prosjektinnstillinger", + "SCHEDULED": "Planlagt", + "SETTINGS": "Innstillinger", + "TAGS": "Merker", + "TASKS": "Oppgaver", + "TASK_LIST": "Oppgaveliste", + "TOGGLE_SHOW_BOOKMARKS": "Vis / skjul bokmerker", + "TOGGLE_SHOW_NOTES": "Vis / skjul prosjektnotater", + "TOGGLE_TRACK_TIME": "Start / stopp sporingstid", + "WORKLOG": "Arbeidslogg" + }, + "PDS": { + "BACK": "Vent, jeg glemte noe!", + "BREAK_LABEL": "Pause (nr / tid)", + "CELEBRATE": "Ta deg tid til å feire!", + "CLEAR_ALL_CONTINUE": "Fjern alt ferdig og fortsett", + "D_CONFIRM_APP_CLOSE": { + "CANCEL": "Nei, bare fjern oppgavene", + "MSG": "Arbeidet ditt er gjort. På tide å gå hjem!", + "OK": "Jepp! Skru av!" + }, + "EVALUATION": "Evaluering", + "EXPORT_TASK_LIST": "Eksporter oppgaveliste", + "NO_TASKS": "Det er ingen oppgaver for denne dagen", + "PLAN": "Plan", + "ROUND_15M": "Avrund alle oppgavene til 15 minutter", + "ROUND_30M": "Avrund alle oppgavene til 30 minutter", + "ROUND_5M": "Avrund alle oppgavene til 5 minutter", + "ROUND_TIME_SPENT": "Avrund tid brukt", + "ROUND_TIME_SPENT_TITLE": "Avrund tid brukt på alle oppgaver. Vær forsiktig! Du kan ikke angre dette!", + "ROUND_TIME_WARNING": "!!! Vær forsiktig, dette kan ikke angres !!!", + "ROUND_UP_15M": "Avrund opp alle oppgavene til 15 minutter", + "ROUND_UP_30M": "Avrund opp alle oppgavene til 30 minutter", + "ROUND_UP_5M": "Avrund opp alle oppgavene til 5 minutter", + "SAVE_AND_GO_HOME": "Lagre og dra hjem", + "START_END": "Start - Slutt", + "SUMMARY_FOR": "Daglig sammendrag for {{dayStr}}", + "TASKS_COMPLETED": "Oppgaver fullført", + "TASK_LIST": "Oppgaveliste", + "TIME_SPENT_AND_ESTIMATE_LABEL": "Tid brukt / estimert", + "TIME_SPENT_ESTIMATE_TITLE": "Tidsbruk: Total tid brukt i dag. Arkiverte oppgaver er ikke inkludert. - Beregnet tid: Anslått tid for oppgaver som jobbes med i dag minus tiden som allerede er brukt sammen med dem andre dager.", + "TODAY": "I dag", + "WEEK": "Uke" + }, + "PM": { + "TITLE": "Prosjektstatistikk" + }, + "PP": { + "ARCHIVED_PROJECTS": "Arkiverte prosjekter", + "ARCHIVE_PROJECT": "Arkivprosjekt", + "CREATE_NEW": "Lag ny", + "DELETE_PROJECT": "Slett prosjekt", + "D_CONFIRM_ARCHIVE": { + "MSG": "Er du sikker på at du vil arkivere dette prosjektet? Arkiverte prosjekter vises ikke lenger i menyen, og du kan ikke få tilgang til dataene deres uten å gjenopprette dem. Det anbefales at du sikkerhetskopierer dataene dine før du gjør dette.", + "OK": "Arkiv" + }, + "D_CONFIRM_DELETE": { + "MSG": "Er du sikker på at du vil slette dette prosjektet? Det anbefales å sikkerhetskopiere dataene dine før du gjør dette.", + "OK": "Slett" + }, + "D_CONFIRM_UNARCHIVE": { + "MSG": "Er du sikker på at du vil fjerne arkivet for dette prosjektet?", + "OK": "Unarkiv" + }, + "EDIT_PROJECT": "Rediger prosjekt", + "EXPORT_PROJECT": "Eksporter prosjekt", + "GITHUB_CONFIGURED": "Github-integrasjon konfigurert", + "GITLAB_CONFIGURED": "Gitlab-integrasjon konfigurert", + "IMPORT_FROM_FILE": "Importer fra fil", + "JIRA_CONFIGURED": "Jira Integration konfigurert", + "S_INVALID_JSON": "Ugyldig json for prosjektfilen", + "TITLE": "Prosjektoversikt", + "UN_ARCHIVE_PROJECT": "Unarchive Project" + }, + "PS": { + "GLOBAL_SETTINGS": "Globale innstillinger", + "ISSUE_INTEGRATION": "Problem integrering", + "PRIVATE_POLICY": "Privat policy", + "PRODUCTIVITY_HELPER": "Produktivitetshjelper", + "PROJECT_SETTINGS": "Prosjektspesifikke innstillinger", + "SYNC_EXPORT": "Synkroniser og eksporter", + "TAG_SETTINGS": "Merk spesifikke innstillinger", + "TOGGLE_DARK_MODE": "Bytt mørk modus" + }, + "S": { + "SYNC": { + "ERROR_FALLBACK_TO_BACKUP": "Noe gikk galt under dataimporten. Faller tilbake til lokal sikkerhetskopi.", + "ERROR_INVALID_DATA": "Feil under synkronisering. Ugyldig data", + "IMPORTING": "Importerer data", + "SUCCESS": "Data importert" + } + }, + "SCHEDULE": { + "NO_SCHEDULED": "Det er for øyeblikket ingen planlagte oppgaver. Du kan planlegge en oppgave ved å velge \"Planlegg oppgave\" i oppgavens kontekstmeny. For å åpne den, klikk på de tre små prikkene til høyre for en oppgave.", + "START_TASK": "Start oppgaven og fjern påminnelsen" + }, + "THEMES": { + "SELECT_THEME": "Velg Tema", + "amber": "rav", + "blue": "blå", + "blue-grey": "blågrå", + "cyan": "turkis", + "deep-orange": "dyp oransje", + "deep-purple": "mørk lilla", + "green": "grønn", + "indigo": "indigo", + "light-blue": "lyseblå", + "light-green": "lysegrønn", + "lime": "kalk", + "pink": "rosa", + "purple": "lilla", + "teal": "blågrønn", + "yellow": "gul" + }, + "V": { + "E_1TO10": "Angi en verdi mellom 1 og 10", + "E_DATETIME": "Den angitte verdien er ikke en datotid!", + "E_MAX": "Bør ikke være større enn {{val}}", + "E_MAX_LENGTH": "Bør være maks {{val}} tegn lang", + "E_MIN": "Bør være mindre enn {{val}}", + "E_MIN_LENGTH": "Bør ha minst {{val}} tegn", + "E_PATTERN": "Ugyldig inndata", + "E_REQUIRED": "Dette feltet er obligatorisk" + }, + "WW": { + "ADD_MORE": "Legg til mer", + "ADD_SOME_TASKS": "Legg til noen oppgaver for å planlegge dagen din!", + "COMPLETED_TASKS": "Fullførte oppgaver", + "ESTIMATE_REMAINING": "Anslått gjenværende:", + "FINISH_DAY": "Avslutt dagen", + "HELP_PROCRASTINATION": "Hjelp jeg utsetter!", + "NO_COMPLETED_TASKS": "Det er for tiden ingen fullførte oppgaver", + "NO_PLANNED_TASKS": "Ingen planlagte oppgaver", + "READY_TO_WORK": "Klar til å arbeide!", + "RESET_BREAK_TIMER": "Tilbakestill uten pausetimer", + "TIME_ESTIMATED": "Anslått tid:", + "WITHOUT_BREAK": "Uten pause:", + "WORKING_TODAY": "Jobber i dag:" + } +} diff --git a/src/assets/i18n/nl.json b/src/assets/i18n/nl.json index c6f563dd1..6a7639bb0 100644 --- a/src/assets/i18n/nl.json +++ b/src/assets/i18n/nl.json @@ -826,7 +826,7 @@ "DISMISS": "Afwijzen", "DO_IT": "Doe het!", "EDIT": "Bewerk", - "EXTENSION_INFO": " Download de Chrome-extensie om communicatie met de Jira Api en Idle Time Handling mogelijk te maken. Merk op dat dit niet werkt voor mobiel.", + "EXTENSION_INFO": " Download de Chrome-extensie om communicatie met de Jira Api en Idle Time Handling mogelijk te maken. Merk op dat dit niet werkt voor mobiel.", "LOGIN": "Log in", "LOGOUT": "Uitloggen", "MINUTES": "{{m}} minuten", @@ -922,6 +922,7 @@ "JA": "Japans", "KO": "Koreaans", "LABEL": "Selecteer een taal", + "NB": "Noors Bokmål", "NL": "Nederlands", "PT": "Portugees", "RU": "Russisch", diff --git a/src/assets/i18n/pt.json b/src/assets/i18n/pt.json index c1ef0dea7..eff725664 100644 --- a/src/assets/i18n/pt.json +++ b/src/assets/i18n/pt.json @@ -5,12 +5,12 @@ "INSTALL": "Instalar", "MSG": "Deseja instalar Super Productivity como PWA?" }, - "B_OFFLINE": "Você está desconectado da internet. Sincronizar e solicitar dados do provedor de problemas não funcionará.", + "B_OFFLINE": "Você está desconectado da internet. A sincronização e solicitação de dados do provedor de problemas não funcionará.", "D_INITIAL": { "TITLE": "Bem-vindo à v{{nr}}" }, - "UPDATE_MAIN_MODEL": "Super Produtividade recebeu uma grande atualização! Algumas migrações para seus dados são necessárias. Observe que isso torna seus dados incompatíveis com versões mais antigas do aplicativo.", - "UPDATE_MAIN_MODEL_NO_UPDATE": "Nenhuma atualização de modelo escolhida. Observe que você deve fazer o downgrade para a última versão, se não desejar executar a atualização do modelo.", + "UPDATE_MAIN_MODEL": "Super Productivity recebeu uma grande atualização! Algumas migrações para seus dados são necessárias. Observe que isso torna seus dados incompatíveis com versões mais antigas do aplicativo.", + "UPDATE_MAIN_MODEL_NO_UPDATE": "Nenhuma atualização de modelo escolhida. Note que você deve fazer o downgrade para a última versão, se não desejar executar a atualização do modelo.", "UPDATE_WEB_APP": "Nova versão disponível. Carregar nova versão?" }, "BL": { @@ -61,7 +61,7 @@ "IMG": "Imagem", "LINK": "Url" }, - "SELECT_ICON": "Selecione um icone", + "SELECT_ICON": "Selecione um ícone", "SELECT_TYPE": "Selecione um tipo", "TYPES": { "COMMAND": "Comando (Comando shell personalizado)", @@ -86,14 +86,14 @@ }, "D_CONFLICT": { "LAST_CHANGE": "última mudança:", - "LAST_SYNC": "última sincronização:", + "LAST_SYNC": "Última sincronização:", "LOCAL": "Local", "LOCAL_REMOTE": "local -> remoto", - "REMOTE": "Controlo remoto", + "REMOTE": "remoto", "TEXT": "

Atualização do Dropbox. Os dados locais e remotos parecem ter sido modificados.

", "TITLE": "Dropbox: dados conflitantes", - "USE_LOCAL": "Use local", - "USE_REMOTE": "Usar controle remoto" + "USE_LOCAL": "Usar local", + "USE_REMOTE": "Usar remoto" }, "FORM": { "B_GENERATE_TOKEN": "Gerar token", @@ -119,8 +119,8 @@ }, "FORM": { "FILTER_USER": "Nome de usuário (ex.: para filtrar as alterações sozinho)", - "IS_AUTO_ADD_TO_BACKLOG": "Adicionar automáticamente problemas não resolvidos do Github para o backlog", - "IS_AUTO_POLL": "Obter automáticamente issues do Github para alterações", + "IS_AUTO_ADD_TO_BACKLOG": "Adicionar automaticamente problemas não resolvidos do Github para o backlog", + "IS_AUTO_POLL": "Obter automaticamente issues do Github para alterações", "IS_SEARCH_ISSUES_FROM_GITHUB": "Mostrar issues do Github como sugestões qando adicionar novas tarefas", "REPO": "\"username/repositoryName\" para o repositório git que você deseja rastrear", "TOKEN": "Token de acesso" @@ -131,12 +131,12 @@ }, "ISSUE_CONTENT": { "ASSIGNEE": "Responsável", - "AT": "at", + "AT": "em", "DESCRIPTION": "Descrição", "LABELS": "Etiquetas", "MARK_AS_CHECKED": "Marcar atualizações como checadas", "STATUS": "Status", - "SUMMARY": "Indíce", + "SUMMARY": "Índice", "WRITE_A_COMMENT": "Comentar" }, "S": { @@ -161,8 +161,8 @@ }, "FORM": { "FILTER_USER": "Nome de usuário (ex.: para filtrar as alterações sozinho)", - "IS_AUTO_ADD_TO_BACKLOG": "Adicionar automáticamente problemas não resolvidos do Gitlab para o backlog", - "IS_AUTO_POLL": "Obter automáticamente issues do Gitlab para alterações", + "IS_AUTO_ADD_TO_BACKLOG": "Adicionar automaticamente problemas não resolvidos do Gitlab para o backlog", + "IS_AUTO_POLL": "Obter automaticamente issues do Gitlab para alterações", "IS_SEARCH_ISSUES_FROM_GITLAB": "Mostrar issues do Gitlab como sugestões qando adicionar novas tarefas", "PROJECT": "Caminho completo ou nome de usuário / projeto", "TOKEN": "Token de acesso" @@ -173,7 +173,7 @@ }, "ISSUE_CONTENT": { "ASSIGNEE": "Responsável", - "AT": "at", + "AT": "em", "DESCRIPTION": "Descrição", "LABELS": "Etiquetas", "MARK_AS_CHECKED": "Marcar atualizações como checadas", @@ -212,7 +212,7 @@ "LOCAL_REMOTE": "local <=> remoto", "OVERWRITE_LOCAL": "Substituir local", "OVERWRITE_REMOTE": "Substituir remoto", - "REMOTE": "romoto", + "REMOTE": "remoto", "TEXT": "

Atualização do Google Drive Backup. Os dados locais e remotos parecem ter sido modificados. Deseja sobrescrever alterações locais não salvas? Todos os dados serão perdidos para sempre.

", "TITLE": "Sobrescrever dados locais com a Atualização GDrive?" }, @@ -260,7 +260,7 @@ }, "JIRA": { "BANNER": { - "BLOCK_ACCESS_MSG": "Jira: A fim de impedir o desligamento da API, o acesso foi bloqueado pela Super Produtividade. Você provavelmente deve verificar suas configurações do jira!", + "BLOCK_ACCESS_MSG": "Jira: A fim de impedir o desligamento da API, o acesso foi bloqueado pelo Super Productivity. Você provavelmente deve verificar suas configurações do jira!", "BLOCK_ACCESS_UNBLOCK": "Desbloquear" }, "CFG_CMP": { @@ -301,7 +301,7 @@ "TITLE": "Jira: Enviar log de trabalho" }, "FORM": { - "IS_AUTO_ADD_TO_BACKLOG": "Adicionar automáticamente problemas não resolvidos do Github para o backlog", + "IS_AUTO_ADD_TO_BACKLOG": "Adicionar automaticamente problemas não resolvidos do Github para o backlog", "IS_AUTO_POLL": "Pesquisar automaticamente problemas importados do git em busca de alterações", "IS_SEARCH_ISSUES_FROM_GITHUB": "Mostrar issues do Github como sugestões qando adicionar novas tarefas", "REPO": "\"username/repositoryName\" para o repositório git que você deseja rastrear" @@ -341,7 +341,7 @@ }, "ISSUE_CONTENT": { "ASSIGNEE": "Resposável", - "AT": "at", + "AT": "em", "ATTACHMENTS": "Anexos", "CHANGED": "Alterado", "COMMENTS": "Comentarios", @@ -349,7 +349,7 @@ "DESCRIPTION": "Descrição", "LIST_OF_CHANGES": "Lista de mudanças", "MARK_AS_CHECKED": "Marcar atualizações como checadas", - "ON": "on", + "ON": "em", "STATUS": "Status", "STORY_POINTS": "Story Points", "SUMMARY": "Indíce", @@ -463,7 +463,7 @@ "ENJOY_YOURSELF": "Divirta-se, mexa-se, volte em:", "FINISH_SESSION_X": "Você terminou a sessão com sucesso {{nr}}!", "NOTIFICATION": { - "BREAK_X_START": "Pomodoro: Pausa {{nr}} iniciado!", + "BREAK_X_START": "Pomodoro: Pausa {{nr}} iniciada!", "SESSION_X_START": "Pomodoro: Sessão {{nr}} iniciada!" }, "S": { @@ -567,7 +567,7 @@ "L_AUTO_SWITCH_ON": "O gatilho automático é ativado por", "L_ICON": "Ícone", "L_ICON_ON": "Ícone quando alternado", - "L_IS_ENABLED": "ativado", + "L_IS_ENABLED": "Ativado", "L_TITLE": "Título", "L_TYPE": "Tipo", "TITLE": "Contadores simples", @@ -611,7 +611,7 @@ "REPEAT": "Repetir", "SCHEDULE_TASK": "Agendar Tarefa", "SUB_TASKS": "Subtarefas ({{nr}})", - "TIME": "tempo" + "TIME": "Tempo" }, "ADD_TASK_BAR": { "ADD_EXISTING_TASK": "Adicionar tarefa existente \"{{taskTitle}}\"", @@ -826,7 +826,7 @@ "DISMISS": "Dispensar", "DO_IT": "Fazer isto!", "EDIT": "Editar", - "EXTENSION_INFO": "Por favor faça o download da extensão chrome para permitir a comunicação com o Jira Api e o Idle Time Handling. Observe que isso não funciona para dispositivos móveis.", + "EXTENSION_INFO": "Por favor faça o download da extensão chrome para permitir a comunicação com o Jira Api e o Idle Time Handling. Observe que isso não funciona para dispositivos móveis.", "LOGIN": "Login", "LOGOUT": "Sair", "MINUTES": "{{m}} minutos", @@ -912,22 +912,23 @@ "ZOOM_OUT": "Diminuir o zoom (somente para computador)" }, "LANG": { - "AR": "Arabic", - "DE": "German", - "EN": "English", - "ES": "Spanish", + "AR": "Árabe", + "DE": "Alemão", + "EN": "Inglês", + "ES": "Espanhol", "FA": "Persa", - "FR": "French", - "IT": "italiano", - "JA": "Japanese", - "KO": "Korean", - "LABEL": "Please select a language", + "FR": "Francês", + "IT": "Italiano", + "JA": "Japonês", + "KO": "Coreano", + "LABEL": "Por favor escolha um idioma", + "NB": "Bokmål norueguês", "NL": "Holandês", - "PT": "português", - "RU": "Russian", - "TITLE": "Language", - "TR": "Turkish", - "ZH": "Chinese" + "PT": "Português", + "RU": "Russo", + "TITLE": "Idioma", + "TR": "Turco", + "ZH": "Chinês" }, "MISC": { "DEFAULT_PROJECT": "Projeto padrão a ser usado para tarefas se nenhum for especificado", @@ -1114,10 +1115,10 @@ "indigo": "índigo", "light-blue": "azul claro", "light-green": "verde claro", - "lime": "Lima", - "pink": "Rosa", + "lime": "lima", + "pink": "rosa", "purple": "roxa", - "teal": "teal", + "teal": "azul petróleo", "yellow": "amarelo" }, "V": { @@ -1140,7 +1141,7 @@ "NO_COMPLETED_TASKS": "No momento, não há tarefas concluídas", "NO_PLANNED_TASKS": "Nenhuma tarefa planejada", "READY_TO_WORK": "Pronto para trabalhar!", - "RESET_BREAK_TIMER": "Reset without break timer", + "RESET_BREAK_TIMER": "Reiniciar sem temporizador de pause", "TIME_ESTIMATED": "Tempo estimado:", "WITHOUT_BREAK": "Sem pausa:", "WORKING_TODAY": "Trabalhando hoje:" diff --git a/src/assets/i18n/ru.json b/src/assets/i18n/ru.json index 173e2678e..d087a19bd 100644 --- a/src/assets/i18n/ru.json +++ b/src/assets/i18n/ru.json @@ -826,7 +826,7 @@ "DISMISS": "Отклонить", "DO_IT": "Сделай это!", "EDIT": "Редактировать", - "EXTENSION_INFO": "Пожалуйста, загрузите расширение Chrome , чтобы обеспечить связь с Jira Api и Idle Time Handling. Обратите внимание, что это не работает для мобильных устройств.", + "EXTENSION_INFO": "Пожалуйста, загрузите расширение Chrome , чтобы обеспечить связь с Jira Api и Idle Time Handling. Обратите внимание, что это не работает для мобильных устройств.", "LOGIN": "Авторизоваться", "LOGOUT": "Выйти", "MINUTES": "{{m}} минут", @@ -922,6 +922,7 @@ "JA": "Японский", "KO": "Корейский", "LABEL": "Пожалуйста, выберите язык", + "NB": "Норвежский букмол", "NL": "голландский", "PT": "португальский", "RU": "Русский", diff --git a/src/assets/i18n/tr.json b/src/assets/i18n/tr.json index 6308a2272..54eaef260 100644 --- a/src/assets/i18n/tr.json +++ b/src/assets/i18n/tr.json @@ -826,7 +826,7 @@ "DISMISS": "Reddet", "DO_IT": "Yap!", "EDIT": "Düzenle", - "EXTENSION_INFO": "Jira Api ve Idle Time Handling ile iletişime izin vermek için lütfen krom uzantısını indirin. Bunun mobil cihazlar için işe yaramadığını unutmayın.", + "EXTENSION_INFO": "Jira Api ve Idle Time Handling ile iletişime izin vermek için lütfen krom uzantısını indirin. Bunun mobil cihazlar için işe yaramadığını unutmayın.", "LOGIN": "Oturum aç", "LOGOUT": "Çıkış Yap", "MINUTES": "{{m}} dakika", @@ -922,6 +922,7 @@ "JA": "Japonca", "KO": "Koreli", "LABEL": "Lütfen bir dil seçin", + "NB": "Norveççe Bokmål", "NL": "Flemenkçe", "PT": "Portekizce", "RU": "Rusça", diff --git a/src/assets/i18n/zh.json b/src/assets/i18n/zh.json index 0c92ed549..136af0523 100644 --- a/src/assets/i18n/zh.json +++ b/src/assets/i18n/zh.json @@ -826,7 +826,7 @@ "DISMISS": "取消", "DO_IT": "开始吧!", "EDIT": "编辑", - "EXTENSION_INFO": "请 下载Chrome扩展程序 ,以便与Jira Api和空闲时间处理进行通信。请注意,这不适用于移动设备。", + "EXTENSION_INFO": "请 下载Chrome扩展程序 ,以便与Jira Api和空闲时间处理进行通信。请注意,这不适用于移动设备。", "LOGIN": "登录", "LOGOUT": "登出", "MINUTES": "{{m}} 分钟", @@ -922,6 +922,7 @@ "JA": "日本语", "KO": "朝鲜语", "LABEL": "请选择一种语言", + "NB": "挪威语 Bokmål", "NL": "荷兰语", "PT": "葡萄牙语", "RU": "俄语", diff --git a/src/index.html b/src/index.html index b0cfc35dc..9b48fcbc0 100644 --- a/src/index.html +++ b/src/index.html @@ -52,7 +52,7 @@ TODO configure more restrictive Content-Security-Policy @@ -190,7 +190,13 @@ TODO configure more restrictive Content-Security-Policy ['Mess creates stress', 'Clean up your desk!'], ['Focus on a single task', 'Multi-tasking is the worst.'], ['Set your goals', 'Having a goal to aim for (or even some smaller ones) increases motivation and focus. Remind yourself why you are doing something.'], - ['Try to get it right the first time', 'This can help you to avoid dragging a task on forever.'] + ['Try to get it right the first time', 'This can help you to avoid dragging a task on forever.'], + ['We are what we repeatedly do.', 'Excellence, then, is not an act, but a habit. - Aristotle'], + ['You don’t learn to walk by following rules.', 'You learn by doing, and by falling over. - Richard Branson'], + ['A problem well-put is half-solved', '- John Dewey'], + ['Wisdom is learning what to overlook', '- William James'], + ['It is not that I’m so smart', 'But I stay with the questions much longer - Albert Einstein'], + ['No problem can withstand the assault of sustained thinking', '- Voltaire'], ]; var randomIndex = Math.floor(Math.random() * productivityTips.length); document.getElementById('heading').innerText = productivityTips[randomIndex][0]; diff --git a/src/main.ts b/src/main.ts index 1d9684e80..e2eb6937c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,9 +16,15 @@ platformBrowserDynamic().bootstrapModule(AppModule).then(() => { if ('serviceWorker' in navigator && (environment.production || environment.stage) && !IS_ELECTRON) { console.log('Registering Service worker'); return navigator.serviceWorker.register('ngsw-worker.js'); + } else if ('serviceWorker' in navigator && IS_ELECTRON) { + navigator.serviceWorker.getRegistrations().then((registrations) => { + for (const registration of registrations) { + registration.unregister(); + } + }); } return; -}).catch(err => { +}).catch((err: any) => { console.log('Service Worker Registration Error'); console.log(err); }); diff --git a/yarn.lock b/yarn.lock index 783147734..7efedf089 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4468,6 +4468,15 @@ ejs@^3.1.3: dependencies: jake "^10.6.1" +electron-builder-notarize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/electron-builder-notarize/-/electron-builder-notarize-1.2.0.tgz#6db86173601513bcb667074f80322f8622e24ff9" + integrity sha512-mSU5CSjydNlO5oFSOimJvzKQ4m/whUUBoE3i2xSAOF7+T2ZIzSfsGCT1SJvqsiHYf2xvTb2RpFoHWE6Oc9Cvgg== + dependencies: + electron-notarize "^0.2.0" + js-yaml "^3.14.0" + read-pkg-up "^7.0.0" + electron-builder@^22.7.0: version "22.8.1" resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-22.8.1.tgz#84295190dae17b3892df7aa39ac334983aeaea06" @@ -4517,6 +4526,14 @@ electron-log@^4.2.2: resolved "https://registry.yarnpkg.com/electron-log/-/electron-log-4.2.4.tgz#a13e42a9fc42ca2cc7d2603c3746352efa82112e" integrity sha512-CXbDU+Iwi+TjKzugKZmTRIORIPe3uQRqgChUl19fkW/reFUn5WP7dt+cNGT3bkLV8xfPilpkPFv33HgtmLLewQ== +electron-notarize@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/electron-notarize/-/electron-notarize-0.2.1.tgz#759e8006decae19134f82996ed910db26d9192cc" + integrity sha512-oZ6/NhKeXmEKNROiFmRNfytqu3cxqC95sjooG7kBXQVEUSQkZnbiAhxVh5jXngL881G197pbwpeVPJyM7Ikmxw== + dependencies: + debug "^4.1.1" + fs-extra "^8.1.0" + electron-notarize@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/electron-notarize/-/electron-notarize-1.0.0.tgz#bc925b1ccc3f79e58e029e8c4706572b01a9fd8f" @@ -10016,7 +10033,7 @@ read-pkg-up@^3.0.0: find-up "^2.0.0" read-pkg "^3.0.0" -read-pkg-up@^7.0.1: +read-pkg-up@^7.0.0, read-pkg-up@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==