diff --git a/.babelrc.js b/.babelrc.js deleted file mode 100644 index 4035cd1084..0000000000 --- a/.babelrc.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - extends: require.resolve('@gera2ld/plaid/config/babelrc'), - presets: [ - ['@babel/preset-env', { - ...process.env.BABEL_ENV !== 'test' && { - modules: false, - }, - useBuiltIns: false, - loose: true, - }], - ], - plugins: [ - '@babel/plugin-proposal-function-bind', - ], -}; diff --git a/.browserslistrc b/.browserslistrc index 7447e41c99..87b41ec234 100644 --- a/.browserslistrc +++ b/.browserslistrc @@ -1,2 +1,2 @@ -Chrome >= 55 -Firefox >= 53 +Chrome >= 61 +Firefox >= 57 diff --git a/.editorconfig b/.editorconfig index 4a7ea3036a..8e6ffe43de 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,6 +7,4 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false +quote_type = single diff --git a/.eslintignore b/.eslintignore index 6c6a8eccb6..55e5d10ea1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,6 @@ **/*.js !src/** !test/** +!scripts/* +!gulpfile.js src/public/** diff --git a/.eslintrc.js b/.eslintrc.js index 1e5f492ab3..ae6c540e22 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,30 +1,34 @@ +const { readGlobalsFile, restrictedSyntax } = require('./scripts/webpack-util'); +const ovr = makeOverrides(); + module.exports = { root: true, extends: [ - require.resolve('@gera2ld/plaid/eslint'), - require.resolve('@gera2ld/plaid-vue/eslint/vue'), + 'eslint:recommended', + 'plugin:vue/vue3-essential', + 'prettier', ], + env: { + browser: true, + node: true, + es2021: true, + }, parserOptions: { - ecmaFeatures: { - legacyDecorators: true, - }, + parser: '@babel/eslint-parser', + ecmaVersion: 'latest', + sourceType: 'module', }, - overrides: [{ - // `browser` is a local variable since we remove the global `chrome` and `browser` in injected* - // to prevent exposing them to userscripts with `@inject-into content` - files: ['*'], - excludedFiles: [ - 'src/injected/**/*.js', - 'src/common/*.js', - ], - globals: { - browser: false, - }, - }], + plugins: ['jest'], rules: { - 'import/extensions': ['error', 'ignorePackages', { - js: 'never', - vue: 'never', + 'prettier/prettier': 'off', + 'no-constant-condition': 'off', + 'no-shadow': 2, + 'no-unused-expressions': 2, + 'no-use-before-define': ['error', { + 'functions': false, + 'classes': true, + 'variables': false, // allowing for upper scopes + 'allowNamedExports': true, }], // copied from airbnb-base, replaced 4 with 8 'object-curly-newline': ['error', { @@ -33,5 +37,153 @@ module.exports = { ImportDeclaration: { minProperties: 8, multiline: true, consistent: true }, ExportDeclaration: { minProperties: 8, multiline: true, consistent: true }, }], + 'semi': ['error'], }, + overrides: [{ + // `browser` is a local variable since we remove the global `chrome` and `browser` in injected* + // to prevent exposing them to userscripts with `@inject-into content` + files: ['*'], + excludedFiles: [...ovr.FILES_INJECTED, ...ovr.FILES_SHARED], + globals: { + browser: false, + ...ovr.GLOBALS_COMMON, + }, + }, { + files: ovr.FILES_SHARED, + globals: ovr.GLOBALS_COMMON, + }, { + files: ovr.FILES_WEB, + globals: ovr.GLOBALS_WEB, + }, { + files: ovr.FILES_CONTENT, + globals: ovr.GLOBALS_CONTENT, + }, { + files: ovr.FILES_INJECTED, + excludedFiles: [...ovr.FILES_CONTENT, ...ovr.FILES_WEB], + // intersection of globals in CONTENT and WEB + globals: Object.keys(ovr.GLOBALS_CONTENT).reduce((res, key) => ( + Object.assign(res, key in ovr.GLOBALS_WEB && { [key]: false }) + ), {}), + }, { + files: [...ovr.FILES_INJECTED, ...ovr.FILES_SHARED], + rules: ovr.INJECTED_RULES, + }, { + files: ovr.FILES_WEB, + rules: { + ...ovr.INJECTED_RULES, + 'no-restricted-syntax': [ + ...ovr.INJECTED_RULES['no-restricted-syntax'], + { + selector: '[regex], NewExpression[callee.name="RegExp"]', + message: 'RegExp internally depends on a *ton* of stuff that may be spoofed or broken', + // https://262.ecma-international.org/12.0/#sec-regexpexec + }, + ], + }, + }, { + // build scripts + files: [ + '*.js', + 'scripts/*.js', + 'scripts/*.mjs', + ], + env: { node: true }, + rules: { + 'global-require': 0, + 'import/newline-after-import': 0, + 'import/no-extraneous-dependencies': 0, // spits errors in github action + 'import/extensions': 0, + } + }, { + files: ['*.vue'], + rules: { + 'vue/multi-word-component-names': 0, + }, + }, { + files: ['test/**'], + env: { + 'jest/globals': true, + }, + }], }; + +function makeOverrides() { + /* Note that `injected` uses several more `common` files indirectly, but we check just these + * two automatically because they are trivial by design and must always pass the check */ + const GLOBALS_SHARED = getGlobals('*'); + const GLOBALS_INJECTED = { + ...getGlobals('injected'), + PAGE_MODE_HANDSHAKE: false, + VAULT_ID: false, + }; + function getGlobals(path) { + const res = {}; + const { ast } = readGlobalsFile(path, { ast: true }); + ast.program.body.forEach(body => { + const { declarations } = body.declaration || body; + if (!declarations) return; + declarations.forEach(function processId({ + id: { + left, + properties, + name = left && left.name, + }, + }) { + if (name) { + // const NAME = whatever + // We consider `let` immutable too to avoid unintentional reassignment + res[name] = false; + } else if (properties) { + // const { NAME1, prototype: { NAME2: ALIAS2 } } = whatever + properties.forEach(({ value }) => processId({ id: value })); + } + }); + }); + return res; + } + + return { + FILES_CONTENT: [ + 'src/injected/index.js', + 'src/injected/content/**/*.js', + ], + FILES_INJECTED: [ + 'src/injected/**/*.js', + ], + FILES_SHARED: [ + 'src/common/browser.js', + 'src/common/consts.js', + 'src/common/safe-globals-shared.js', + ], + FILES_WEB: [ + 'src/injected/web/**/*.js', + ], + GLOBALS_INJECTED, + GLOBALS_SHARED, + GLOBALS_COMMON: { + ...GLOBALS_SHARED, + ...getGlobals('common'), + re: false, // transform-modern-regexp with useRe option + }, + GLOBALS_CONTENT: { + INIT_FUNC_NAME: false, + ...GLOBALS_SHARED, + ...getGlobals('injected/content'), + ...GLOBALS_INJECTED, + }, + GLOBALS_WEB: { + ...GLOBALS_SHARED, + ...getGlobals('injected/web'), + ...GLOBALS_INJECTED, + IS_FIREFOX: false, // passed as a parameter to VMInitInjection in webpack.conf.js + }, + INJECTED_RULES: { + 'no-restricted-imports': [ + 'error', { + patterns: ['*/common', '*/common/*'], + } + ], + 'no-restricted-syntax': ['error', ...restrictedSyntax], + }, + }; +} diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 241f8bda94..0000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -custom: https://violentmonkey.github.io/donate/ diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index d7f1575928..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,28 +0,0 @@ - - -### What is the problem? - -### How to reproduce it? -1. -2. -3. - -### What is the expected result? - -### What is the actual result? - -### What is the result in the [upcoming release](https://github.com/violentmonkey/violentmonkey/releases)? - - - -### Environment -- Browser: -- Browser version: -- Violentmonkey version: -- OS: diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..8fb69b4723 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: For bugs that also exist in beta +title: "[BUG] " +labels: bug +assignees: '' + +--- + + + +### Sequence of actions: +1. +2. +3. + +### Problem: + +### Expected result: + +#### Devtools console contents: + +#### Environment: +- OS: Windows +- Browser: Chrome 123 +- Violentmonkey Version: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..40055588bc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[Feature] " +labels: enhancement +assignees: '' + +--- + +**Motivation** + + +**Proposed Solution** + + +**Use Cases** + diff --git a/.github/workflows/amo-download.yml b/.github/workflows/amo-download.yml new file mode 100644 index 0000000000..5d5f82368a --- /dev/null +++ b/.github/workflows/amo-download.yml @@ -0,0 +1,50 @@ +name: Download AMO signed unlisted package + +on: + workflow_dispatch: + inputs: + version: + required: true + repository_dispatch: + types: + - amo_download + +jobs: + amo-download: + if: ${{ github.event.client_payload.version || github.event.inputs.version }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Prepare + run: yarn && node scripts/action-helper.js + env: + ACTION_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + DISCORD_WEBHOOK_RELEASE: ${{ secrets.DISCORD_WEBHOOK_RELEASE }} + VERSION: ${{ github.event.client_payload.version || github.event.inputs.version }} + + - name: Download signed file and upload to GitHub release + run: | + mkdir -p $TEMP_DIR/updates + DEBUG=amo-upload BETA=1 node scripts/amo-upload.mjs + env: + AMO_KEY: ${{ secrets.AMO_KEY }} + AMO_SECRET: ${{ secrets.AMO_SECRET }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Update updates.json + uses: JamesIves/github-pages-deploy-action@4.1.0 + with: + branch: updates + folder: ${{ env.TEMP_DIR }}/updates + commit-message: Update to ${{ env.VERSION }} 🚀 + + - name: Upload assets + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + node -e 'import("./scripts/release-helper.mjs").then(({ uploadAssets }) => uploadAssets())'; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2b3d9f00f..f9ddda37f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,58 @@ name: CI on: - - pull_request + workflow_dispatch: + push: + branches: [master] + tags-ignore: + - 'v*' # version-tagged commits are releases which have their own workflow + paths-ignore: + - '.github/**' # this ci.yml is also excluded so you need to re-run it explicitly if necessary + - src/types.d.ts + - LICENSE + - README.md + pull_request: + branches: [master] jobs: ci: runs-on: ubuntu-latest + steps: - name: Checkout code - uses: actions/checkout@v2 - - uses: actions/setup-node@v2-beta + uses: actions/checkout@v1 # v1 keeps tags + with: + fetch-depth: 100 # for revision index in version and the `has-changed-path` action + - uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Build + env: + SYNC_DROPBOX_CLIENT_ID: ${{ secrets.SYNC_DROPBOX_CLIENT_ID }} + SYNC_GOOGLE_DESKTOP_ID: ${{ secrets.SYNC_GOOGLE_DESKTOP_ID }} + SYNC_GOOGLE_DESKTOP_SECRET: ${{ secrets.SYNC_GOOGLE_DESKTOP_SECRET }} + SYNC_ONEDRIVE_CLIENT_ID: ${{ secrets.SYNC_ONEDRIVE_CLIENT_ID }} + run: yarn && yarn build + + - name: Get version and SHA + run: node scripts/action-helper.js ci + - name: Upload Artifact + uses: actions/upload-artifact@v4 + continue-on-error: true + with: + name: 'Violentmonkey-test-webext-${{ env.GIT_DESCRIBE }}' + path: 'dist/*' + if-no-files-found: error + + - name: Upload to Transifex - check src + if: github.event_name == 'push' || github.event.pull_request.merged == true + uses: marceloprado/has-changed-path@v1.0.1 + id: changed-path-src with: - node-version: '15' - - name: CI - run: yarn && yarn ci + paths: src + - name: Upload to Transifex + if: steps.changed-path-src.outputs.changed == 'true' + run: node scripts/transifex.mjs update + env: + TRANSIFEX_TOKEN: ${{ secrets.TRANSIFEX_TOKEN }} diff --git a/.github/workflows/release-edge.yml b/.github/workflows/release-edge.yml new file mode 100644 index 0000000000..16dd01c43f --- /dev/null +++ b/.github/workflows/release-edge.yml @@ -0,0 +1,54 @@ +name: Release for Edge + +on: + workflow_dispatch: + push: + tags: + - v* + +jobs: + build: + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v1 # v1 keeps tags + with: + fetch-depth: 250 # for `action-helper` + # persist-credentials: false # not implemented in v1 + + - uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Prepare + run: yarn && node scripts/action-helper.js + env: + ACTION_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + DISCORD_WEBHOOK_RELEASE: ${{ secrets.DISCORD_WEBHOOK_RELEASE }} + + - name: Build + env: + SYNC_DROPBOX_CLIENT_ID: ${{ secrets.SYNC_DROPBOX_CLIENT_ID }} + SYNC_GOOGLE_DESKTOP_ID: ${{ secrets.SYNC_GOOGLE_DESKTOP_ID }} + SYNC_GOOGLE_DESKTOP_SECRET: ${{ secrets.SYNC_GOOGLE_DESKTOP_SECRET }} + SYNC_ONEDRIVE_CLIENT_ID: ${{ secrets.SYNC_ONEDRIVE_CLIENT_ID }} + run: | + mkdir -p $ASSETS_DIR + yarn build + cd dist && zip -r ../$ASSETS_DIR/$ASSET_ZIP . && cd .. + + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Publish to Edge + run: | + if [ "$PRERELEASE" != "true" ]; then + deno run -A https://raw.githubusercontent.com/violentmonkey/publish-edge-ext/main/main.ts $ASSETS_DIR/$ASSET_ZIP + else + echo Skip BETA for Edge + fi + env: + CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }} + API_KEY: ${{ secrets.EDGE_API_KEY }} + PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3bb7bd6df9..00e59140e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,7 @@ name: Release on: + workflow_dispatch: push: tags: - v* @@ -9,50 +10,92 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + + - uses: actions/checkout@v1 # v1 keeps tags with: - persist-credentials: false - - uses: actions/setup-node@v2 + fetch-depth: 250 # for `action-helper` + # persist-credentials: false # not implemented in v1 + + - uses: actions/setup-node@v3 with: - node-version: '15' + node-version: '20' + - name: Prepare run: yarn && node scripts/action-helper.js + env: + ACTION_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + DISCORD_WEBHOOK_RELEASE: ${{ secrets.DISCORD_WEBHOOK_RELEASE }} + - name: Build + env: + SYNC_DROPBOX_CLIENT_ID: ${{ secrets.SYNC_DROPBOX_CLIENT_ID }} + SYNC_GOOGLE_DESKTOP_ID: ${{ secrets.SYNC_GOOGLE_DESKTOP_ID }} + SYNC_GOOGLE_DESKTOP_SECRET: ${{ secrets.SYNC_GOOGLE_DESKTOP_SECRET }} + SYNC_ONEDRIVE_CLIENT_ID: ${{ secrets.SYNC_ONEDRIVE_CLIENT_ID }} run: | - yarn build mkdir -p $ASSETS_DIR $TEMP_DIR + + # Create source zip + git archive @ --format=zip > $TEMP_DIR/$SOURCE_ZIP + # Include .env to ensure the output is reproducible + export | grep SYNC_ | sed -e 's/^declare -x //' > .env \ + && zip -u $TEMP_DIR/$SOURCE_ZIP .env && rm .env + + # Build for release, also upload to GitHub assets + yarn build + # Build one more time to make sure the output is the same + mv dist $TEMP_DIR/dist && yarn build && diff -qr dist $TEMP_DIR/dist + cd dist && zip -r ../$ASSETS_DIR/$ASSET_ZIP . && cd .. - # Same as `yarn build:selfHosted` but only manifest is changed for now + # Build for CWS beta, append `BETA` to version name + # Same as `BETA=1 yarn build` but only manifest is changed by now + BETA=1 npx gulp manifest + cd dist && zip -r ../$TEMP_DIR/$ASSET_CWS_BETA_ZIP . && cd .. + + # Build for AMO unlisted, append `BETA` to version name and set update_url for FF + # Same as `yarn build:selfHosted` but only manifest is changed by now TARGET=selfHosted BETA=1 npx gulp manifest cd dist && zip -r ../$TEMP_DIR/$ASSET_SELF_HOSTED_ZIP . && cd .. - - name: Sign AMO - id: signAMO + + - name: Publish to AMO continue-on-error: true run: | mkdir -p $TEMP_DIR/updates - node scripts/amo-sign + if [ "$PRERELEASE" != "true" ]; then + echo Publish listed version + node scripts/amo-upload.mjs + else + echo Publish unlisted version + DEBUG=amo-upload BETA=1 node scripts/amo-upload.mjs + fi env: AMO_KEY: ${{ secrets.AMO_KEY }} AMO_SECRET: ${{ secrets.AMO_SECRET }} - BETA: 1 - - name: Update updates.json - uses: JamesIves/github-pages-deploy-action@4.1.0 - if: steps.signAMO.outcome == 'success' - with: - branch: updates - folder: ${{ env.TEMP_DIR }}/updates - commit-message: Update to ${{ env.VERSION }} 🚀 - - name: Create Release - env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Try to publish if not exist + AMO_PUBLISH: true + + - name: Publish to CWS + continue-on-error: true run: | set -x - params=("-m" "$RELEASE_NAME" "-m" "$RELEASE_NOTE") - if [ "$PRERELEASE" = "true" ]; then - params+=("-p") + if [ "$PRERELEASE" != "true" ]; then + echo Publish release + npx chrome-webstore-upload-cli@1 upload --extension-id $EXTENSION_ID_RELEASE --source $ASSETS_DIR/$ASSET_ZIP --auto-publish + else + echo Publish prerelease + npx chrome-webstore-upload-cli@1 upload --extension-id $EXTENSION_ID_BETA --source $TEMP_DIR/$ASSET_CWS_BETA_ZIP --auto-publish fi - for asset in $ASSETS_DIR/*; do - params+=("-a" "$asset") - done - hub release create "${params[@]}" v$VERSION + env: + EXTENSION_ID_BETA: opokoaglpekkimldnlggpoagmjegichg + EXTENSION_ID_RELEASE: jinjaccalgkegednnccohejagnlnfdag + CLIENT_ID: ${{ secrets.CWS_CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CWS_CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.CWS_REFRESH_TOKEN }} + + - name: Create/Update Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + node -e 'import("./scripts/release-helper.mjs").then(({ uploadAssets }) => uploadAssets())'; diff --git a/.github/workflows/transifex-pull-translations.yml b/.github/workflows/transifex-pull-translations.yml index a370931262..ff7618d9ab 100644 --- a/.github/workflows/transifex-pull-translations.yml +++ b/.github/workflows/transifex-pull-translations.yml @@ -13,13 +13,13 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - uses: actions/setup-node@v2-beta + - uses: actions/setup-node@v3 with: - node-version: '15' + node-version: '20' - name: Install deps run: yarn - name: Update translations - run: node scripts/transifex pull + run: node scripts/transifex.mjs pull env: TRANSIFEX_TOKEN: ${{ secrets.TRANSIFEX_TOKEN }} - name: Create pull request @@ -27,9 +27,9 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} branch: i18n-patch - commit-message: Update locale files from Transifex + commit-message: 'chore: update locale files from Transifex' title: Update locale files from Transifex body: >- Violentmonkey updates translations from Transifex every week automatically. - If you want to help translate into your language, please visit [the transifex platform](https://www.transifex.com/projects/p/violentmonkey-nex/resource/messagesjson/). + If you want to help translate into your language, please join [our transifex project](https://explore.transifex.com/violentmonkey/violentmonkey-nex/). diff --git a/.github/workflows/transifex-push-resources.yml b/.github/workflows/transifex-push-resources.yml deleted file mode 100644 index 7e370f14a9..0000000000 --- a/.github/workflows/transifex-push-resources.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Upload resources to Transifex - -on: - push: - branches: - - master - paths: - - src/** - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - uses: actions/setup-node@v2-beta - with: - node-version: '15' - - name: Update locale files - run: yarn && yarn copyI18n - - name: Upload to Transifex - run: 'curl -i -L --user api:$TRANSIFEX_TOKEN -X PUT -F file=@dist/_locales/en/messages.json https://www.transifex.com/api/2/project/violentmonkey-nex/resource/messagesjson/content/' - env: - TRANSIFEX_TOKEN: ${{ secrets.TRANSIFEX_TOKEN }} diff --git a/.github/workflows/transifex-push-translations.yml b/.github/workflows/transifex-push-translations.yml index 5ac7781adc..48cbf82bab 100644 --- a/.github/workflows/transifex-push-translations.yml +++ b/.github/workflows/transifex-push-translations.yml @@ -15,13 +15,13 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - uses: actions/setup-node@v2-beta + - uses: actions/setup-node@v3 with: - node-version: '15' + node-version: '20' - name: Install deps run: yarn - name: Upload translations - run: node scripts/transifex push + run: node scripts/transifex.mjs push env: TRANSIFEX_TOKEN: ${{ secrets.TRANSIFEX_TOKEN }} DIFF_URL: ${{ github.event.pull_request.diff_url }} diff --git a/.gitignore b/.gitignore index 2964862b9b..ffae16b372 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ /dist/ node_modules/ *.log +.eslintcache /stats.json /tmp/ /dist-assets/ +.eslintcache +.env diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000000..3ca39cac60 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn ci diff --git a/.postcssrc.js b/.postcssrc.js new file mode 100644 index 0000000000..068564c1dc --- /dev/null +++ b/.postcssrc.js @@ -0,0 +1,23 @@ +const { alias } = require('./scripts/common'); + +module.exports = { + parser: 'postcss-scss', + plugins: [ + 'postcss-simple-vars', + // Transform @import, resolve `@` to `src` + require('postcss-import')({ + resolve(id) { + if (id.startsWith('~')) { + const parts = id.slice(1).split('/'); + parts[0] = alias[parts[0]] || parts[0]; + return require.resolve(parts.join('/')); + } + return id; + }, + }), + // Calculate at compile time + require('postcss-calc'), + require('postcss-nested'), + require('autoprefixer'), + ], +}; diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000000..3fab874a49 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,17 @@ +# Development + +## Icons + +All icons from [Iconify's MDI set](https://icon-sets.iconify.design/mdi/) can be used with [unplugin-icons](https://github.com/unplugin/unplugin-icons). + +Icons follow the pattern: `~icons/mdi/{icon-name}` where `{icon-name}` matches the MDI icon name (e.g., `mdi/home`, `mdi/account-circle`). + +```vue + + + +``` diff --git a/README.md b/README.md index 2879308e61..39bb946459 100644 --- a/README.md +++ b/README.md @@ -3,24 +3,26 @@ [![Chrome Web Store](https://img.shields.io/chrome-web-store/v/jinjaccalgkegednnccohejagnlnfdag.svg)](https://chrome.google.com/webstore/detail/violentmonkey/jinjaccalgkegednnccohejagnlnfdag) [![Firefox Add-ons](https://img.shields.io/amo/v/violentmonkey.svg)](https://addons.mozilla.org/firefox/addon/violentmonkey) [![Microsoft Edge Add-on](https://img.shields.io/badge/dynamic/json?label=microsoft%20edge%20add-on&query=%24.version&url=https%3A%2F%2Fmicrosoftedge.microsoft.com%2Faddons%2Fgetproductdetailsbycrxid%2Feeagobfjdenkkddmbclomhiblgggliao)](https://microsoftedge.microsoft.com/addons/detail/eeagobfjdenkkddmbclomhiblgggliao) -[![Gitter](https://img.shields.io/gitter/room/violentmonkey/violentmonkey.svg)](https://gitter.im/violentmonkey/violentmonkey) -[![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/ViolentMonkey)](https://www.reddit.com/r/ViolentMonkey/) Violentmonkey provides userscripts support for browsers. It works on browsers with [WebExtensions](https://developer.mozilla.org/en-US/Add-ons/WebExtensions) support. More details can be found [here](https://violentmonkey.github.io/). -## Related projects +Join our Discord server: -- [Violentmonkey for Opera Presto](https://github.com/violentmonkey/violentmonkey-oex) -- [Violentmonkey for Maxthon](https://github.com/violentmonkey/violentmonkey-mx) +[![Discord](https://img.shields.io/discord/995346102003965952?label=discord&logo=discord&logoColor=white&style=for-the-badge)](https://discord.gg/XHtUNSm6Xc) + +## Automated Builds for Testers + +A test build is generated automatically for changes between beta releases. It can be installed as an unpacked extension in Chrome and Chromium-based browsers or as a temporary extension in Firefox. It's likely to have bugs so do an export in Violentmonkey settings first. This zip is available only if you're logged-in on GitHub site. Open an entry in the [CI workflows](https://github.com/violentmonkey/violentmonkey/actions/workflows/ci.yml) table and click the `Violentmonkey-...` link at the bottom to download it. ## Workflows ### Development -Make sure [Node.js](https://nodejs.org/) greater than v10.0 and Yarn v1.x is installed. +Install [Node.js](https://nodejs.org/) and Yarn v1.x. +The version of Node.js should match `"node"` key in `package.json`. ``` sh # Install dependencies @@ -32,9 +34,9 @@ $ yarn dev Then load the extension from 'dist/'. -### Building +### Build -After a new (pre)release is created, we should build the project and upload to web stores. +To release a new version, we must build the assets and upload them to web stores. ``` sh # Build for normal releases @@ -44,16 +46,11 @@ $ yarn build $ yarn build:selfHosted ``` -## Release +### Release -Just create a tag and push it. +See [RELEASE](RELEASE.md) for the release flow. -When a tag is pushed to GitHub, a (pre)release will be created with assets built by GitHub Actions. +## Related Projects -```sh -# Create a prerelease -$ yarn bump - -# Create a patch release -$ yarn version --patch -``` +- [Violentmonkey for Opera Presto](https://github.com/violentmonkey/violentmonkey-oex) +- [Violentmonkey for Maxthon](https://github.com/violentmonkey/violentmonkey-mx) diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000000..3bb5f9dbf5 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,29 @@ +# Release + +Create a tag and push, GitHub Actions will build the assets and upload them to different stores. + +## Prerelease + +```bash +$ git checkout master +$ yarn bump +$ git push origin master --follow-tags +``` + +## Release + +Merge everything that is ready to release to the `release` branch and bump version. + +```bash +$ git checkout release +$ git merge master # or git merge v2.13.2 +$ yarn version --minor +$ git push origin release --follow-tags +``` + +Finally merge `release` back to `master`. + +```bash +$ git checkout master +$ git merge release +``` diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000000..5dbcab5014 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,30 @@ +const { alias, extensions } = require('./scripts/common'); + +const isTest = process.env.BABEL_ENV === 'test'; + +module.exports = { + presets: [ + ['@babel/preset-env', { + ...!isTest && { + modules: false, + }, + useBuiltIns: false, + bugfixes: true, + // debug: true, + loose: true, + }], + ], + plugins: [ + ['@babel/plugin-transform-runtime', { + useESModules: !isTest, + version: '^7.5.0', + }], + ['babel-plugin-module-resolver', { + alias, + extensions, + }], + './scripts/babel-plugin-safe-bind.js', + ['@babel/plugin-transform-for-of', { assumeArray: true }], + ['transform-modern-regexp', { useRe: true }], + ], +}; diff --git a/gulpfile.js b/gulpfile.js index 561941002d..5a7483685a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -31,31 +31,38 @@ function watch() { } async function jsDev() { - require('@gera2ld/plaid-webpack/bin/develop')(); + return runCommand('webpack-cli', ['-w', '--config', 'scripts/webpack.conf.js']); } async function jsProd() { - return require('@gera2ld/plaid-webpack/bin/build')({ - api: true, - keep: true, + return runCommand('webpack-cli', ['--config', 'scripts/webpack.conf.js']); +} + +function runCommand(command, args) { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + stdio: 'inherit', + }); + child.on('close', (code, signal) => { + (code ? reject : resolve)(signal); + }); }); } /** - * Versioning - * - * The version of extension is composed of `version` and `beta` fields in `package.json`. - * - * Note: prerelease is ignored and not recommended since both Chrome and Firefox do not support semver + * manifest is already handled in ListBackgroundScriptsPlugin * + * This task is only used to tweak dist/manifest.json without rebuilding */ async function manifest() { - const data = await buildManifest(); + const base = JSON.parse(await fs.readFile(`${DIST}/manifest.json`, 'utf8')); + const data = await buildManifest(base); + await fs.mkdir(DIST).catch(() => {}); await fs.writeFile(`${DIST}/manifest.json`, JSON.stringify(data), 'utf8'); } async function createIcons() { - const ALPHA = .5; + const ALPHA = 0.5; const dist = `${DIST}/public/images`; await fs.mkdir(dist, { recursive: true }); const icon = Sharp(`src/resources/icon${isBeta() ? '-beta' : ''}.png`); @@ -73,7 +80,7 @@ async function createIcons() { ]; const handle = (size, type = '', image = icon) => { let res = image.clone().resize({ width: size }); - if (size < 48) res = res.sharpen(size < 32 ? .5 : .25); + if (size < 48) res = res.sharpen(size < 32 ? 0.5 : 0.25); return res.toFile(`${dist}/icon${size}${type}.png`); }; const darkenOuterEdge = async img => img.composite([{ @@ -83,16 +90,18 @@ async function createIcons() { const handle16 = async ([type, image]) => { const res = image.clone() .resize({ width: 18 }) - .sharpen(.5, 0) + .sharpen(0.5, 0) .extract({ left: 1, top: 2, width: 16, height: 16 }); return (type === 'w' ? res : await darkenOuterEdge(res)) .toFile(`${dist}/icon16${type}.png`); }; return Promise.all([ - handle(48), handle(128), ...types.map(handle16), - ...[19, 32, 38].flatMap(size => types.map(t => handle(size, ...t))), + // 32px dashboard icon (recycled) + 2xDPI browser_action desktop + // 38px dashboard icon (normal) + 1.5xDPI browser_action Android + // 48px 2xDPI browser_action Android + ...[32, 38, 48, 64].flatMap(size => types.map(t => handle(size, ...t))), ]); } @@ -105,7 +114,7 @@ async function bump() { } else { pkg.beta = (+pkg.beta || 0) + 1; } - await fs.writeFile('package.json', JSON.stringify(pkg, null, 2), 'utf8'); + await fs.writeFile('package.json', JSON.stringify(pkg, null, 2) + '\n', 'utf8'); if (process.argv.includes('--commit')) { const version = `v${getVersion()}`; spawn.sync('git', ['commit', '-am', version]); @@ -127,6 +136,7 @@ function copyI18n() { useDefaultLang: true, markUntouched: false, extension: '.json', + stripDescriptions: true, }) .pipe(gulp.dest(`${DIST}/_locales`)); } @@ -140,6 +150,7 @@ function updateI18n() { .pipe(plumber(logError)) .pipe(i18n.extract({ base: 'src/_locales', + manifest: 'src/manifest.yml', touchedOnly: false, useDefaultLang: false, markUntouched: true, @@ -158,15 +169,15 @@ function copyZip() { 'node_modules/@zip.js/zip.js/dist/zip-no-worker.min.js', 'node_modules/@zip.js/zip.js/dist/z-worker.js', ]) - .pipe(gulp.dest(`${DIST}/public/lib`)); + .pipe(gulp.dest(`${DIST}/public/lib`)); } -const pack = gulp.parallel(manifest, createIcons, copyI18n); +const pack = gulp.parallel(createIcons, copyI18n, copyZip); exports.clean = clean; exports.manifest = manifest; -exports.dev = gulp.series(gulp.parallel(copyZip, pack, jsDev), watch); -exports.build = gulp.series(clean, gulp.parallel(copyZip, pack, jsProd)); +exports.dev = gulp.parallel(gulp.series(pack, watch), jsDev); +exports.build = gulp.series(clean, gulp.parallel(pack, jsProd)); exports.i18n = updateI18n; exports.check = checkI18n; exports.copyI18n = copyI18n; diff --git a/jsconfig.json b/jsconfig.json index 6210eef65b..ffdee473a1 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,9 +1,13 @@ { "compilerOptions": { "baseUrl": ".", + "jsx": "preserve", "paths": { - "#/*": ["./src/*"] + "@/*": ["./src/*"] } }, - "exclude": ["node_modules"] + "typeAcquisition": { + "include": ["./types.d.ts"] + }, + "exclude": ["node_modules", "dist"] } diff --git a/package.json b/package.json index cf4a1857b6..0c3fe7c441 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "violentmonkey", "description": "Violentmonkey", - "version": "2.12.14", + "version": "2.31.0", "scripts": { + "prepare": "husky install", "dev": "gulp dev", "prebuild": "yarn ci", "build": "cross-env NODE_ENV=production gulp build", @@ -11,42 +12,82 @@ "i18n": "gulp i18n", "copyI18n": "gulp copyI18n", "lint": "run-s lint:js lint:yml", - "lint:js": "eslint --ext .js,.vue .", + "lint:js": "eslint --ext .js,.vue . --cache", "lint:yml": "gulp check", - "svgo": "plaid svgo", - "test": "cross-env BABEL_ENV=test tape -r ./test/mock/register \"test/**/*.test.js\"", - "ci": "run-s lint test", - "transform": "locky yarn yarn", + "test": "cross-env BABEL_ENV=test TEST=test jest test", + "ci": "run-p lint test", "bumpVersion": "gulp bump", "bump": "run-s ci \"bumpVersion --commit\"", "preversion": "run-s ci \"bumpVersion --reset\"" }, "devDependencies": { - "@actions/core": "^1.2.6", - "@babel/plugin-proposal-function-bind": "^7.8.3", - "@babel/register": "^7.9.0", - "@gera2ld/locky": "^0.1.1", - "@gera2ld/plaid": "~1.5.6", - "@gera2ld/plaid-vue": "~1.5.5", - "@gera2ld/plaid-webpack": "~1.5.5", - "@types/chrome": "0.0.101", - "cross-env": "^7.0.2", - "cross-spawn": "^7.0.1", - "del": "^5.1.0", - "fancy-log": "^1.3.2", + "@actions/core": "^1.9.0", + "@actions/github": "^5.0.3", + "@babel/core": "^7.22.10", + "@babel/eslint-parser": "^7.22.15", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-function-bind": "^7.18.6", + "@babel/plugin-transform-runtime": "^7.22.10", + "@babel/preset-env": "^7.22.10", + "@babel/preset-typescript": "^7.22.5", + "@iconify-json/mdi": "^1.2.3", + "@types/chrome": "^0.0.208", + "@types/firefox-webext-browser": "94.0.1", + "@types/jest": "^29.5.3", + "@typescript-eslint/eslint-plugin": "^6.4.0", + "@typescript-eslint/parser": "^6.4.0", + "@violentmonkey/types": "^0.3.1", + "@vue/compiler-sfc": "^3.5.25", + "@vue/eslint-config-typescript": "^13.0.0", + "amo-upload": "^0.5.5", + "autoprefixer": "^10.4.15", + "babel-jest": "^29.6.2", + "babel-loader": "^9.1.3", + "babel-plugin-module-resolver": "^5.0.0", + "babel-plugin-transform-modern-regexp": "^0.0.6", + "cross-env": "^7.0.3", + "cross-spawn": "^7.0.3", + "css-loader": "^6.8.1", + "css-minimizer-webpack-plugin": "^7.0.0", + "del": "^6.1.1", + "eslint": "^8.57.1", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-jest": "^27.2.3", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-vue": "^9.27.0", + "fancy-log": "^2.0.0", "gulp": "^4.0.2", "gulp-plumber": "^1.1.0", - "husky": "^4.2.3", - "js-yaml": "^3.13.1", - "jsdom": "^16.2.1", + "html-webpack-plugin": "^5.6.0", + "husky": "^8.0.1", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "mini-css-extract-plugin": "^2.7.6", "npm-run-all": "^4.1.5", - "plugin-error": "^1.0.0", - "sharp": "^0.26.2", - "sign-addon": "^3.1.0", - "tape": "^4.13.2", - "through2": "^3.0.1", - "vinyl": "^2.1.0", - "wrapper-webpack-plugin": "2.1.0" + "plugin-error": "^2.0.0", + "postcss": "^8.4.28", + "postcss-calc": "^9.0.1", + "postcss-import": "^15.1.0", + "postcss-loader": "^7.3.3", + "postcss-nested": "^6.0.1", + "postcss-scss": "^4.0.4", + "postcss-simple-vars": "^6.0.3", + "prettier": "^3.0.2", + "sharp": "^0.32.6", + "style-loader": "^3.3.3", + "svg-sprite-loader": "^6.0.11", + "terser-webpack-plugin": "^5.3.9", + "through2": "^4.0.2", + "typescript": "^5.1.6", + "unplugin-icons": "^22.5.0", + "vinyl": "^2.2.1", + "vue-loader": "^17.4.2", + "vue-tsc": "^2.0.29", + "webpack": "^5.95.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-cli": "^5.1.4" }, "author": "Gerald ", "repository": { @@ -59,24 +100,21 @@ "homepage": "https://github.com/violentmonkey/violentmonkey", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.9.2", - "@violentmonkey/shortcut": "^1.2.3", - "@zip.js/zip.js": "^2.2.26", - "codemirror": "5.61.0", - "codemirror-js-mixed": "^0.9.2", - "core-js": "^3.6.4", - "tldjs": "^2.3.1", - "vue": "^2.6.11", - "vueleton": "^1.0.6" + "@violentmonkey/shortcut": "^1.4.4", + "@zip.js/zip.js": "2.4.4", + "codemirror": "^5.65.20", + "tldts": "^7.0.19", + "vue": "^3.5.25", + "vueleton": "^2.0.2" }, "engines": { - "node": ">=10" + "node": ">=20" }, - "husky": { - "hooks": { - "pre-commit": "yarn transform -t", - "pre-push": "yarn lint" - } + "jest": { + "setupFilesAfterEnv": [ + "./test/mock/index.js" + ], + "testEnvironment": "./test/mock/env.js" }, - "beta": 1 -} \ No newline at end of file + "beta": 6 +} diff --git a/scripts/action-helper.js b/scripts/action-helper.js index ee996dd917..7e966cf0fb 100644 --- a/scripts/action-helper.js +++ b/scripts/action-helper.js @@ -1,8 +1,10 @@ const core = require('@actions/core'); const { getVersion, isBeta } = require('./version-helper'); +const { exec } = require('./common'); -const version = getVersion(); +const version = process.env.VERSION || getVersion(); const beta = isBeta(); +const ci = process.argv.includes('ci'); const envs = { VERSION: version, @@ -17,18 +19,16 @@ const envs = { PRERELEASE: !!beta, TEMP_DIR: 'tmp', ASSETS_DIR: 'dist-assets', + GIT_DESCRIBE: ci ? exec('git describe --abbrev=7') : `v${version}`, + ACTION_BUILD_URL: process.env.ACTION_BUILD_URL, + DISCORD_WEBHOOK_RELEASE: process.env.DISCORD_WEBHOOK_RELEASE, }; +envs.SOURCE_ZIP = `${envs.RELEASE_PREFIX}-${envs.VERSION}-source.zip`; envs.ASSET_ZIP = `${envs.RELEASE_PREFIX}-webext-v${envs.VERSION}.zip`; +envs.ASSET_CWS_BETA_ZIP = `${envs.RELEASE_PREFIX}-webext-beta-v${envs.VERSION}.zip`; envs.ASSET_SELF_HOSTED_ZIP = `${envs.RELEASE_PREFIX}-webext-ffself-v${envs.VERSION}.zip`; -// TODO generate release notes by conventional commit messages and add installation instructions -envs.RELEASE_NOTE = beta ? `\ -**This is a beta release of Violentmonkey, use it at your own risk.** -` : `\ -See for more details. -`; - Object.entries(envs).forEach(([key, value]) => { core.exportVariable(key, value); }); diff --git a/scripts/amo-sign.js b/scripts/amo-sign.js deleted file mode 100644 index fd81d4337d..0000000000 --- a/scripts/amo-sign.js +++ /dev/null @@ -1,35 +0,0 @@ -const fs = require('fs').promises; -const path = require('path'); -const { signAddon } = require('sign-addon'); -const { readManifest, buildUpdatesList } = require('./manifest-helper'); -const { getVersion } = require('./version-helper'); - -async function main() { - const manifest = await readManifest(); - const rawVersion = process.env.VERSION; - // version may be suffixed for unlisted version - const version = getVersion(); - const result = await signAddon({ - xpiPath: path.join(process.env.TEMP_DIR, process.env.ASSET_SELF_HOSTED_ZIP), - version, - apiKey: process.env.AMO_KEY, - apiSecret: process.env.AMO_SECRET, - channel: 'unlisted', - downloadDir: process.env.ASSETS_DIR, - id: manifest.browser_specific_settings.gecko.id, - }); - if (!result.success) { - console.error(result); - if (!result.errorDetails?.startsWith('Version already exists.')) { - process.exitCode = 1; - return; - } - } - // const fileName = path.basename(result.downloadedFiles[0]); - const fileName = `violentmonkey-${version}-an+fx.xpi`; - const url = `https://github.com/violentmonkey/violentmonkey/releases/download/v${rawVersion}/${fileName}`; - const updates = await buildUpdatesList(version, url); - await fs.writeFile(path.join(process.env.TEMP_DIR, 'updates/updates.json'), JSON.stringify(updates, null, 2), 'utf8'); -} - -main(); diff --git a/scripts/amo-upload.mjs b/scripts/amo-upload.mjs new file mode 100644 index 0000000000..5911b5e5a4 --- /dev/null +++ b/scripts/amo-upload.mjs @@ -0,0 +1,111 @@ +import { signAddon } from 'amo-upload'; +import { mkdir, rename, writeFile } from 'fs/promises'; +import { join } from 'path'; +import { buildUpdatesList, readManifest } from './manifest-helper.js'; +import { hasAsset, notifyReleaseStatus } from './release-helper.mjs'; +import { getVersion, isBeta } from './version-helper.js'; + +const version = getVersion(); +const beta = isBeta(); + +async function handleAddon() { + const manifest = await readManifest(); + const fileName = `violentmonkey-${version}${beta ? 'b' : ''}.xpi`; + const url = `https://github.com/violentmonkey/violentmonkey/releases/download/v${version}/${fileName}`; + + if (await hasAsset(fileName)) { + // Throw an error so `updates.json` won't be updated in the next step. + throw new Error('File already downloaded, skipping'); + } + + const tempFile = join( + process.env.TEMP_DIR, + Math.random().toString(36).slice(2, 8).toString(), + ); + const releaseUrl = `https://github.com/violentmonkey/violentmonkey/releases/tag/v${version}`; + await signAddon({ + apiKey: process.env.AMO_KEY, + apiSecret: process.env.AMO_SECRET, + addonId: manifest.browser_specific_settings.gecko.id, + addonVersion: version, + channel: beta ? 'unlisted' : 'listed', + compatibility: { + android: { min: '121.0a1' }, + firefox: { min: '57.0' }, + }, + ...(process.env.AMO_PUBLISH + ? { + distFile: beta + ? join(process.env.TEMP_DIR, process.env.ASSET_SELF_HOSTED_ZIP) + : join(process.env.ASSETS_DIR, process.env.ASSET_ZIP), + sourceFile: join(process.env.TEMP_DIR, process.env.SOURCE_ZIP), + approvalNotes: `\ +yarn && yarn build +`, + releaseNotes: { + 'en-US': `\ +Please follow the link below to view the change log: + +${releaseUrl} +`, + }, + } + : {}), + output: tempFile, + + // Don't poll since the review process takes quite a long time + pollRetry: 0, + }); + + const xpiFile = join(process.env.ASSETS_DIR, fileName); + await mkdir(process.env.ASSETS_DIR, { recursive: true }); + await rename(tempFile, xpiFile); + + const updates = await buildUpdatesList(version, url); + await writeFile( + join(process.env.TEMP_DIR, 'updates/updates.json'), + JSON.stringify(updates, null, 2), + 'utf8', + ); +} + +async function main() { + let error; + try { + await handleAddon(); + } catch (err) { + if (err?.message === 'Polling skipped') { + error = beta ? new Error('Pending review') : undefined; + } else { + error = err; + } + } + if (error) throw error; +} + +main().then( + () => { + notifyReleaseStatus({ + title: `AMO Release Success: ${process.env.RELEASE_NAME}`, + description: `See the changelog at https://github.com/violentmonkey/violentmonkey/releases/tag/v${process.env.VERSION}.`, + }); + }, + (err) => { + // if (err instanceof FatalError) { + notifyReleaseStatus({ + title: `AMO Release Failure: ${process.env.RELEASE_NAME}`, + description: [ + 'An error occurred:', + '', + `> ${err}`, + ...(process.env.ACTION_BUILD_URL + ? ['', `See ${process.env.ACTION_BUILD_URL} for more details.`] + : []), + ].join('\n'), + success: false, + }); + // } + console.error(err); + process.exitCode = 1; + }, +); diff --git a/scripts/babel-plugin-safe-bind.js b/scripts/babel-plugin-safe-bind.js new file mode 100644 index 0000000000..8b510f4a4c --- /dev/null +++ b/scripts/babel-plugin-safe-bind.js @@ -0,0 +1,106 @@ +/* +Modified the original to use a global `safeCall`: +https://babeljs.io/docs/en/babel-plugin-proposal-function-bind + +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = void 0; + +var _helperPluginUtils = require("@babel/helper-plugin-utils"); + +var _pluginSyntaxFunctionBind = require("@babel/plugin-syntax-function-bind"); + +var _core = require("@babel/core"); + +var _default = (0, _helperPluginUtils.declare)(api => { + api.assertVersion(7); + + function getTempId(scope) { + let id = scope.path.getData("functionBind"); + if (id) return _core.types.cloneNode(id); + id = scope.generateDeclaredUidIdentifier("context"); + return scope.path.setData("functionBind", id); + } + + function getStaticContext(bind, scope) { + const object = bind.object || bind.callee.object; + return scope.isStatic(object) && (_core.types.isSuper(object) ? _core.types.thisExpression() : object); + } + + function inferBindContext(bind, scope) { + const staticContext = getStaticContext(bind, scope); + if (staticContext) return _core.types.cloneNode(staticContext); + const tempId = getTempId(scope); + + if (bind.object) { + bind.callee = _core.types.sequenceExpression([_core.types.assignmentExpression("=", tempId, bind.object), bind.callee]); + } else { + bind.callee.object = _core.types.assignmentExpression("=", tempId, bind.callee.object); + } + + return _core.types.cloneNode(tempId); + } + + return { + name: "safe-function-bind", + inherits: _pluginSyntaxFunctionBind.default, + visitor: { + CallExpression({ + node, + scope + }) { + const bind = node.callee; + if (!_core.types.isBindExpression(bind)) return; + // ORIGINAL: + // const context = inferBindContext(bind, scope); + // node.callee = _core.types.memberExpression(bind.callee, _core.types.identifier("call")); + // node.arguments.unshift(context); + // MODIFIED to use safeCall created in safe-globals.js: + const object = bind.object || bind.callee.object; + const context = scope.isStatic(object) && _core.types.isSuper(object) + ? _core.types.thisExpression() + : object; + node.callee = _core.types.identifier("safeCall"); + node.arguments.unshift(bind.callee, _core.types.cloneNode(context)); + }, + + BindExpression(path) { + const { + node, + scope + } = path; + const context = inferBindContext(node, scope); + path.replaceWith(_core.types.callExpression(_core.types.memberExpression(node.callee, _core.types.identifier("bind")), [context])); + } + + } + }; +}); + +exports.default = _default; diff --git a/scripts/common.js b/scripts/common.js new file mode 100644 index 0000000000..d354303ec2 --- /dev/null +++ b/scripts/common.js @@ -0,0 +1,21 @@ +const childProcess = require('child_process'); +const path = require('path'); + +const isProd = process.env.NODE_ENV === 'production'; + +function exec(cmd) { + try { + return childProcess.execSync(cmd, { encoding: 'utf8' }).trim(); + } catch (e) { + // ignore + } +} + +exports.isProd = isProd; +exports.alias = { + '@': path.resolve('src'), +}; +exports.extensions = [ + '.ts', '.tsx', '.mjs', '.js', '.jsx', '.vue', +]; +exports.exec = exec; diff --git a/scripts/config-helper.js b/scripts/config-helper.js new file mode 100644 index 0000000000..524f97c77c --- /dev/null +++ b/scripts/config-helper.js @@ -0,0 +1,49 @@ +const fs = require('fs'); + +class ConfigLoader { + data = {}; + + add(values) { + Object.assign(this.data, values); + return this; + } + + env() { + return this.add(process.env); + } + + envFile(filepath = '.env') { + let content = ''; + try { + content = fs.readFileSync(filepath, 'utf8'); + } catch { + // ignore error + } + const values = content + .split('\n') + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith('#')) + .map((line) => { + const i = line.indexOf('='); + if (i < 0) return []; + const key = line.slice(0, i).trim(); + let value = line.slice(i + 1).trim(); + // Note: escaped characters are not supported + if (/^(['"]).*\1$/.test(value)) value = value.slice(1, -1); + return [key, value]; + }) + .reduce((prev, [key, value]) => { + if (key) prev[key] = value; + return prev; + }, {}); + return this.add(values); + } + + get(key, def) { + return this.data[key] ?? def; + } +} + +exports.ConfigLoader = ConfigLoader; + +exports.configLoader = new ConfigLoader(); diff --git a/scripts/fake-dep-loader.js b/scripts/fake-dep-loader.js new file mode 100644 index 0000000000..89fb2107d0 --- /dev/null +++ b/scripts/fake-dep-loader.js @@ -0,0 +1,6 @@ +const Path = require('path'); + +module.exports = function FakeDepLoader(source, sourcemap) { + this.query.files.forEach(f => this.addDependency(Path.resolve(f))); + this.callback(null, source, sourcemap); +}; diff --git a/scripts/i18n.js b/scripts/i18n.js index d41b9c64af..d39f2320a8 100644 --- a/scripts/i18n.js +++ b/scripts/i18n.js @@ -6,7 +6,7 @@ const through = require('through2'); const yaml = require('js-yaml'); const transformers = { - '.yml': data => yaml.safeLoad(data), + '.yml': data => yaml.load(data), '.json': data => JSON.parse(data), }; @@ -52,11 +52,19 @@ class Locale { return this.data[key]; } - dump(data, { extension }) { + dump(data, { extension, stripDescriptions }) { if (extension === '.json') { + if (stripDescriptions) { + data = Object.entries(data).reduce((res, [key, value]) => { + // eslint-disable-next-line no-unused-vars + const { description, ...stripped } = value; + res[key] = stripped; + return res; + }, {}); + } data = JSON.stringify(data, null, 2); } else if (extension === '.yml') { - data = yaml.safeDump(data, { sortKeys: true }); + data = yaml.dump(data, { sortKeys: true }); } else { throw 'Unknown extension name!'; } @@ -99,12 +107,18 @@ class Locales { options = options || {}; const data = {}; const langData = this.locales[lang]; - const defaultData = options.useDefaultLang && lang != this.defaultLang && this.locales[this.defaultLang]; + const defaultData = options.useDefaultLang && lang !== this.defaultLang + ? this.locales[this.defaultLang] + : null; + const colons = defaultData && !/^(ja|ko|zh)/.test(lang); Object.keys(this.data).forEach(key => { if (options.touchedOnly && !this.data[key].touched) return; + const msg = langData.getMessage(key) || defaultData?.getMessage(key) || ''; data[key] = { description: this.data[key].description || this.newLocaleItem, - message: langData.getMessage(key) || defaultData && defaultData.getMessage(key) || '', + message: colons + ? normalizeTrailingColon(msg, defaultData.getMessage(key)) + : msg, }; if (options.markUntouched && !this.data[key].touched) data[key].touched = false; }); @@ -135,7 +149,7 @@ function extract(options) { default: ['\\b(?:i18n\\(\'|i18n-key=")(\\w+)[\'"]', 1], json: ['__MSG_(\\w+)__', 1], }; - const types = { + const typePatternMap = { '.js': 'default', '.json': 'json', '.html': 'default', @@ -151,7 +165,7 @@ function extract(options) { const pattern = new RegExp(patternData[0], 'g'); const groupId = patternData[1]; let groups; - while (groups = pattern.exec(data)) { + while ((groups = pattern.exec(data))) { keys.add(groups[groupId]); } }); @@ -161,7 +175,7 @@ function extract(options) { if (file.isNull()) return cb(); if (file.isStream()) return this.emit('error', new PluginError('VM-i18n', 'Stream is not supported.')); const extname = path.extname(file.path); - const type = types[extname]; + const type = typePatternMap[extname]; if (type) extractFile(file.contents, type); cb(); } @@ -187,6 +201,10 @@ function extract(options) { .catch(cb); } + if (options.manifest) { + const mf = require('fs').readFileSync(options.manifest, 'utf8'); + extractFile(mf, 'json'); + } return through.obj(bufferContents, endStream); } @@ -196,6 +214,16 @@ function read(options) { return stream; } +function normalizeTrailingColon(str, sourceStr = '') { + if (sourceStr.endsWith(': ') && str.endsWith(':')) { + return str + ' '; + } + if (sourceStr.endsWith(':') && str.endsWith(': ')) { + return str.slice(0, -1); + } + return str; +} + module.exports = { extract, read, diff --git a/scripts/manifest-helper.js b/scripts/manifest-helper.js index 5023e14bc3..70bec37698 100644 --- a/scripts/manifest-helper.js +++ b/scripts/manifest-helper.js @@ -4,19 +4,21 @@ const { getVersion, isBeta } = require('./version-helper'); async function readManifest() { const input = await fs.readFile('src/manifest.yml', 'utf8'); - const data = yaml.safeLoad(input); + const data = yaml.load(input); return data; } -async function buildManifest() { - const data = await readManifest(); +async function buildManifest(base) { + const data = base ? { ...base } : await readManifest(); data.version = getVersion(); if (process.env.TARGET === 'selfHosted') { data.browser_specific_settings.gecko.update_url = 'https://raw.githubusercontent.com/violentmonkey/violentmonkey/updates/updates.json'; } if (isBeta()) { // Do not support i18n in beta version - data.name = data.browser_action.default_title = 'Violentmonkey BETA'; + const name = 'Violentmonkey BETA'; + data.name = name; + data.browser_action.default_title = name; } return data; } @@ -32,12 +34,36 @@ async function buildUpdatesList(version, url) { update_link: url, }, ], - } + }, }, }; return data; } +class ListBackgroundScriptsPlugin { + constructor({ minify } = {}) { + this.minify = minify; + } + + apply(compiler) { + compiler.hooks.afterEmit.tap(this.constructor.name, async compilation => { + const dist = compilation.outputOptions.path; + const path = `${dist}/manifest.json`; + const manifest = await buildManifest(); + const bgId = 'background/index'; + const bgEntry = compilation.entrypoints.get(bgId); + const scripts = bgEntry.chunks.flatMap(c => [...c.files]); + if (`${manifest.background.scripts}` !== `${scripts}`) { + manifest.background.scripts = scripts; + await fs.writeFile(path, + JSON.stringify(manifest, null, this.minify ? 0 : 2), + { encoding: 'utf8' }); + } + }); + } +} + exports.readManifest = readManifest; exports.buildManifest = buildManifest; exports.buildUpdatesList = buildUpdatesList; +exports.ListBackgroundScriptsPlugin = ListBackgroundScriptsPlugin; diff --git a/scripts/plaid.conf.js b/scripts/plaid.conf.js deleted file mode 100644 index 449fafc57a..0000000000 --- a/scripts/plaid.conf.js +++ /dev/null @@ -1,67 +0,0 @@ -const { isProd } = require('@gera2ld/plaid/util'); - -/** - * For each entry, `key` is the chunk name, `value` has following properties: - * - value.entry: webpack entry. - * - value.html: options object passed to HtmlWebpackPlugin. - * - value.html.inlineSource: if true, JS and CSS files will be inlined in HTML. - */ -const injectTo = item => { - if (!(item.attributes.src || '').endsWith('/index.js')) return 'head'; -}; -const htmlFactory = extra => options => ({ - ...options, - title: 'Violentmonkey', - ...extra, - chunks: ['browser', ...options.chunks], - injectTo, -}); -exports.pages = { - 'browser': { - entry: './src/common/browser', - }, - 'background/index': { - entry: './src/background', - html: htmlFactory(), - }, - 'options/index': { - entry: './src/options', - html: htmlFactory(), - }, - 'confirm/index': { - entry: './src/confirm', - html: htmlFactory(), - }, - 'popup/index': { - entry: './src/popup', - html: htmlFactory(), - }, -}; - -const splitVendor = prefix => ({ - [prefix]: { - test: new RegExp(`node_modules[/\\\\]${prefix}.*?\\.js`), - name: `public/lib/${prefix}`, - chunks: 'all', - priority: 100, - }, -}); - -exports.devServer = false; -exports.devtool = isProd ? false : 'inline-source-map'; -exports.optimization = { - runtimeChunk: false, - splitChunks: { - cacheGroups: { - common: { - name: 'common', - minChunks: 2, - enforce: true, - chunks: chunk => chunk.name !== 'browser', - }, - ...splitVendor('codemirror'), - ...splitVendor('tldjs'), - ...splitVendor('vue'), - }, - }, -}; diff --git a/scripts/release-helper.mjs b/scripts/release-helper.mjs new file mode 100644 index 0000000000..7c48abe161 --- /dev/null +++ b/scripts/release-helper.mjs @@ -0,0 +1,117 @@ +import { readdir, readFile } from 'fs/promises'; +import { join } from 'path'; +import github from '@actions/github'; +import { exec } from './common.js'; + +const { VERSION, ASSETS_DIR, GITHUB_TOKEN } = process.env; +const tag = `v${VERSION}`; + +let octokit; +function getOctokit() { + octokit ||= github.getOctokit(GITHUB_TOKEN); + return octokit; +} + +export async function getRelease() { + try { + const result = await getOctokit().rest.repos.getReleaseByTag({ + ...github.context.repo, + tag, + }); + console.info('Found release:', tag); + return result.data; + } catch (err) { + if (err.status !== 404) throw err; + } +} + +function listCommits() { + const thisTag = exec('git describe --abbrev=0 --tags'); + const prevTag = exec(`git describe --abbrev=0 --tags "${thisTag}^"`); + const tagRange = `${prevTag}...${thisTag}`; + const list = exec(`git log --oneline --skip=1 --reverse "${tagRange}"`) + .replace(/ `${str.split(/\s/, 2)[1]}${10000 + i}\n* ${str}`) + .sort() + .map(str => str.split('\n')[1]) + .join('\n'); + return `${prevTag}:\n${list}\n\nCommit log: ${ + process.env.GITHUB_SERVER_URL || 'https://github.com' + }/${ + process.env.GITHUB_REPOSITORY || 'violentmonkey/violentmonkey' + }/compare/${tagRange}`; +} + +function getReleaseNote() { + return `${process.env.PRERELEASE === 'true' ? `\ +**This is a beta release of Violentmonkey (also in [WebStore](\ +https://chrome.google.com/webstore/detail/violentmonkey-beta/opokoaglpekkimldnlggpoagmjegichg\ +)), use it at your own risk.**
\ +If you already use Violentmonkey, click \`Export to zip\` in settings before installing the beta. + +` : ''}Notable changes since ${listCommits()}`; +} + +export async function createRelease() { + console.info('Create release:', tag); + const result = await getOctokit().rest.repos.createRelease({ + ...github.context.repo, + tag_name: tag, + name: process.env.RELEASE_NAME, + body: getReleaseNote(), + prerelease: process.env.PRERELEASE == 'true', + }); + return result.data; +} + +export async function ensureRelease() { + const release = await getRelease() || await createRelease(); + return release; +} + +export async function hasAsset(fileName) { + const release = await getRelease(); + return release?.assets.some(asset => asset.name === fileName); +} + +export async function uploadAssets() { + const release = await ensureRelease(); + let assets = await readdir(ASSETS_DIR); + assets = assets.filter(asset => release.assets.every(({ name }) => name !== asset)); + for (const asset of assets) { + console.info(`> Upload asset: ${asset}`); + await getOctokit().rest.repos.uploadReleaseAsset({ + ...github.context.repo, + release_id: release.id, + name: asset, + data: await readFile(join(ASSETS_DIR, asset)), + }); + } + if (assets.length) console.info('Done'); + else console.info('No asset to upload'); +} + +export async function notifyReleaseStatus({ title, description, success = true }) { + const { DISCORD_WEBHOOK_RELEASE } = process.env; + if (!DISCORD_WEBHOOK_RELEASE) { + console.warn('DISCORD_WEBHOOK_RELEASE is not available!'); + return; + } + const res = await fetch(DISCORD_WEBHOOK_RELEASE, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + embeds: [ + { + title, + description, + color: success ? 0x00ff00 : 0xff0000, + }, + ], + }), + }); + if (!res.ok) console.error(res); +} diff --git a/scripts/sandbox-globals.html b/scripts/sandbox-globals.html deleted file mode 100644 index 98132e6722..0000000000 --- a/scripts/sandbox-globals.html +++ /dev/null @@ -1,52 +0,0 @@ -

Run it both in Chrome and FF

- - - diff --git a/scripts/transifex.js b/scripts/transifex.js deleted file mode 100644 index b688156925..0000000000 --- a/scripts/transifex.js +++ /dev/null @@ -1,205 +0,0 @@ -const fs = require('fs').promises; -const spawn = require('cross-spawn'); -const yaml = require('js-yaml'); - -function delay(time) { - return new Promise(resolve => setTimeout(resolve, time)); -} - -function defer() { - const deferred = {}; - deferred.promise = new Promise((resolve, reject) => { - deferred.resolve = resolve; - deferred.reject = reject; - }); - return deferred; -} - -function memoize(fn) { - const cache = {}; - function wrapped(...args) { - const key = args.toString(); - let result = cache[key]; - if (!result) { - result = { data: fn(...args) }; - cache[key] = result; - } - return result.data; - } - return wrapped; -} - -function exec(cmd, args, options) { - return new Promise((resolve, reject) => { - const { stdin, ...rest } = options || {}; - const child = spawn( - cmd, - args, - { ...rest, stdio: ['pipe', 'pipe', 'inherit'] }, - ); - if (stdin != null) { - child.stdin.write(stdin); - child.stdin.end(); - } - const stdoutBuffer = []; - child.stdout.on('data', chunk => { - stdoutBuffer.push(chunk); - }) - child.on('exit', (code) => { - if (code) { - reject(code); - return; - } - const result = Buffer.concat(stdoutBuffer).toString('utf8'); - resolve(result); - }); - }); -} - -let lastRequest; -async function transifexRequest(url, { - method = 'GET', - responseType = 'json', - data = null, -} = {}) { - const deferred = defer(); - const prevRequest = lastRequest; - lastRequest = deferred.promise; - try { - await prevRequest; - let result = await exec( - 'curl', - [ - '-sSL', - '--user', - `api:${process.env.TRANSIFEX_TOKEN}`, - '-X', - method, - '-H', - 'Content-Type: application/json', - ...data == null ? [] : ['-d', '@-'], - `https://www.transifex.com${url}`, - ], - { - stdin: data ? JSON.stringify(data) : null, - }, - ); - if (responseType === 'json') { - result = JSON.parse(result); - } - deferred.resolve(delay(500)); - return result; - } catch (err) { - deferred.reject(err); - throw err; - } -} - -async function getLanguages() { - const result = await transifexRequest('/api/2/project/violentmonkey-nex/?details'); - return result.teams; -} - -async function loadRemote(lang) { - // Reference: https://docs.transifex.com/api/translations#downloading-and-uploading-translations - // Use translated messages since we don't have enough reviewers - const result = await transifexRequest(`/api/2/project/violentmonkey-nex/resource/messagesjson/translation/${lang}/?mode=onlytranslated`); - const remote = JSON.parse(result.content); - return remote; -} - -const loadData = memoize(async function loadData(lang) { - const remote = await loadRemote(lang); - const filePath = `src/_locales/${lang}/messages.yml`; - const local = yaml.safeLoad(await fs.readFile(filePath, 'utf8')); - return { local, remote, filePath }; -}); - -const loadUpdatedLocales = memoize(async function loadUpdatedLocales() { - const diffUrl = process.env.DIFF_URL; - if (!diffUrl) return; - const result = await exec('curl', ['-sSL', diffUrl]); - // Example: - // diff --git a/src/_locales/ko/messages.yml b/src/_locales/ko/messages.yml - const codes = result.split('\n') - .map(line => { - const matches = line.match(/^diff --git a\/src\/_locales\/([^/]+)\/messages.yml b\/src\/_locales\/([^/]+)\/messages.yml$/); - const [, code1, code2] = matches || []; - return code1 === code2 && code1; - }) - .filter(Boolean); - return codes; -}); - -async function pushTranslations(lang) { - const codes = await loadUpdatedLocales(); - // Limit to languages changed in this PR only - if (codes && !codes.includes(lang)) return; - const { local, remote } = await loadData(lang); - const remoteUpdate = {}; - Object.entries(local) - .forEach(([key, value]) => { - const remoteMessage = remote[key] && remote[key].message; - if (value.touched !== false && value.message && value.message !== remoteMessage) remoteUpdate[key] = value; - }); - if (Object.keys(remoteUpdate).length) { - const strings = await transifexRequest(`/api/2/project/violentmonkey-nex/resource/messagesjson/translation/${lang}/strings/`); - const updates = strings.filter(({ key, reviewed }) => !reviewed && remoteUpdate[key]) - .map(({ key, string_hash }) => ({ - source_entity_hash: string_hash, - translation: remoteUpdate[key].message, - })); - process.stdout.write(`\n Uploading translations for ${lang}:\n ${JSON.stringify(updates)}\n`); - await transifexRequest(`/api/2/project/violentmonkey-nex/resource/messagesjson/translation/${lang}/strings/`, { - method: 'PUT', - responseType: 'text', - data: updates, - }); - process.stdout.write(' finished\n'); - } else { - process.stdout.write('up to date\n'); - } -} - -async function pullTranslations(code) { - const { local, remote, filePath } = await loadData(code); - Object.entries(local) - .forEach(([key, value]) => { - const remoteMessage = remote[key] && remote[key].message; - if (remoteMessage) value.message = remoteMessage; - }); - await fs.writeFile(filePath, yaml.safeDump(local), 'utf8'); -} - -async function main() { - let handle; - if (process.argv.includes('push')) handle = pushTranslations; - else if (process.argv.includes('pull')) handle = pullTranslations; - else process.exit(2); - process.stdout.write('Loading languages...'); - const codes = await getLanguages(); - process.stdout.write('OK\n'); - process.stdout.write(`Got ${codes.length} language codes\n`); - for (const code of codes) { - await fs.mkdir(`src/_locales/${code}`, { recursive: true }); - } - spawn.sync('yarn', ['i18n'], { stdio: 'inherit' }); - let current = 0; - const showProgress = (lang) => { - process.stdout.write(`\rLoading translations ${lang} (${current}/${codes.length})...`); - }; - for (const code of codes) { - current += 1; - showProgress(code); - try { - await handle(code); - } catch (err) { - process.stderr.write(`\nError pulling ${code}\n`) - throw err; - } - } - showProgress('OK'); - process.stdout.write('\n'); -} - -main(); diff --git a/scripts/transifex.mjs b/scripts/transifex.mjs new file mode 100644 index 0000000000..4c20362808 --- /dev/null +++ b/scripts/transifex.mjs @@ -0,0 +1,329 @@ +import { readFile, writeFile, mkdir } from 'fs/promises'; +import spawn from 'cross-spawn'; +import yaml from 'js-yaml'; + +const PROJECT_ID = 'o:violentmonkey:p:violentmonkey-nex'; +const RESOURCE_ID = `${PROJECT_ID}:r:messagesjson`; +const RESOURCE_FILE = 'src/_locales/en/messages.yml'; +const request = limitConcurrency(doRequest, 5); + +function delay(time) { + return new Promise(resolve => setTimeout(resolve, time)); +} + +function defer() { + const deferred = {}; + deferred.promise = new Promise((resolve, reject) => { + deferred.resolve = resolve; + deferred.reject = reject; + }); + return deferred; +} + +function memoize(fn) { + const cache = {}; + function wrapped(...args) { + const key = args.toString(); + let result = cache[key]; + if (!result) { + result = { data: fn(...args) }; + cache[key] = result; + } + return result.data; + } + return wrapped; +} + +function limitConcurrency(fn, concurrency) { + const tokens = []; + const processing = new Set(); + async function getToken() { + const token = defer(); + tokens.push(token); + check(); + await token.promise; + return token; + } + function releaseToken(token) { + processing.delete(token); + check(); + } + function check() { + while (tokens.length && processing.size < concurrency) { + const token = tokens.shift(); + processing.add(token); + token.resolve(); + } + } + async function limited(...args) { + const token = await getToken(); + try { + return await fn(...args); + } finally { + releaseToken(token); + } + } + return limited; +} + +async function doRequest(path, options) { + options = { + method: 'GET', + ...options, + }; + const init = { + method: options.method, + headers: { + accept: 'application/vnd.api+json', + ...options.headers, + authorization: `Bearer ${process.env.TRANSIFEX_TOKEN}`, + }, + }; + const qs = options.query ? `?${new URLSearchParams(options.query)}` : ''; + if (options.body) { + init.headers['content-type'] ||= 'application/vnd.api+json'; + init.body = JSON.stringify(options.body); + } + if (!path.includes('://')) path = `https://rest.api.transifex.com${path}`; + const resp = await fetch(path + qs, init); + const isJson = /[+/]json$/.test(resp.headers.get('content-type').split(';')[0]); + const result = await resp[isJson ? 'json' : 'text'](); + if (!resp.ok) throw { resp, result }; + return { resp, result }; +} + +async function uploadResource() { + const source = yaml.load(await readFile(RESOURCE_FILE, 'utf8')); + const content = Object.entries(source).reduce((prev, [key, value]) => { + if (value.touched !== false) { + prev[key] = { + description: value.description, + message: value.message, + }; + } + return prev; + }, {}); + let { result } = await request('/resource_strings_async_uploads', { + method: 'POST', + body: { + data: { + type: 'resource_strings_async_uploads', + attributes: { + content: JSON.stringify(content), + content_encoding: 'text', + }, + relationships: { + resource: { + data: { + id: RESOURCE_ID, + type: 'resources', + }, + }, + }, + }, + }, + }); + while (['pending', 'processing'].includes(result.data.attributes.status)) { + await delay(500); + ({ result } = await request(`/resource_strings_async_uploads/${result.data.id}`)); + } + if (result.data.attributes.status !== 'succeeded') throw { result }; + return result.data.attributes.details; +} + +async function getLanguages() { + const { result } = await request(`/projects/${PROJECT_ID}/languages`); + return result.data.map(({ attributes: { code } }) => code); +} + +async function loadRemote(lang) { + let { resp, result } = await request('/resource_translations_async_downloads', { + method: 'POST', + body: { + data: { + type: 'resource_translations_async_downloads', + attributes: { + mode: 'onlytranslated', + }, + relationships: { + language: { + data: { + id: `l:${lang}`, + type: 'languages', + }, + }, + resource: { + data: { + id: RESOURCE_ID, + type: 'resources', + }, + }, + }, + }, + }, + }); + while (!resp.redirected && ['pending', 'processing', 'succeeded'].includes(result.data.attributes.status)) { + await delay(500); + ({ resp, result } = await request(`/resource_translations_async_downloads/${result.data.id}`)); + } + if (!resp.redirected) throw { resp, result }; + return result; +} + +async function getTranslations(lang) { + let { result } = await request('/resource_translations', { + query: { + 'filter[resource]': RESOURCE_ID, + 'filter[language]': `l:${lang}`, + include: 'resource_string', + }, + }); + let { data, included } = result; + while (result.links.next) { + ({ result } = await request(result.links.next)); + data = data.concat(result.data); + included = included.concat(result.included); + } + const includedMap = included.reduce((prev, item) => { + prev[item.id] = item; + return prev; + }, {}); + data.forEach(item => { + Object.assign(item.relationships.resource_string.data, includedMap[item.relationships.resource_string.data.id]); + }); + return data; +} + +async function updateTranslations(updates) { + const { result } = await request('/resource_translations', { + method: 'PATCH', + headers: { + 'content-type': 'application/vnd.api+json;profile="bulk"', + }, + body: { + data: updates, + }, + }); + return result.data; +} + +const loadData = memoize(async function loadData(lang) { + const remote = await loadRemote(lang); + const filePath = `src/_locales/${lang}/messages.yml`; + const local = yaml.load(await readFile(filePath, 'utf8')); + return { local, remote, filePath }; +}); + +async function loadUpdatedLocales() { + const diffUrl = process.env.DIFF_URL; + if (!diffUrl) return; + const res = await fetch(diffUrl); + const result = await res.text(); + // Example: + // diff --git a/src/_locales/ko/messages.yml b/src/_locales/ko/messages.yml + const langs = result.split('\n') + .map(line => { + const matches = line.match(/^diff --git a\/src\/_locales\/([^/]+)\/messages.yml b\/src\/_locales\/([^/]+)\/messages.yml$/); + const [, code1, code2] = matches || []; + return code1 === code2 && code1; + }) + .filter(Boolean); + return langs; +} + +async function pushTranslations(lang) { + const { local, remote } = await loadData(lang); + const remoteUpdate = {}; + Object.entries(local) + .forEach(([key, value]) => { + const remoteMessage = remote[key] && remote[key].message; + if (value.touched !== false && value.message && value.message !== remoteMessage) remoteUpdate[key] = value; + }); + if (Object.keys(remoteUpdate).length) { + const strings = await getTranslations(lang); + const updates = strings.filter(item => !item.attributes.reviewed && remoteUpdate[item.relationships.resource_string.data.attributes.key]) + .map(item => ({ + id: item.id, + type: item.type, + attributes: { + strings: { + other: remoteUpdate[item.relationships.resource_string.data.attributes.key].message, + }, + }, + })); + process.stdout.write(`\n Uploading translations for ${lang}:\n ${JSON.stringify(updates)}\n`); + await updateTranslations(updates); + process.stdout.write(' finished\n'); + } else { + process.stdout.write('up to date\n'); + } +} + +async function pullTranslations(lang) { + const { local, remote, filePath } = await loadData(lang); + Object.entries(local) + .forEach(([key, value]) => { + const remoteMessage = remote[key] && remote[key].message; + if (remoteMessage) value.message = remoteMessage; + }); + await writeFile(filePath, yaml.dump(local), 'utf8'); +} + +async function batchHandle(handle, allowedLangs) { + process.stdout.write('Loading languages...'); + let langs = await getLanguages(); + process.stdout.write('OK\n'); + process.stdout.write(`Got ${langs.length} language codes\n`); + for (const lang of langs) { + await mkdir(`src/_locales/${lang}`, { recursive: true }); + } + spawn.sync('yarn', ['i18n'], { stdio: 'inherit' }); + if (allowedLangs) langs = langs.filter(lang => allowedLangs.includes(lang)); + let finished = 0; + const showProgress = () => { + process.stdout.write(`\rHandling translations (${finished}/${langs.length})...`); + }; + showProgress(); + await Promise.all(langs.map(async lang => { + try { + await handle(lang); + finished += 1; + showProgress(); + } catch (err) { + process.stderr.write(`\nError handling ${lang}\n`); + throw err; + } + })); + process.stdout.write('\n'); +} + +async function updateResource() { + const result = await uploadResource(); + console.log(Object.entries(result).map(([key, value]) => `${key}: ${value}`).join(', ')); +} + +async function main() { + const [,, command] = process.argv; + switch (command) { + case 'update': { + return updateResource(); + } + case 'push': { + // Limit to languages changed in this PR only + const allowedLangs = await loadUpdatedLocales(); + return batchHandle(pushTranslations, allowedLangs); + } + case 'pull': { + return batchHandle(pullTranslations); + } + default: { + throw new Error(`Unknown command: ${command}`); + } + } +} + +main().catch(err => { + console.error(err); + if (err?.result) console.error('Response:', JSON.stringify(err.result, null, 2)); + process.exitCode = 1; +}); diff --git a/scripts/version-helper.js b/scripts/version-helper.js index 6bd251ad1e..ca60608145 100644 --- a/scripts/version-helper.js +++ b/scripts/version-helper.js @@ -6,12 +6,7 @@ const pkg = require('../package.json'); * > manifest.version = `${pkg.version}.${pkg.beta}` */ function getVersion() { - let version = pkg.version.replace(/-[^.]*/, ''); - if (pkg.beta) version += `.${pkg.beta}`; - // Create a beta release with the same code as in stable release. - // Used in unlisted version. - else if (process.env.BETA) version += 'b'; - return version; + return `${pkg.version.match(/\d+\.\d+/)[0]}.${pkg.beta || 0}`; } function isBeta() { diff --git a/scripts/webpack-base.js b/scripts/webpack-base.js new file mode 100644 index 0000000000..c9b0fc986f --- /dev/null +++ b/scripts/webpack-base.js @@ -0,0 +1,241 @@ +const { resolve } = require('path'); +const { VueLoaderPlugin } = require('vue-loader'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); +const deepmerge = require('deepmerge'); +const GroupAssetsPlugin = require('./webpack-group-assets-plugin'); +const { alias, extensions, isProd } = require('./common'); + +const defaultHtmlOptions = { + minify: isProd && { + collapseWhitespace: true, + removeAttributeQuotes: true, + removeComments: true, + removeOptionalTags: true, + removeRedundantAttributes: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true, + }, + meta: { viewport: 'width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0' }, +}; +const MIN_OPTS = { + extractComments: false, + parallel: true, + terserOptions: { + compress: { + // `terser` often inlines big one-time functions inside a small "hot" function + reduce_funcs: false, + }, + output: { + ascii_only: true, + comments: false, + wrap_func_args: false, // disabling a premature optimization designed for old browsers + }, + }, +}; +const MIN_OPTS_PUBLIC = isProd && { + include: 'public/', + ...MIN_OPTS, +}; +const MIN_OPTS_MAIN = isProd && deepmerge.all([{}, MIN_OPTS, { + exclude: 'public/', + terserOptions: { + compress: { + ecma: 8, // ES2017 Object.entries and so on + passes: 2, // necessary now since we removed plaid's minimizer + unsafe_arrows: true, // it's 'safe' since we don't rely on function prototypes + }, + }, +}]); +const nodeModules = resolve('node_modules'); + +const pages = [ + 'background', + 'confirm', + 'options', + 'popup', +]; +const createHtmlPage = key => new HtmlWebpackPlugin({ + ...defaultHtmlOptions, + filename: `${key}/index.html`, + chunks: [`${key}/index`], + title: 'Violentmonkey', + scriptLoading: 'blocking', // we don't need `defer` and it breaks in some browsers, see #1632 + // For GroupAssetsPlugin, inject only `index.js` into `body` to avoid FOUC + injectTo: item => ((item.attributes.src || '').endsWith('/index.js') ? 'body' : 'head'), +}); + +const splitVendor = prefix => ({ + [prefix]: { + test: new RegExp(`node_modules[/\\\\]${prefix}`), + name: `public/lib/${prefix}`, + chunks: 'all', + priority: 100, + }, +}); + +function styleLoader(options) { + const { + extract, + loaders = [], + fallback = 'style-loader', + modules = false, + } = options || {}; + const cssLoader = { + loader: 'css-loader', + options: { + modules, + importLoaders: 1, + sourceMap: false, + }, + }; + return [ + extract ? MiniCssExtractPlugin.loader : fallback, + cssLoader, + ...loaders, + ]; +} + +function styleRule(options, rule) { + return { + test: /\.css$/, + use: styleLoader(options), + ...rule, + }; +} + +const styleOptions = { + extract: isProd, +}; +const postcssLoader = { + loader: 'postcss-loader', +}; + +const getBaseConfig = () => ({ + mode: isProd ? 'production' : 'development', + target: 'web', // required by live reloading + devtool: isProd ? false : 'inline-source-map', + output: { + path: resolve('dist'), + publicPath: '/', + filename: '[name].js', + hashFunction: 'xxhash64', + }, + node: { + global: false, + }, + performance: { + maxEntrypointSize: 1e6, + maxAssetSize: 0.5e6, + }, + resolve: { + alias, + extensions, + }, + module: { + rules: [ + // JS/TS + { + test: /\.m?[jt]sx?$/, + use: 'babel-loader', + exclude: file => /node_modules/.test(file) && !/vueleton|@vue[/\\]shared/.test(file), + }, + // CSS + { + oneOf: [ + // library CSS files: node_modules/**/*.css + styleRule(styleOptions, { + include: [nodeModules], + }), + // CSS modules: src/**/*.module.css + styleRule({ + ...styleOptions, + loaders: [postcssLoader], + modules: {}, + }, { + test: /\.module\.css$/, + }), + // normal CSS files: src/**/*.css + styleRule({ + ...styleOptions, + loaders: [postcssLoader], + }), + ], + }, + // SVG + { + test: /\.svg$/, + use: [{ + loader: 'svg-sprite-loader', + options: { + // extract: extractSVG, + }, + }], + include: [resolve('src/resources/svg')], + }, + // Vue + { + test: /\.vue$/, + loader: 'vue-loader', + options: { + babelParserPlugins: ['functionBind'], + compilerOptions: { + whitespace: 'condense', + }, + }, + }, + ], + }, + optimization: { + runtimeChunk: false, + splitChunks: { + cacheGroups: { + 'common-ui': { + name: 'common-ui', + test: new RegExp([ + /\bsvg/, + // don't extract CSS as it'll change the relative order of rules which breaks appearance + 'src/common/(?!zip|.*\\.css$)', + 'node_modules/@violentmonkey/shortcut', + 'node_modules/@?vue', + ].map(re => re.source || re).join('|').replace(/\\?\//g, '[/\\\\]')), + chunks: c => ![ + 'background/index', // only 4kB of common code + 'injected', + 'injected-web', + ].includes(c.name), + }, + ...splitVendor('codemirror'), + }, + }, + minimizer: isProd ? [ + new CssMinimizerPlugin(), + new TerserPlugin(MIN_OPTS_PUBLIC), + new TerserPlugin(MIN_OPTS_MAIN), + ] : [], + }, + plugins: [ + new VueLoaderPlugin(), + new GroupAssetsPlugin(), + ...styleOptions.extract ? [new MiniCssExtractPlugin({ + filename: '[name].css', + })] : [], + require('unplugin-icons/webpack')(), + ], +}); + +const getPageConfig = () => { + const config = getBaseConfig(); + config.entry = Object.fromEntries(pages.map(name => [`${name}/index`, `./src/${name}`])); + config.plugins = [ + ...config.plugins, + ...pages.filter(key => key !== 'background').map(createHtmlPage), + ]; + return config; +}; + +exports.isProd = isProd; +exports.getBaseConfig = getBaseConfig; +exports.getPageConfig = getPageConfig; diff --git a/scripts/webpack-group-assets-plugin.js b/scripts/webpack-group-assets-plugin.js new file mode 100644 index 0000000000..0ffbed4d55 --- /dev/null +++ b/scripts/webpack-group-assets-plugin.js @@ -0,0 +1,36 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +class GroupAssetsPlugin { + apply(compiler) { + compiler.hooks.compilation.tap('GroupAssetsPlugin', (compilation) => { + HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync( + 'GroupAssetsPlugin', (data, callback) => { + this.groupAssets(data); + // console.log('grouped', data); + callback(null, data); + } + ); + }); + } + + groupAssets(data) { + const { injectTo } = data.plugin.options; + if (typeof injectTo === 'function') { + const groups = { head: [], body: [] }; + [ + ['head', data.headTags], + ['body', data.bodyTags], + ].forEach(([defaultGroup, items]) => { + items.forEach(item => { + const groupName = injectTo(item, defaultGroup); + const group = groups[groupName] || groups[defaultGroup]; + group.push(item); + }); + }); + data.headTags = groups.head; + data.bodyTags = groups.body; + } + } +} + +module.exports = GroupAssetsPlugin; diff --git a/scripts/webpack-protect-bootstrap-plugin.js b/scripts/webpack-protect-bootstrap-plugin.js new file mode 100644 index 0000000000..751ff962d4 --- /dev/null +++ b/scripts/webpack-protect-bootstrap-plugin.js @@ -0,0 +1,109 @@ +const escapeStringRegexp = require('escape-string-regexp'); +const webpack = require('webpack'); + +const G = webpack.RuntimeGlobals; +const OBJ_RULE = [ + /([[(,=:]\s*{)(?!__proto__:)\s*(.)/g, + (_, str, next) => `${str}__proto__: null${next === '}' ? '' : ','}${next}`, +]; +const BOOTSTRAP_RULES = [ + OBJ_RULE, + [ + "typeof Symbol !== 'undefined' && Symbol.toStringTag", + 'true', + G.makeNamespaceObject, + ], [ + 'Symbol.toStringTag', + 'toStringTagSym', + G.makeNamespaceObject, + ], [ + 'Object.defineProperty(', + 'defineProperty(', + G.definePropertyGetters, + ], [ + `${G.hasOwnProperty}(definition, key) && !${G.hasOwnProperty}(exports, key)`, + '!(key in exports)', + G.definePropertyGetters, + ], [ + 'Object.prototype.hasOwnProperty.call(', + 'hasOwnProperty(', + G.hasOwnProperty, + ], +]; +const MAIN_RULES = [ + [ + /(__webpack_modules__\[moduleId])\.call\(/g, + 'safeCall($1, ', + false, + ], [ + new RegExp(`(?:${[ + '// webpackBootstrap\\s+(?:/\\*+/\\s*)?"use strict";\\s+', + `var (__webpack_module_cache__|${G.require}) = {};.*?`, + ].join('|')})var ${G.exports} =`, 's'), + patchBootstrap, + false, + ], [ + new RegExp(`(${[ + `${G.definePropertyGetters}\\(${G.exports}, {`, + `var ${G.exports} = {`, + `var __webpack_modules__ = \\({`, + ].join('|')})(?!__proto__:)\\s*(.)`, 'g'), + OBJ_RULE[1], + false, + ], +]; + +/** + * WARNING! The following globals must be correctly assigned using wrapper-webpack-plugin. + * toStringTagSym = Symbol.toStringTag + * defineProperty = Object.defineProperty + * hasOwnProperty = Object.prototype.hasOwnProperty + * safeCall = Function.prototype.call.bind(Function.prototype.call) + */ +class WebpackProtectBootstrapPlugin { + apply(compiler) { + const NAME = WebpackProtectBootstrapPlugin.name; + compiler.hooks.compilation.tap(NAME, (compilation) => { + const hooks = webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(compilation); + hooks.renderMain.tap(NAME, replace.bind(null, MAIN_RULES)); + }); + } +} + +function patchBootstrap(src, moduleCache) { + if (!moduleCache) return ' ' + src; // everything was concatenated + const props = src.match(new RegExp(`(?<=\\b${G.require}\\.)(\\w+)(?= = )`, 'g')); + const guard = props + ? `for (let i = 0, props="${[...new Set(props)].join('')}"; i < props.length; i++) + defineProperty(${G.require}, props[i], {__proto__: null, value: 0, writable: 1});\n` + : ''; + const res = replace(BOOTSTRAP_RULES, src, this); + // splicing the guard after the first line to handle `require = {}` + const i = res.indexOf('\n'); + return res.slice(0, i) + guard + res.slice(i); +} + +function replace(rules, src, info) { + src = src.source?.() || src; + let res = src; + for (const rule of rules) { + const [from, to, test = true] = rule; + const fromRe = typeof from === 'string' + ? new RegExp(escapeStringRegexp(from), 'g') + : from; + const dst = res.replace(fromRe, to.bind?.(info) || to); + const mandatory = test === true + || test.test?.(src) + || typeof test === 'string' && src.includes(test); + if (dst === res && mandatory) { + const err = `[${WebpackProtectBootstrapPlugin.name}] ` + + `"${from}" not found in ${info.chunk.name || 'bootstrap'}`; + console.warn(`${err}:\n${src.slice(0, 500)}`); // this prints immediately + throw new Error(err); // this prints at the end of build + } + res = dst; + } + return res; +} + +module.exports = WebpackProtectBootstrapPlugin; diff --git a/scripts/webpack-util.js b/scripts/webpack-util.js new file mode 100644 index 0000000000..2404eaf8ce --- /dev/null +++ b/scripts/webpack-util.js @@ -0,0 +1,101 @@ +const fs = require('fs'); +const babelCore = require('@babel/core'); +const webpack = require('webpack'); + +const entryGlobals = { + 'common': [], + 'injected/content': [], + 'injected/web': [], +}; +const entryPathToFilename = path => path === '*' + ? `./src/common/safe-globals-shared.js` + : `./src/${path}/safe-globals.js`; +Object.entries(entryGlobals).forEach(([name, val]) => { + const parts = name.split('/'); + if (parts[1]) parts[1] = name; + val.push('*', ...parts); +}); + +exports.restrictedSyntax = ( + // Hiding `code` so eslint doesn't complain about invalid schema + rules => rules.map(r => ( + Object.defineProperty(r, 'code', { enumerable: false, value: r.code }) + )) +)([{ + selector: 'ArrayPattern', + message: 'Destructuring via Symbol.iterator may be spoofed/broken in an unsafe environment', + code: '[window.foo]=[]', +}, { + selector: ':matches(ArrayExpression, CallExpression) > SpreadElement', + message: 'Spreading via Symbol.iterator may be spoofed/broken in an unsafe environment', + code: 'open([...[]])', +}, { + selector: '[callee.object.name="Object"], MemberExpression[object.name="Object"]', + message: 'Using potentially spoofed methods in an unsafe environment', + code: 'Object.assign()', + // TODO: auto-generate the rule using GLOBALS +}, { + selector: `CallExpression[callee.name="defineProperty"]:not(${[ + '[arguments.2.properties.0.key.name="__proto__"]', + ':has(CallExpression[callee.name="nullObjFrom"])' + ].join(',')})`, + message: 'Prototype of descriptor may be spoofed/broken in an unsafe environment', + code: 'defineProperty(open, "foo", {foo:1})', +}]); + +/** + * Adds a watcher for files in entryGlobals to properly recompile the project on changes. + */ +function addWrapperWithGlobals(name, config, defsObj, callback) { + config.module.rules.push({ + test: new RegExp(`/${name}/.*?\\.js$`.replace(/\//g, /[/\\]/.source)), + use: [{ + loader: './scripts/fake-dep-loader.js', + options: { files: entryGlobals[name].map(entryPathToFilename) }, + }], + }); + const defsRe = new RegExp(`\\b(${ + Object.keys(defsObj) + .join('|') + .replace(/\./g, '\\.') + })\\b`, 'g'); + const reader = () => ( + entryGlobals[name] + .map(path => readGlobalsFile(path)) + .join('\n') + .replace(defsRe, s => defsObj[s]) + ); + const { header, footer, test } = callback(reader); + config.plugins.push( + new webpack.BannerPlugin({ test, raw: true, banner: header }), + new webpack.BannerPlugin({ test, raw: true, banner: footer, footer: true }) + ); +} + +function getCodeMirrorThemes() { + const name = 'neo.css'; + return fs.readdirSync( + require.resolve(`codemirror/theme/${name}`).slice(0, -name.length), + { withFileTypes: true }, + ).map(e => e.isFile() && e.name.endsWith('.css') && e.name.slice(0, -4)) + .filter(Boolean); +} + +function readGlobalsFile(path, babelOpts = {}) { + const { ast, code = !ast } = babelOpts; + const filename = entryPathToFilename(path); + const src = fs.readFileSync(filename, { encoding: 'utf8' }) + .replace(/\bexport\s+(function\s+(\w+))/g, 'const $2 = $1') + .replace(/\bexport\s+(?=(const|let)\s)/g, ''); + const res = babelCore.transformSync(src, { + ...babelOpts, + ast, + code, + filename, + }); + return ast ? res : res.code; +} + +exports.addWrapperWithGlobals = addWrapperWithGlobals; +exports.getCodeMirrorThemes = getCodeMirrorThemes; +exports.readGlobalsFile = readGlobalsFile; diff --git a/scripts/webpack.conf.js b/scripts/webpack.conf.js index 44ccbf4b6a..b545258d2e 100644 --- a/scripts/webpack.conf.js +++ b/scripts/webpack.conf.js @@ -1,110 +1,110 @@ -const { modifyWebpackConfig, shallowMerge, defaultOptions } = require('@gera2ld/plaid'); -const { isProd } = require('@gera2ld/plaid/util'); const webpack = require('webpack'); -const WrapperWebpackPlugin = require('wrapper-webpack-plugin'); -const TerserPlugin = require('terser-webpack-plugin'); -const projectConfig = require('./plaid.conf'); -const mergedConfig = shallowMerge(defaultOptions, projectConfig); +const { ListBackgroundScriptsPlugin } = require('./manifest-helper'); +const { addWrapperWithGlobals, getCodeMirrorThemes } = require('./webpack-util'); +const ProtectWebpackBootstrapPlugin = require('./webpack-protect-bootstrap-plugin'); +const { getVersion } = require('./version-helper'); +const { configLoader } = require('./config-helper'); +const { getBaseConfig, getPageConfig, isProd } = require('./webpack-base'); -const INIT_FUNC_NAME = 'VMInitInjection'; -// Copied from gulpfile.js: strip alphabetic suffix -const VM_VER = require('../package.json').version.replace(/-[^.]*/, ''); +// Avoiding collisions with globals of a content-mode userscript +const INIT_FUNC_NAME = '**VMInitInjection**'; +const VAULT_ID = 'VAULT_ID'; +const PAGE_MODE_HANDSHAKE = 'PAGE_MODE_HANDSHAKE'; +const VM_VER = getVersion(); -const definitions = new webpack.DefinePlugin({ - 'process.env.INIT_FUNC_NAME': JSON.stringify(INIT_FUNC_NAME), - 'process.env.DEBUG': JSON.stringify(process.env.DEBUG || false), - 'process.env.VM_VER': JSON.stringify(VM_VER), -}); -const minimizerOptions = { - cache: true, - parallel: true, - sourceMap: true, - terserOptions: { - output: { - ascii_only: true, - }, - }, -}; -const minimizer = isProd && [ - new TerserPlugin({ - chunkFilter: ({ name }) => !name.startsWith('public/'), - ...minimizerOptions, - terserOptions: { - ...minimizerOptions.terserOptions, - compress: { - ecma: 6, - // 'safe' since we don't rely on function prototypes - unsafe_arrows: true, - }, - }, - }), - new TerserPlugin({ - chunkFilter: ({ name }) => name.startsWith('public/'), - ...minimizerOptions, - }), -]; +global.localStorage = {}; // workaround for node 25 and HtmlWebpackPlugin's `...global` + +configLoader + // Default values + .add({ + DEBUG: false, + }) + // Load from `./.env` + .envFile() + // Load from `process.env` + .env() + // Override values + .add({ + VM_VER, + }); -const modify = (extra, init) => modifyWebpackConfig( - (config) => { - config.plugins.push(definitions); - if (init) init(config); - return config; - }, { - projectConfig: { - ...mergedConfig, - ...extra, - optimization: { - ...mergedConfig.optimization, - ...(extra || {}).pages && { - runtimeChunk: false, - splitChunks: false, - }, - minimizer, - }, - }, - }, -); +const pickEnvs = (items) => { + return Object.assign({}, ...items.map(key => ({ + [`process.env.${key}`]: JSON.stringify(configLoader.get(key)), + }))); +}; +const defsObj = { + ...pickEnvs([ + 'DEBUG', + 'VM_VER', + 'SYNC_GOOGLE_DESKTOP_ID', + 'SYNC_GOOGLE_DESKTOP_SECRET', + 'SYNC_ONEDRIVE_CLIENT_ID', + 'SYNC_DROPBOX_CLIENT_ID', + ]), + 'process.env.INIT_FUNC_NAME': JSON.stringify(INIT_FUNC_NAME), + 'process.env.CODEMIRROR_THEMES': JSON.stringify(getCodeMirrorThemes()), + 'process.env.DEV': JSON.stringify(!isProd), + 'process.env.TEST': JSON.stringify(process.env.BABEL_ENV === 'test'), +}; // avoid running webpack bootstrap in a potentially hacked environment // after documentElement was replaced which triggered reinjection of content scripts -const skipReinjectionHeader = `if (window['${INIT_FUNC_NAME}'] !== 1)`; -const skipReinjectionConfig = (config, test) => config.plugins.push( - new WrapperWebpackPlugin({ - header: skipReinjectionHeader, - ...test && { test }, +const skipReinjectionHeader = `{ + const INIT_FUNC_NAME = '${INIT_FUNC_NAME}'; + if (window[INIT_FUNC_NAME] !== 1)`; + +const buildConfig = (page, entry, init) => { + const config = entry ? getBaseConfig() : getPageConfig(); + config.plugins.push(new webpack.DefinePlugin({ + ...defsObj, + // Conditional compilation to remove unsafe and unused stuff from `injected` + 'process.env.IS_INJECTED': JSON.stringify(/injected/.test(page) && page), })); + if (typeof entry === 'string') { + config.entry = { [page]: entry }; + } + if (!entry) init = page; + if (init) init(config); + return config; +}; -module.exports = Promise.all([ - modify(null, config => { - config.output.publicPath = '/'; - skipReinjectionConfig(config, /^browser\.js$/); +module.exports = [ + buildConfig((config) => { + addWrapperWithGlobals('common', config, defsObj, getGlobals => ({ + header: () => `{ ${getGlobals()}`, + footer: '}', + test: /^(?!injected|public).*\.js$/, + })); + config.plugins.push(new ListBackgroundScriptsPlugin({ + minify: false, // keeping readable + })); + (config.ignoreWarnings ??= []).push({ + // suppressing a false warning (the HTML spec allows it) as we don't need SSR + message: / cannot be child of /, + }); }), - modify({ - pages: { - injected: { - entry: './src/injected', - }, - }, - }, skipReinjectionConfig), - modify({ - pages: { - 'injected-web': { - entry: './src/injected/web', - }, - }, - }, (config) => { + + buildConfig('injected', './src/injected', (config) => { + config.plugins.push(new ProtectWebpackBootstrapPlugin()); + addWrapperWithGlobals('injected/content', config, defsObj, getGlobals => ({ + header: () => `${skipReinjectionHeader} { ${getGlobals()}`, + footer: '}}', + })); + }), + + buildConfig('injected-web', './src/injected/web', (config) => { config.output.libraryTarget = 'commonjs2'; - config.plugins.push( - new WrapperWebpackPlugin({ - header: `${skipReinjectionHeader} - window['${INIT_FUNC_NAME}'] = function () { - var module = { exports: {} }; - `, - footer: ` - var exports = module.exports; - return exports.__esModule ? exports['default'] : exports; - };0;`, - }), - ); + config.plugins.push(new ProtectWebpackBootstrapPlugin()); + addWrapperWithGlobals('injected/web', config, defsObj, getGlobals => ({ + header: () => `${skipReinjectionHeader} + window[INIT_FUNC_NAME] = function (IS_FIREFOX,${PAGE_MODE_HANDSHAKE},${VAULT_ID}) { + const module = { __proto__: null }; + ${getGlobals()}`, + footer: ` + const { exports } = module; + return exports.__esModule ? exports.default : exports; + }};0;`, + })); }), -]); +]; diff --git a/src/_locales/ar/messages.yml b/src/_locales/ar/messages.yml new file mode 100644 index 0000000000..af621d4ee4 --- /dev/null +++ b/src/_locales/ar/messages.yml @@ -0,0 +1,1086 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: تطبيق +buttonCancel: + description: Cancel button on dialog. + message: إلغاء +buttonCheckForUpdates: + description: Button to check a script for updates. + message: '' + touched: false +buttonClose: + description: Button to close window. + message: إغلاق +buttonConfirmInstallation: + description: Button to confirm installation of a script. + message: '' + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: '' + touched: false +buttonDisable: + description: Button to disable a script. + message: تعطيل +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false +buttonEdit: + description: Button to edit a script. + message: تعديل +buttonEditClickHint: + description: Tooltip for the Edit button in popup. + message: >- + يمكنك أيضًا النقر بزر الماوس الأيمن، أو Ctrl+نقر، أو نقر عجلة الماوس على اسم + السكربت. +buttonEmptyRecycleBin: + description: Button to empty the recycle bin. + message: أفرغ سلة المحذوفات الآن! +buttonEnable: + description: Button to enable a script. + message: تفعيل +buttonExportData: + description: Button to open the data export dialog. + message: تصدير إلى ملف مضغوط (zip) +buttonFilter: + description: Button to show filters menu. + message: '' + touched: false +buttonHome: + description: Button to open homepage. + message: الصفحة الرئيسية +buttonImportData: + description: Button to choose a file for data import. + message: استيراد من ملف مضغوط (zip) +buttonInstallFromURL: + description: Button to ask for URL of a user script. + message: تثبيت من عنوان URL +buttonInstallOptions: + description: Button to show options of installation confirm page. + message: '' + touched: false +buttonNew: + description: Button to create a new script. + message: جديد +buttonOK: + description: OK button on dialog. + message: موافق +buttonRecycleBin: + description: Button to list scripts in recycle bin. + message: سلة المحذوفات +buttonRemove: + description: Button to remove a script. + message: إزالة +buttonReplace: + description: Button to replace the current match. + message: استبدال +buttonReplaceAll: + description: Button to replace all matches. + message: الكل +buttonReset: + description: Button to reset to default values. + message: إعادة تعيين +buttonResetSettings: + description: Button in settings page to reset all settings + message: إعادة تعيين الإعدادات +buttonRestore: + description: Button to restore a removed script. + message: استعادة +buttonSave: + description: Button to save modifications of a script. + message: حفظ +buttonSaveClose: + description: Button to save modifications of a script and then close the editing page. + message: حفظ وإغلاق +buttonSaved: + description: Button text after saving. + message: تم الحفظ +buttonShowEditorState: + description: Button to show the list of currently used CodeMirror options. + message: عرض حالة المحرر +buttonSupport: + description: Button to open support page. + message: صفحة الدعم +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: سحب مرة واحدة إلى المحلي +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: اضغط مرة واحدة للتحكم عن بعد +buttonUndo: + description: Button to undo removement of a script. + message: تراجع +buttonUpdate: + description: Button to update a script. + message: تحديث +buttonUpdateAll: + description: Check all scripts for updates. + message: '' + touched: false +buttonVacuum: + description: Button to vacuum extension data. + message: تنظيف قاعدة البيانات +buttonVacuumed: + description: Message shown when data is vacuumed. + message: تم تنظيف البيانات +buttonVacuuming: + description: Message shown when data vacuum is in progress. + message: جارٍ تنظيف البيانات... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + التحديث التلقائي معطّل لهذا السكربت! + انقر "موافق" لتحديثه على أي حال. +confirmNotSaved: + description: Confirm message shown when there are unsaved script modifications. + message: |- + لم يتم حفظ التعديلات! + انقر "موافق" لتجاهلها أو "إلغاء" للبقاء. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + هل تريد التراجع عن جميع التغييرات التي أجريتها على قاعدة البيانات + (الاستيراد، التحديث، التعديل، التخصيص)؟ +descBlacklist: + description: Description for the global injection blacklist. + message: قائمة الحظر للحقن (لن يتم تشغيل السكربتات في المواقع المطابقة) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + قائمة الحظر الشبكية (لن تتصل السكربتات بالمواقع المطابقة ولا بملفات تعريف + الارتباط الخاصة بها) +descCustomCSS: + description: Description of custom CSS section. + message: >- + CSS مخصص لصفحة الإعدادات وصفحة تثبيت السكربتات. إذا لم تكن متأكدًا من الغرض + من هذا الحقل، فالرجاء عدم تعديله. +descEditorOptions: + description: Description of editor options JSON section. + message: >- + خيارات مخصصة لـ CodeMirror والإضافات الخاصة به بصيغة كائن JSON مثل + {"indentUnit":2, "smartIndent":true}، مع ملاحظة أن بعضها قد لا + يعمل في Violentmonkey. راجع القائمة الكاملة. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + يمكن تبديل الخيارات المنطقية (Boolean) بالنقر المزدوج على القيمة: + true = مفعّل، false = معطّل. الخيارات الرقمية التي + تنتهي بـ Delay أو Interval أو Rate أو + Time تكون بوحدة الملي ثانية. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + خيارات غير قياسية: autocompleteOnTyping هو تأخير بالمللي ثانية + لعرض اقتراحات الإكمال التلقائي بعد الكتابة (0 = تعطيل)، + killTrailingSpaceOnSave يزيل تلقائيًا المسافات الزائدة في نهاية + كل سطر عند الحفظ، وshowTrailingSpace يعرض المسافات الزائدة + كنقاط. +descScriptTemplate: + description: Description of script template section. + message: >- + المتغيرات المدعومة: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + بصيغة متوافقة مع MomentJS، على سبيل المثال <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: مجموعة +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: إخفاء +disabledScriptsSelector: + description: Label of the option. + message: 'السكربتات المعطّلة:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: عرض +editHelpDocumention: + description: Label in the editor help tab for the documentation link. + message: >- + الوثائق الخاصة بكتلة البيانات الوصفية للسكربتات وواجهة برمجة + GM: +editHelpKeyboard: + description: Label in the editor help tab for the keyboard shortcuts. + message: 'اختصارات لوحة المفاتيح:' +editHowToHint: + description: The text of the how-to link in the editor header. + message: هل ترغب في استخدام محرر آخر؟ +editLabelMeta: + description: Metadata section in settings tab of script editor. + message: بيانات وصفية مخصصة +editLabelSettings: + description: Settings section in settings tab of script editor. + message: إعدادات السكربت +editLongLine: + description: Shown in the editor in lines that were cut due to being too long + message: السطر طويل جدًّا +editLongLineTooltip: + description: Tooltip shown in the editor in lines that were cut due to being too long + message: |- + هذا السطر طويل جدًّا، لذا تم طي نصه لتجنب التأخير أثناء التعديل. + يمكنك ضبط الحد في الإعدادات المتقدمة، على سبيل المثال: + "maxDisplayLength": 20000 +editNavCode: + description: Label of code tab in script editor. + message: الكود +editNavSettings: + description: Label of settings tab in script editor. + message: الإعدادات +editNavValues: + description: Label of values tab in script editor. + message: القيم +editValueAll: + description: Button to show/edit the entire script value storage. + message: '' + touched: false +editValueAllHint: + description: Tooltip for the 'all' button. + message: عرض/تعديل كامل مساحة تخزين القيم الخاصة بالسكربت +extDescription: + description: Description for this extension, will be displayed in web store + message: مدير سكربتات مفتوح المصدر يدعم العديد من المتصفحات +extName: + description: Name of this extension. + message: Violentmonkey +failureReasonBlacklisted: + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: موجود في قائمة الحظر في إعدادات Violentmonkey +failureReasonNoninjectable: + description: >- + Shown for URLs that cannot be processed (same places as + failureBlacklistedUrl) + message: >- + لا يمكن لـ Violentmonkey تشغيل سكربتات المستخدم في هذه الصفحة + + (أمثلة شائعة: واجهة المتصفح، إضافة، محظور عبر سياسات، موقع محرك بحث في + Opera) +failureReasonRestarted: + description: Shown in the popup. + message: >- + تم إعادة تشغيل Violentmonkey. يُرجى إعادة تحميل علامة التبويب لتشغيل سكربتات + المستخدم. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + توجد عدة طرق لتثبيت ملف محلي أو تتبع تعديلاته: <1> اسحب الملف وأفلته في صفحة + Violentmonkey المفتوحة أو في النافذة المنبثقة، بما في ذلك هذه الصفحة. <2> قم + بتشغيل خادم HTTP محلي لهذا الملف وافتحه عبر http://localhost. <3> فعّل خيار + "السماح بالوصول إلى عناوين الملفات" في صفحة تفاصيل Violentmonkey في + chrome://extensions. هذا الخيار خطير لأن أي سكربت مستخدم قد يقرأ أي ملف + محلي. +filterAlphabeticalOrder: + description: Label for option to sort scripts in alphabetical order. + message: أبجدي +filterExecutionOrder: + description: Label for option to sort scripts in execution order. + message: ترتيب التنفيذ +filterLastUpdateOrder: + description: Label for option to sort scripts by last update time. + message: وقت آخر تحديث +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: آخر وقت للزيارة +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: آخر مرة قمت فيها بزيارة أي موقع مستهدف بواسطة هذا السكريبت. +filterScopeAll: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterScopeCode: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterScopeName: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: الحجم +genericError: + description: Label for generic error. + message: خطأ +genericOff: + description: To indicate something is turned off or disabled, similar to "no". + message: معطّل +genericOn: + description: To indicate something is turned on or enabled, similar to "yes". + message: مفعّل +genericUseGlobal: + description: To indicate some per-script option will use its analog in global settings. + message: استخدام الإعداد العام +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + أنماط "#" تعمل فقط عند فتح الموقع أول مرة أو عند إعادة تحميل علامة التبويب: + $1 +headerRecycleBin: + description: Text shown in the header when scripts in recycle bin are listed. + message: سلة المحذوفات +helpForLocalFile: + description: Label of the checkbox. + message: عرض التعليمات الخاصة بالسكربتات المحلية التي تم سحبها وإفلاتها +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: لـ $1 سكربتًا مطابقًا +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: |- + لا يمكن للنص البرمجي المعزول تغيير هذه الخصائص العامة: $1. + لإصلاح النص البرمجي إما أضف `@grant none` لتعطيل العزل، + أو استخدم `unsafeWindow` بدلاً من `window`. +hintInputURL: + description: Hint for a prompt box to input URL of a user script. + message: 'أدخل عنوان URL:' +hintRecycleBin: + description: Hint for recycle bin. + message: السكربتات المحذوفة مدرجة هنا وستُحتفظ بها لمدة 7 أيام. +hintUseDownloadURL: + description: Shown as a place holder for @updateURL when it is not assigned + message: استخدم @downloadURL +hintVacuum: + description: Hint for vacuuming data. + message: تجاهل الزوائد ومحاولة إعادة تحميل الموارد المفقودة من الذاكرة المؤقتة. +install: + description: Label for button to install a script. + message: تثبيت +installFrom: + description: Label for button to install script from a userscript site. + message: تثبيت من $1 +installOptionClose: + description: Option to close confirm window after installation. + message: '' + touched: false +installOptionTrackTooltip: + description: >- + Tooltip in Firefox 68+ for the option to track the loading local file before + window is closed. + message: يجب ترك علامة تبويب الملف المصدر مفتوحة في Firefox 68 والإصدارات الأحدث. +labelAbout: + description: Label shown on top of the about page. + message: '' + touched: false +labelAdvanced: + description: Label for button to show advanced settings. + message: متقدم +labelAllowUpdate: + description: Option to allow checking updates for a script. + message: السماح بالتحديث +labelAuthor: + description: Label of author shown in the details of a script. + message: 'المؤلف: ' +labelAutoReloadCurrentTab: + description: Option to reload current tab after a script is switched on or off from menu. + message: إعادة تحميل علامة التبويب الحالية بعد تفعيل/تعطيل سكربت من القائمة +labelAutoReloadCurrentTabDisabled: + description: >- + Tooltip in menu after Violentmonkey is switched off if auto-reload is + disabled. + message: '' + touched: false +labelAutoUpdate: + description: Option to allow automatically checking scripts for updates + message: التحقق من تحديثات السكربتات كل $1 يوم/أيام، استخدم 0 لتعطيل الميزة +labelBackup: + description: Label of the import/export section in settings. + message: '' + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: النسخ الاحتياطي والصيانة +labelBadge: + description: Label for option to show number on badge. + message: 'الشارة:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'ألوان الشارة: ' +labelBadgeNone: + description: Option to display nothing on badge. + message: بدون +labelBadgeTotal: + description: Option to display total number of running scripts on badge. + message: عدد السكربتات قيد التشغيل +labelBadgeUnique: + description: Option to display number of unique running scripts on badge. + message: عدد السكربتات الفريدة قيد التشغيل +labelBlacklist: + description: Label for global blacklist settings in security section. + message: قائمة الحظر +labelContributors: + description: Label for link to contributors. + message: المساهمون +labelCurrentLang: + description: Label of current language. + message: 'اللغة الحالية: ' +labelCustomCSS: + description: Label for custom CSS section. + message: نمط مخصص +labelDataExport: + description: Section title of data export. + message: '' + touched: false +labelDataImport: + description: Section title of data import. + message: '' + touched: false +labelDonate: + description: Label of link to donate page. + message: '' + touched: false +labelDownloadURL: + description: Label of script @downloadURL in custom meta data. + message: 'عنوان التنزيل:' +labelEditValue: + description: Label shown in the panel to edit a script value. + message: تعديل قيمة السكربت +labelEditValueAll: + description: Label shown in the panel to edit the entire script value storage. + message: تعديل مساحة تخزين السكربت +labelEditor: + description: Label for Editor settings + message: المحرر +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: السكربتات المفعّلة فقط +labelExclude: + description: Label of @exclude rules. + message: قواعد @exclude +labelExcludeMatch: + description: Label of @exclude-match rules. + message: قواعد @exclude-match +labelExportScriptData: + description: Option to export script data along with scripts. + message: تصدير بيانات السكربت +labelExposeStatus: + description: Option in advanced settings. + message: 'إظهار الإصدار المثبت على مواقع كتالوج سكربتات المستخدم: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: وضع $1 بديل في Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + طريقة حقن بديلة لسكربتات بدءًا من Firefox 59، أسرع من الوضع + الافتراضي. مثل "وضع الصفحة المتزامن"، فإنه يزيد أيضًا من استهلاك الذاكرة، + لذا قد ترغب في تعطيله إذا كانت سكربتاتك تعمل بشكل صحيح دونه. +labelFeedback: + description: Label of link to feedback page. + message: ملاحظات +labelGeneral: + description: Label for general settings. + message: عام +labelHelpTranslate: + description: Label for link to localization guide in about tab + message: المساعدة في الترجمة +labelHomepage: + description: Label for home page in about tab. + message: الصفحة الرئيسية +labelHomepageURL: + description: Label of script @homepageURL in custom meta data. + message: 'عنوان الصفحة الرئيسية:' +labelIconURL: + description: Label for the input. + message: 'عنوان الأيقونة:' +labelImportScriptData: + description: Option to import script data along with scripts. + message: استيراد بيانات السكربت +labelImportSettings: + description: Label for option to import settings from zip file. + message: استيراد الإعدادات +labelInclude: + description: Label of @include rules. + message: قواعد @include +labelInjectionMode: + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'وضع الحقن: ' +labelInstall: + description: Shown in the title of the confirm page while trying to install a script. + message: جارٍ تثبيت السكربت +labelKeepOriginal: + description: Option to keep the original match or ignore rules. + message: الاحتفاظ بالأصل +labelLastUpdatedAt: + description: Label shown on last updated time. + message: آخر تحديث في $1 +labelLineNumber: + description: Label for line number jumper. + message: 'رقم السطر: ' +labelMatch: + description: Label of @match rules. + message: قواعد @match +labelName: + description: Label of script name. + message: 'الاسم:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'التشغيل في الإطارات: ' +labelNoName: + description: Text as the name of a script when no @name is assigned. + message: بدون اسم +labelNoSearchScripts: + description: Message shown when no script is found in search results. + message: لم يتم العثور على أي سكربت. +labelNotifyThisUpdated: + description: >- + A per-script option in editor to enable notification when this script is + updated. The text follows "Allow update" checkbox option so it's like a + continuation of the phrase. + message: '، ثم إشعار:' +labelNotifyUpdates: + description: Option to show notification when script is updated. + message: إشعار بتحديثات السكربتات +labelNotifyUpdatesGlobal: + description: Option to prioritize global notification option over script's setting. + message: تجاهل إشعارات كل سكربت على حدة (من علامة التبويب "الإعدادات" في المحرر) +labelPrivacyPolicy: + description: Label of link to privacy policy + message: سياسة الخصوصية +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: إعادة تثبيت السكربت +labelRelated: + description: Label of related links. + message: 'روابط ذات صلة: ' +labelRemovedAt: + description: Label for the time when the script is removed. + message: تمت إزالته في $1 +labelReplace: + description: Label for replace input in search box. + message: 'استبدال بـ: ' +labelRunAt: + description: Label of script @run-at properties in custom meta data. + message: 'Run-At: ' +labelRunAtDefault: + description: Shown when custom @run-at is not assigned. + message: (افتراضي) +labelScriptTemplate: + description: Label for custom script template. + message: قالب سكربت مخصص +labelSearch: + description: Label for search input in search box. + message: 'البحث عن: ' +labelSearchScript: + description: Placeholder for script search box. + message: البحث في السكربتات... +labelSettings: + description: Label shown on the top of settings page + message: الإعدادات +labelShowOrder: + description: Label for option in dashboard -> script list + message: عرض مواضع ترتيب التنفيذ +labelShowVisited: + description: Label for option in dashboard -> script list + message: عرض وقت آخر زيارة +labelSync: + description: Label for sync options. + message: المزامنة +labelSyncAnonymous: + description: Label for using anonymous account. + message: استخدام حساب مجهول +labelSyncAuthorize: + description: Label for button to authorize a service. + message: تفويض +labelSyncAuthorizing: + description: Label for button when authorization is in progress. + message: جارٍ التفويض +labelSyncAutomatically: + description: Label for option to sync automatically. + message: المزامنة تلقائياً +labelSyncDisabled: + description: Label for option to disable sync service. + message: بدون +labelSyncPassword: + description: Label for input to hold password. + message: 'كلمة المرور: ' +labelSyncReauthorize: + description: Option to reauthorize sync service when expired. + message: '' + touched: false +labelSyncRevoke: + description: Label for button to revoke authorization for a service. + message: إلغاء التفويض +labelSyncScriptStatus: + description: Label for option to sync script status. + message: مزامنة حالة السكربت +labelSyncServerUrl: + description: Label for input to hold server URL. + message: 'عنوان الخادم: ' +labelSyncService: + description: Label for sync service select. + message: المزامنة مع +labelSyncUsername: + description: Label for input to hold username. + message: 'اسم المستخدم: ' +labelTags: + description: Label for custom tags. + message: 'الوسوم (مفصولة بمسافات):' +labelTheme: + description: Label for the visual theme option. + message: 'السمة: ' +labelTranslator: + description: Label of translator. + message: '' + touched: false +labelUpdateURL: + description: Label of script @updateURL in custom meta data. + message: 'عنوان التحديث:' +labelViewSingleColumn: + description: >- + Label for option in dashboard script list to show the scripts in single + column. + message: عمود واحد +labelViewTable: + description: Label for option in dashboard script list to show the scripts as a table. + message: عرض جدولي +labelWidth: + description: Width. + message: 'العرض:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: وضع $1 متزامن +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + فعّله فقط إذا كان لديك سكربت يحتاج للتشغيل قبل بدء تحميل الصفحة، وهو حاليًا + يُنفَّذ متأخرًا جدًّا. مثل وضع الحقن الفوري في Tampermonkey، يستخدم هذا + الخيار XHR المتزامن المُهمَل، لذا ستظهر تحذيرات في وحدة تحكم أدوات المطور في + Chrome/Chromium، لكن يمكنك تجاهلها بأمان لأن تأثيراتها السلبية ضئيلة في هذه + الحالة. يمكنك إخفاء التحذيرات نهائيًا بالنقر بزر الماوس الأيمن على أحدها. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: >- + (باستثناء وضع التصفح المتخفي والمواقع التي تم تعطيل ملفات تعريف الارتباط + فيها) +lastSync: + description: Label for last sync timestamp. + message: آخر مزامنة في $1 +learnBlacklist: + description: Refers to a link to introduce blacklist patterns. + message: معرفة المزيد عن أنماط قائمة الحظر. +learnInjectionMode: + description: Refers to a link to introduce injection modes. + message: معرفة المزيد عن أنماط الحقن. +menuCommands: + description: Menu item to list script commands. + message: '' + touched: false +menuDashboard: + description: Label for menu item to open dashboard. + message: فتح لوحة التحكم +menuExclude: + description: >- + Shown in popup menu after clicking script's "..." dropdown so try to keep + the label short. + message: استبعاد... +menuExcludeHint: + description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. + message: >- + ستُعاد تلقائيًا تحميل علامة التبويب الحالية إذا كنت قد فعّلت هذا الخيار في + الإعدادات العامة. + + لتطبيق التغييرات على باقي العلامات، يُرجى إعادة تحميلها يدويًا. + + استخدم علامة التبويب "الإعدادات" في المحرر لمزيد من المرونة. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: ملاحظات +menuFindScripts: + description: Menu item to find scripts for a site. + message: البحث عن سكربتات لهذا الموقع +menuInjectionFailed: + description: Injection error. + message: تعذّر حقن بعض السكربتات. +menuInjectionFailedFix: + description: Injection error fix, shown in case the default mode is "page". + message: إعادة المحاولة في الوضع "التلقائي" +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: السكربتات المطابقة والمعطّلة +menuMatchedFrameScripts: + description: Label for menu listing matching scripts in sub-frames. + message: سكربتات الإطارات الفرعية فقط +menuMatchedScripts: + description: Label for menu listing matched scripts. + message: السكربتات المطابقة +menuNewScript: + description: Menu item to create a new script. + message: إنشاء سكربت جديد +menuScriptDisabled: + description: Menu item showing the status of Violentmonkey, when disabled. + message: السكربتات معطّلة +menuScriptEnabled: + description: Menu item showing the status of Violentmonkey, when enabled. + message: السكربتات مفعّلة +msgCheckingForUpdate: + description: Message shown when a script is being checked for updates by version numbers. + message: جارٍ التحقق من وجود تحديثات... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + انقر لفتح وثائق MomentJS. الرموز المسموحة: $1. استخدم [الأقواس المربعة] + لحماية النص الحرفي. +msgErrorFetchingResource: + description: >- + Message shown when Violentmonkey fails fetching a resource/require/icon of + the script. + message: خطأ في جلب المورد! +msgErrorFetchingScript: + description: Message shown when Violentmonkey fails fetching a new version of the script. + message: خطأ في جلب السكربت! +msgErrorFetchingUpdateInfo: + description: Message shown when Violentmonkey fails fetching version data of the script. + message: فشل في جلب معلومات التحديث. +msgErrorLoadingData: + description: >- + Message shown on confirm page when the script to be installed cannot be + loaded. + message: خطأ في تحميل بيانات السكربت. +msgErrorLoadingDependency: + description: Message shown when not all requirements are loaded successfully. + message: خطأ في تحميل التبعيات. +msgImported: + description: >- + Message shown after import. There is an argument referring to the count of + scripts imported. + message: $1 عنصر/عناصر تم استيرادها. +msgIncognitoChanges: + description: >- + Message shown in popup and installation tab when opened in an incognito + window. + message: >- + التعديلات التي تقوم بها في وضع التصفح المتخفي تنطبق أيضًا على ملف التعريف + الرئيسي. +msgInstalled: + description: Message shown when a script is installed. + message: تم تثبيت السكربت. +msgInvalidScript: + description: Message shown when script is invalid. + message: سكربت غير صالح! +msgLoadedData: + description: >- + Message shown in the confirm page when a javascript file to be installed is + loaded. + message: '' + touched: false +msgLoading: + description: Message shown in the options page before script list is loaded. + message: '' + touched: false +msgLoadingData: + description: Message shown on confirm page when the script to be installed is loading. + message: جارٍ تحميل بيانات السكربت... +msgLoadingDependency: + description: Message shown on confirm page when the requirements are being downloaded. + message: جارٍ تحميل التبعيات... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: الموارد المطلوبة مفقودة. +msgNamespaceConflict: + description: >- + Message shown when namespace of the new script conflicts with an existent + one. + message: >- + سكربت مشابه مثبّت بالفعل. + + يرجى استخدام @name و@namespace مختلفين هنا، أو تعديل ذلك السكربت بدلًا من + ذلك. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: سكربت بنفس @name و@namespace مثبّت بالفعل. +msgNewVersion: + description: >- + Message shown when a new version of script is found by @updateURL, but no + @downloadURL is provided. + message: تم العثور على إصدار جديد. +msgNoUpdate: + description: Message shown when there is no new version of a script. + message: لم يتم العثور على تحديث. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: خطأ في تحديث السكربتات. انقر لفتحها. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'يرجى إعادة الحفظ أو إعادة تثبيت هذه السكربتات:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: ' (الكود نفسه)' +msgSavedBlacklist: + description: Message shown when blacklist are saved. + message: '' + touched: false +msgSavedCustomCSS: + description: Message shown when custom CSS is saved. + message: '' + touched: false +msgSavedEditorOptions: + description: Message shown when editor options are saved. + message: '' + touched: false +msgSavedScriptTemplate: + description: Message shown when custom script template is saved. + message: '' + touched: false +msgScriptUpdated: + description: Notification message for script updates. + message: تم تحديث السكربت [$1]! +msgShowHide: + description: Tooltip or text shown on a toggle that shows/hides stuff + message: عرض/إخفاء +msgSyncError: + description: Message shown when sync failed. + message: خطأ في المزامنة! +msgSyncInit: + description: Message shown when sync service is initializing. + message: جارٍ التهيئة... +msgSyncInitError: + description: Message shown when sync fails in initialization. + message: خطأ في التهيئة! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: لم يتم التفويض بعد. +msgSyncReady: + description: Message shown when sync will start soon. + message: ستبدأ المزامنة قريبًا... + touched: false +msgSyncing: + description: Message shown when sync is in progress. + message: المزامنة قيد التنفيذ... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: هل هناك خطأ نحوي؟ +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + السكربت لم يكن موجودًا أو لم يتطابق مع عنوان URL عند تحميل الصفحة، وهو ما + يحدث في مواقع تطبيقات الصفحات الواحدة (SPA) مثل Facebook أو Instagram التي + تستخدم التنقّل الوهمي. يمكنك إعادة تحميل علامة التبويب لتشغيل السكربت. + ولإصلاح المشكلة، استخدم @match للموقع بأكمله ثم اكتشف التغييرات باستخدام + MutationObserver أو واجهة برمجة window.navigation. +msgUpdated: + description: Message shown when a script is updated/reinstalled. + message: تم تحديث السكربت. +msgUpdating: + description: Message shown when a new version of script is being fetched. + message: جارٍ التحديث... +noValues: + description: Label shown when there is no value for current script. + message: لم يتم تخزين أي قيمة +optionEditorWindow: + description: Label for the option in settings + message: فتح المحرر من النافذة المنبثقة في نافذة جديدة +optionEditorWindowHint: + description: >- + Tooltip for optionEditorWindow in case the browser doesn't support + onBoundsChanged + message: سيتم حفظ موضع نافذة المحرر فقط عند تغيير حجمها أو حفظها +optionEditorWindowSimple: + description: Label for the editor window type + message: إخفاء شريط العناوين +optionPopup: + description: Label of the popup menu section in settings. + message: القائمة المنبثقة والأيقونة +optionPopupEnabledFirst: + description: Option to show enabled scripts first in popup. + message: المفعّلة أولًا +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: '' + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: تجميع حسب مرحلة @run-at +optionPopupHideDisabled: + description: Option to hide disabled scripts in popup. + message: '' + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: '' + touched: false +optionShowEnabledFirst: + description: Option to show enabled scripts first in alphabetical order. + message: عرض السكربتات المفعّلة أولًا +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'سمة واجهة المستخدم: ' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: تلقائي +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: داكن +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: فاتح +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: تحديث +popupSettings: + description: Item inside the popup's "⋮" menu + message: إعدادات النافذة المنبثقة +popupSettingsHint: + description: Hint for the config button in the popup + message: 'النقر بزر الماوس الأيمن: إعدادات النافذة المنبثقة' +readonly: + description: Text in the editor's header for non-editable scripts. + message: للقراءة فقط +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + السكربت الذي يتم تحديثه تلقائيًا للقراءة فقط ما لم تعطّل التحديثات أو تسمح + بالتعديل. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: السماح بالتعديل +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (سيتم استبدال التعديلات في التحديث القادم) +reinstall: + description: Button to reinstall a script + message: إعادة التثبيت +reloadTab: + description: Label of action to reload the tab + message: إعادة تحميل علامة التبويب +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + إذا تم تفعيله، فسيتم إعادة تحميل علامة التبويب النشطة عند اكتشاف تغييرات + وتطابق هذا السكربت مع عنوان URL الخاص بها. +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false +searchCaseSensitive: + description: Option to perform a case-sensitive search + message: حساس لحالة الأحرف +searchUseRegex: + description: Option to perform a regular expression search + message: استخدام التعبيرات النمطية (Regex) +sideMenuAbout: + description: 'Side menu: About' + message: حول +sideMenuInstalled: + description: 'Side menu: Installed scripts' + message: السكربتات المثبّتة +sideMenuSettings: + description: 'Side menu: Settings' + message: الإعدادات +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: إعادة تحميل الصفحة بدون سكربتات المستخدم +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + لقد عطّلت سكربتات المستخدم لهذه الصفحة. لتشغيلها، أعد تحميل الصفحة أو انتقل + إلى علامة تبويب أخرى. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'ترتيب الفرز:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: إيقاف التتبع +titleBadgeColor: + description: Tooltip for option to set badge color. + message: لون الشارة العادي +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: لون الشارة عندما يكون الموقع غير قابل للحقن (في قائمة الحظر أو غير مدعوم) +titleSearchHint: + description: Hover title for search icon in dashboard. + message: '' + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * مفتاح Enter يضيف النص إلى سجل الإكمال التلقائي + + * جميع الشروط غير حساسة لحالة الأحرف + + * يمكن دمج الشروط المفصولة بمسافات + + * البحث حسب البيانات الوصفية: "Awesome Script" "Description" + + * البحث حسب الوسوم: #tag1 #tag2 + + * البحث حسب اسم السكربت: name:"awesome name" + + * البحث حسب كود السكربت: code:"awesome code" + + * البحث السلبي: !#tag2 !name:"unwanted" + + * التعبيرات النمطية: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? with space" +toggleInjection: + description: Label for a keyboard shortcut. + message: تبديل حقن سكربتات المستخدم +trackEdits: + description: Button in a script installation dialog. + message: تتبع التعديلات الخارجية +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: ابقَ هذه الصفحة مفتوحة لتتبع تعديلاتك على الملف المحلي +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: تحديث السكربتات ($1) +updateScript: + description: Button to update one script. + message: تحديث +updateScriptsAll: + description: Command/button to update all scripts. + message: تحديث جميع السكربتات +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: تحديث سكربتات علامة التبويب الحالية +valueLabelKey: + description: Label for key of a script value. + message: المفتاح (نص) +valueLabelValue: + description: Label for value of a script value. + message: القيمة (مُسلسلة بصيغة JSON) +valueLabelValueAll: + description: Label for input of entire script value storage. + message: جميع القيم (مُسلسلة بصيغة JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' +visitWebsite: + description: Label for link to open Violentmonkey website. + message: '' + touched: false diff --git a/src/_locales/cs/messages.yml b/src/_locales/cs/messages.yml index 6628a9b61c..0dbc1c0158 100644 --- a/src/_locales/cs/messages.yml +++ b/src/_locales/cs/messages.yml @@ -1,15 +1,31 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: '' buttonCancel: description: Cancel button on dialog. message: Cancel +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Zkontrolovat aktualizace + touched: false buttonClose: description: Button to close window. message: Zavřít buttonConfirmInstallation: description: Button to confirm installation of a script. message: Potvrdit instalaci + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: '' + touched: false buttonDisable: description: Button to disable a script. message: Vypnout +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false buttonEdit: description: Button to edit a script. message: Upravit @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: '' +buttonResetSettings: + description: Button in settings page to reset all settings + message: '' buttonRestore: description: Button to restore a removed script. message: '' @@ -72,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Uložit a zavřít +buttonSaved: + description: Button text after saving. + message: '' buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: '' buttonSupport: description: Button to open support page. message: Support page +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. - message: '' - touched: false + message: Undo buttonUpdate: - description: Check a script for updates. - message: Zkontrolovat aktualizace + description: Button to update a script. + message: Update buttonUpdateAll: description: Check all scripts for updates. message: Zkontrolovat aktualizace + touched: false buttonVacuum: description: Button to vacuum extension data. message: Vyprázdnit data @@ -97,14 +125,25 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Čištení dat... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: '' confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: >- Úpravy nebyly uloženy! Klikni na OK pro návrat a ztrátu změn, nebo na zrušit pro setrvání na této stránce. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: '' descBlacklist: - description: HTML Description for the global blacklist. + description: Description for the global injection blacklist. message: URL matched in this list will not be injected by scripts. +descBlacklistNet: + description: Description for the global network blacklist. + message: '' descCustomCSS: description: Description of custom CSS section. message: >- @@ -113,6 +152,27 @@ descCustomCSS: descEditorOptions: description: Description of editor options JSON section. message: '' +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: '' +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: '' +descScriptTemplate: + description: Description of script template section. + message: '' +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: '' +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: '' +disabledScriptsSelector: + description: Label of the option. + message: '' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: '' editHelpDocumention: description: Label in the editor help tab for the documentation link. message: '' @@ -146,29 +206,30 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: '' + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: '' -editValueSave: - description: Button to save modification of a script value. - message: '' extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: '' extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: '' failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) message: '' +failureReasonRestarted: + description: Shown in the popup. + message: '' +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: '' filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. message: alphabetical order @@ -178,30 +239,58 @@ filterExecutionOrder: filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: '' +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: '' + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: '' + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: '' + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: '' genericError: description: Label for generic error. message: '' genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: '' genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: '' genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: '' +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: '' headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: '' +helpForLocalFile: + description: Label of the checkbox. + message: '' +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: '' +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Adresa skriptu:' @@ -214,15 +303,16 @@ hintUseDownloadURL: hintVacuum: description: Hint for vacuuming data. message: Zbavit se nadbytečného a zkusit znovu načíst chybějící zdroje v mezipaměti. +install: + description: Label for button to install a script. + message: '' installFrom: description: Label for button to install script from a userscript site. message: Install from $1 installOptionClose: description: Option to close confirm window after installation. message: Zavřít po instalaci -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Sledovat místní soubor před zavřením tohoto okna + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -234,7 +324,7 @@ labelAbout: touched: false labelAdvanced: description: Label for button to show advanced settings. - message: '' + message: Pokročilý labelAllowUpdate: description: Option to allow checking updates for a script. message: Povolit aktualizace @@ -249,12 +339,23 @@ labelAutoReloadCurrentTabDisabled: Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: '' + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates message: '' +labelBackup: + description: Label of the import/export section in settings. + message: '' + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: '' labelBadge: description: Label for option to show number on badge. message: '' +labelBadgeColors: + description: Label for option group to set badge colors. + message: '' labelBadgeNone: description: Option to display nothing on badge. message: '' @@ -279,12 +380,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Záloha dat + touched: false labelDataImport: description: Section title of data import. message: Obnova dat + touched: false labelDonate: description: Label of link to donate page. message: Přispět + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'Stažení (URL):' @@ -297,6 +401,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: '' +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: '' labelExclude: description: Label of @exclude rules. message: Výjimky @@ -309,12 +416,15 @@ labelExportScriptData: labelExposeStatus: description: Option in advanced settings. message: '' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: '' +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: '' labelFeedback: description: Label of link to feedback page. message: Zpětná vazba -labelFilterSort: - description: Label for sort filter. - message: Sort by $1 labelGeneral: description: Label for general settings. message: General @@ -323,10 +433,13 @@ labelHelpTranslate: message: '' labelHomepage: description: Label for home page in about tab. - message: '' + message: Homepage labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'Domovská stránka (URL):' +labelIconURL: + description: Label for the input. + message: '' labelImportScriptData: description: Option to import script data along with scripts. message: '' @@ -337,7 +450,9 @@ labelInclude: description: Label of @include rules. message: Zahrnout labelInjectionMode: - description: Label for default option to inject scripts. + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. message: '' labelInstall: description: Shown in the title of the confirm page while trying to install a script. @@ -357,10 +472,12 @@ labelMatch: labelName: description: Label of script name. message: 'Jméno:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: '' labelNoName: description: Text as the name of a script when no @name is assigned. message: Jméno skriptu nebylo určeno. - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: No script is found. @@ -376,12 +493,14 @@ labelNotifyUpdates: labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: '' -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: '' labelPrivacyPolicy: description: Label of link to privacy policy message: '' +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: '' labelRelated: description: Label of related links. message: 'Související odkazy: ' @@ -409,6 +528,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Nastavení +labelShowOrder: + description: Label for option in dashboard -> script list + message: '' +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Sync @@ -421,6 +546,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Authorizing +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: None @@ -446,6 +574,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: '' +labelTags: + description: Label for custom tags. + message: '' +labelTheme: + description: Label for the visual theme option. + message: '' labelTranslator: description: Label of translator. message: 'Překladatel: ' @@ -461,6 +595,20 @@ labelViewSingleColumn: labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. message: '' +labelWidth: + description: Width. + message: '' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: '' +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: '' +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: '' lastSync: description: Label for last sync timestamp. message: Last sync at $1 @@ -485,6 +633,11 @@ menuExclude: menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. message: '' +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Zpětná vazba menuFindScripts: description: Menu item to find scripts for a site. message: Najdi skripty pro tuto stránku @@ -492,7 +645,12 @@ menuInjectionFailed: description: Injection error. message: '' menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". + message: '' +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. message: '' menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. @@ -504,14 +662,17 @@ menuNewScript: description: Menu item to create a new script. message: '' menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Scripts disabled menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Skripty zapnuty msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Kontroluji aktualizace... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: '' msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -563,11 +724,21 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Vkládání požadavků... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: '' msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. message: Script namespace conflicts! Please modify @name and @namespace. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: '' msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -576,21 +747,36 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Aktualizace nenalezena. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: '' +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: '' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: '' msgSavedBlacklist: description: Message shown when blacklist are saved. message: Blacklist updated. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Custom style is updated. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: '' + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: '' + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Script [$1] is updated!' + message: Script [$1] is updated! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: '' @@ -603,12 +789,22 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Initializing error! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: '' msgSyncReady: description: Message shown when sync will start soon. message: Sync will start soon... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Sync in progress... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: '' +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: '' msgUpdated: description: Message shown when a script is updated/reinstalled. message: Skript aktualizován. @@ -629,15 +825,82 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: '' +optionPopup: + description: Label of the popup menu section in settings. + message: '' optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: '' +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: '' + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: '' optionPopupHideDisabled: description: Option to hide disabled scripts in popup. message: '' + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: '' + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Show enabled scripts first +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: '' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: '' +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Update +popupSettings: + description: Item inside the popup's "⋮" menu + message: '' +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: '' +readonlyNote: + description: Warning when trying to type in the editor. + message: '' +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: '' +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: '' +reinstall: + description: Button to reinstall a script + message: '' +reloadTab: + description: Label of action to reload the tab + message: '' +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: '' +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: '' @@ -653,12 +916,56 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Nastavení -titleScriptUpdated: - description: Notification title for script updates. - message: Update +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: '' +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: '' +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: '' +titleBadgeColor: + description: Tooltip for option to set badge color. + message: '' +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: '' titleSearchHint: description: Hover title for search icon in dashboard. message: '' + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: '' +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: '' +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: '' +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: '' +updateScript: + description: Button to update one script. + message: Update +updateScriptsAll: + description: Command/button to update all scripts. + message: '' +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: '' valueLabelKey: description: Label for key of a script value. message: '' @@ -668,6 +975,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: '' +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: '' + touched: false diff --git a/src/_locales/de/messages.yml b/src/_locales/de/messages.yml index 76e785b37e..5e08578737 100644 --- a/src/_locales/de/messages.yml +++ b/src/_locales/de/messages.yml @@ -1,21 +1,37 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Übernehmen buttonCancel: description: Cancel button on dialog. message: Abbrechen +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Auf Aktualisierungen prüfen + touched: false buttonClose: description: Button to close window. message: Schließen buttonConfirmInstallation: description: Button to confirm installation of a script. message: Installation bestätigen + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Re-Installation bestätigen + touched: false buttonDisable: description: Button to disable a script. message: Deaktivieren +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: Themes herunterladen + touched: false buttonEdit: description: Button to edit a script. message: Bearbeiten buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: 'Auch Rechts-, Strg- und Mausrad-Klicks auf den Skriptnamen sind möglich.' + message: Auch Rechts-, Strg- und Mausrad-Klicks auf den Skriptnamen sind möglich. buttonEmptyRecycleBin: description: Button to empty the recycle bin. message: Papierkorb jetzt leeren! @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Zurücksetzen +buttonResetSettings: + description: Button in settings page to reset all settings + message: Einstellungen zurücksetzen buttonRestore: description: Button to restore a removed script. message: Wiederherstellen @@ -72,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Speichern & Schließen +buttonSaved: + description: Button text after saving. + message: Gespeichert buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Editorstatus anzeigen buttonSupport: description: Button to open support page. message: Support-Seite +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: Einmalig auf lokale Ebene ziehen +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: Einmalig auf Remote-Ebene schieben buttonUndo: description: Button to undo removement of a script. - message: Rückgängig machen - touched: false + message: Rückgängig buttonUpdate: - description: Check a script for updates. - message: Auf Aktualisierungen prüfen + description: Button to update a script. + message: Aktualisierung buttonUpdateAll: description: Check all scripts for updates. message: Alle auf Aktualisierungen prüfen + touched: false buttonVacuum: description: Button to vacuum extension data. message: Datenbank säubern @@ -97,14 +125,31 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Säubere Daten... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Automatische Aktualisierung ist für dieses Skript deaktiviert! + OK klicken, um es dennoch zu aktualisieren. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Die Änderungen sind nicht gespeichert! Zum Verwerfen 'OK' drücken oder 'Abbrechen' um zurückzukehren. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Alle an der Datenbank gemachten Änderungen zurücksetzen (importieren, + aktualisieren, editieren, anpassen) descBlacklist: - description: HTML Description for the global blacklist. - message: URLs in dieser Liste bleiben von Skripten unberührt. + description: Description for the global injection blacklist. + message: Injektions-Blacklist (Skripte werden bei gematchten Seiten nicht ausgeführt) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Netzwerk-Blacklist (Skripte verbinden sich nicht mit gematchten Seiten und + ihren Cookies) descCustomCSS: description: Description of custom CSS section. message: >- @@ -117,12 +162,40 @@ descEditorOptions: {"indentUnit":2, "smartIndent":true}, möglicherweise werden einige von ihnen jedoch nicht mit Violentmonkey funktionieren. Konsultieren Sie die vollständige Liste. Um ein - benutzerdefiniertes CodeMirror-Theme zu benutzen, spezifizieren Sie hier - dessen Dateinamen ähnlich wie "theme":"3024-day" und fügen Sie - die tatsächliche Theme-CSS in das - "Benutzerdefinierte Aussehen"-Eingabefeld unterhalb ein. + target="_blank" rel="noopener noreferrer">vollständige Liste. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Boole'sche Optionen können mit einem Doppelklick auf den Wert umgeschaltet + werden: true = aktiviert, false = deaktiviert. + Numerische Optionen endend mit Delay, Interval, + Rate, Time sind in Millisekunden. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Nicht übliche Optionen: autocompleteOnTyping ist eine + Verzögerung in Millisekunden um Autovervollständingungs-Vorschläge nach dem + Eintippen anzuzeigen (0 = deaktivieren). + killTrailingSpaceOnSave entfernt automatisch überflüssige + Leerzeichen in Zeilen beim Speichern, showTrailingSpace zeigt + die überflüssigen Leerzeichen als Punkte. +descScriptTemplate: + description: Description of script template section. + message: >- + Unterstützte Variablen: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + in MomentJS-kompatiblem Format, zum Beispiel: <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: gruppieren +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: ausblenden +disabledScriptsSelector: + description: Label of the option. + message: 'Deaktivierte Skripte:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: anzeigen editHelpDocumention: description: Label in the editor help tab for the documentation link. message: >- @@ -165,67 +238,116 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: Alles + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: Gesamten Skriptwert-Speicher anzeigen/bearbeiten -editValueCancel: - description: Button to cancel modification of a script value. - message: Abbrechen -editValueSave: - description: Button to save modification of a script value. - message: Speichern extDescription: - description: 'Description for this extension, will be displayed in web store' - message: 'Ein quelloffener Benutzerskript-Manager, der viele Browser unterstützt.' + description: Description for this extension, will be displayed in web store + message: Ein quelloffener Benutzerskript-Manager, der viele Browser unterstützt. extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: In den Violentmonkey-Einstellungen geblacklistet failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) - message: |- + message: >- Violentmonkey kann keine Benutzerskripte auf dieser Seite ausführen - (häufige Beispiele: Browseroberfläche oder Erweiterung) + + (gewöhnliche Beispiele: Browser-UI, eine Erweiterung, durch Richtlinien + blockiert, eine Suchmaschinen-Seite in Opera) +failureReasonRestarted: + description: Shown in the popup. + message: >- + Violentmonkey wurde neu gestartet. Bitte laden Sie den Tab neu um + Benutzerskripte auszuführen. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Es gibt mehrere Methoden um eine lokale Datei zu installieren oder + Bearbeitungen in dieser zu tracken: <1> Drag-and-Drop der Datei in eine + geöffnete Violentmonkey-Seite oder -Pop-up, inklusive diesem. <2> Einen + lokalen HTTP-Server für die Datei installieren und diese via + http://localhost öffnen. <3> In den Violentmonkey-Details auf der + chrome://extensions-Seite "Zugriff auf Datei-URLs zulassen" aktivieren. Dies + ist potenziell gefährlich, da dies jedem Benutzerskript ermöglicht jede + lokale Datei zu lesen. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: alphabetischer Reihenfolge + message: alphabetisch filterExecutionOrder: description: Label for option to sort scripts in execution order. - message: Ausführungsreihenfolge + message: Ausführung filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: Zeitpunkt der letzten Aktualisierung +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: Letzter Besuchszeitpunkt +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: >- + Der letzte Zeitpunkt, an dem eine besuchte Seite von diesem Skript erfasst + wurde. filterScopeAll: description: Option in dashboard's search scope filter. message: Alles + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Code + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: Name + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: Größe genericError: description: Label for generic error. message: Fehler genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: aus genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: an genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: Globale Einstellung nutzen +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + "#"-Ausdrücke funktionieren nur bei erstmaligem Öffnen der Seite oder dem + Neuladen des Tabs: $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Papierkorb +helpForLocalFile: + description: Label of the checkbox. + message: Anleitung für per Drag-and-Drop hinzugefügte lokale Skripte anzeigen +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: für $1 gematchte Skripte +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: >- + Ein gesandboxtes Skript kann diese globalen Eigenschaften nicht ändern: + $1.Um das Skript zu reparieren, fügen Sie entweder '@grant-none' hinzu um + die Sandbox zu deaktivieren,oder nutzen Sie 'unsafeWindow' anstatt von + 'window'. hintInputURL: description: Hint for a prompt box to input URL of a user script. - message: 'URL eingeben:' + message: 'Input-URL:' hintRecycleBin: description: Hint for recycle bin. message: Gelöschte Skripte werden hier aufgelistet und 7 Tage aufbewahrt. @@ -235,17 +357,18 @@ hintUseDownloadURL: hintVacuum: description: Hint for vacuuming data. message: >- - Die Redundanz verwerfen und versuchen, die fehlenden Ressourcen im Cache neu - zu laden. + Die Redundanz verwerfen und versuchen, die fehlenden Ressourcen neu in den + Cache zu laden. +install: + description: Label for button to install a script. + message: Installieren installFrom: description: Label for button to install script from a userscript site. message: Installiere aus $1 installOptionClose: description: Option to close confirm window after installation. message: Nach der Installation schließen -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: 'Lokale Datei tracken, bis dieses Fenster geschlossen wird' + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -272,14 +395,25 @@ labelAutoReloadCurrentTabDisabled: Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: Gegenwärtig ausgeführte Skripte bleiben aktiv bis der Tab neu geladen wird + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates message: >- Auf Skript-Aktualisierungen jede(n) $1Tag(e) prüfen, 0 eingeben um zu deaktivieren +labelBackup: + description: Label of the import/export section in settings. + message: Backup + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Backup und Wartung labelBadge: description: Label for option to show number on badge. - message: 'Auf dem Symbol anzeigen: ' + message: 'Badge:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Badge-Farben:' labelBadgeNone: description: Option to display nothing on badge. message: nichts @@ -288,7 +422,7 @@ labelBadgeTotal: message: Anzahl angewandter Skripte labelBadgeUnique: description: Option to display number of unique running scripts on badge. - message: 'Anzahl angewandter, einzigartiger Skripte' + message: Anzahl angewandter, einzigartiger Skripte labelBlacklist: description: Label for global blacklist settings in security section. message: Blacklist @@ -304,12 +438,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Datenexport + touched: false labelDataImport: description: Section title of data import. message: Datenimport + touched: false labelDonate: description: Label of link to donate page. message: Spenden + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'Download-URL:' @@ -322,6 +459,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: Editor +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: nur bei aktivierten Skripten labelExclude: description: Label of @exclude rules. message: Ausschlussregeln (@exclude) @@ -334,12 +474,20 @@ labelExportScriptData: labelExposeStatus: description: Option in advanced settings. message: 'Installierte Version auf Benutzerskript-Katalogseiten offenbaren: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Alternativer $1-Modus in Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Ein alternativer Injektionsmodus für -Skripte seit Firefox + 59, schneller als der Standardmodus. Ähnlich wie der "Synchrone Page-Modus" + erhöht er den Arbeitsspeicher-Verbrauch, daher sollten Sie ihn unter + Umständen deaktivieren, falls Ihre Skripte ohne diese Option korrekt + funktionieren. labelFeedback: description: Label of link to feedback page. message: Feedback -labelFilterSort: - description: Label for sort filter. - message: Sortieren nach $1 labelGeneral: description: Label for general settings. message: Allgemein @@ -352,6 +500,9 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'Homepage-URL:' +labelIconURL: + description: Label for the input. + message: 'Icon-URL:' labelImportScriptData: description: Option to import script data along with scripts. message: Skriptdaten importieren @@ -362,8 +513,10 @@ labelInclude: description: Label of @include rules. message: Einschlussregeln (@include) labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Standard-Injektionsmodus: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Injektionsmodus: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: Skript installieren @@ -382,10 +535,12 @@ labelMatch: labelName: description: Label of script name. message: 'Name:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'In Frames ausführen:' labelNoName: description: Text as the name of a script when no @name is assigned. - message: Ohne Name - touched: false + message: Ohne Namen labelNoSearchScripts: description: Message shown when no script is found in search results. message: Kein Skript gefunden. @@ -403,12 +558,14 @@ labelNotifyUpdatesGlobal: message: >- Skriptspezifische Benachrichtigung ignorieren ("Einstellungen"-Tab im Editor) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Skripte im Pop-up sortieren nach $1 labelPrivacyPolicy: description: Label of link to privacy policy message: Datenschutzrichtlinie +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Skript re-installieren labelRelated: description: Label of related links. message: 'Verwandte Links: ' @@ -436,6 +593,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Einstellungen +labelShowOrder: + description: Label for option in dashboard -> script list + message: Positionen in der Ausführungsreihenfolge anzeigen +labelShowVisited: + description: Label for option in dashboard -> script list + message: Letzten Besuchszeitpunkt anzeigen labelSync: description: Label for sync options. message: Sync @@ -448,6 +611,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Autorisiere... +labelSyncAutomatically: + description: Label for option to sync automatically. + message: Automatischer Sync labelSyncDisabled: description: Label for option to disable sync service. message: nichts @@ -473,6 +639,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: 'Benutzername: ' +labelTags: + description: Label for custom tags. + message: 'Tags (leerzeichen-separiert):' +labelTheme: + description: Label for the visual theme option. + message: 'Theme: ' labelTranslator: description: Label of translator. message: 'Übersetzer: ' @@ -488,12 +660,33 @@ labelViewSingleColumn: labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. message: Tabellenansicht +labelWidth: + description: Width. + message: 'Breite:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Synchroner $1-Modus +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Nur aktivieren, falls Sie ein Skript haben, das vor dem Laden der Seite + ausgeführt werden muss und dies gegenwärtig zu spät passiert. Ebenso wie + Tampermonkeys Modus der "Sofortigen Injektion" nutzt diese Option das + veraltete synchrone XHR, daher werden Sie in Chrome/Chromium Warnungen in + der DevTools-Konsole sehen, jedoch können Sie diese gefahrlos ignorieren, da + nachteilige Effekte in diesem Fall vernachlässigbar sind. Sie können die + Warnungen mit einem Rechtsklick auf eine dieser dauerhaft ausblenden. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (außer inkognito und Seiten mit deaktivierten Cookies) lastSync: description: Label for last sync timestamp. message: Letzter Sync am $1 learnBlacklist: description: Refers to a link to introduce blacklist patterns. - message: Mehr Informationen zur Anwendung der Blacklist + message: Mehr Informationen zu Blacklist-Ausdrücken learnInjectionMode: description: Refers to a link to introduce injection modes. message: Mehr Informationen zu den Injektionsmodi @@ -519,6 +712,11 @@ menuExcludeHint: manuell neu. Nutzen Sie für mehr Flexibilität den "Einstellungen"-Tab des Editors. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Feedback menuFindScripts: description: Menu item to find scripts for a site. message: Skripte für diese Seite finden @@ -526,8 +724,13 @@ menuInjectionFailed: description: Injection error. message: Einige Skripte konnten nicht injiziert werden. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: Im "Auto"-Modus erneut versuchen +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Gematchte deaktivierte Skripte menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. message: Subframe-spezifische Skripte @@ -538,14 +741,19 @@ menuNewScript: description: Menu item to create a new script. message: Ein neues Skript erstellen menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Skripte deaktiviert menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Skripte aktiviert msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Prüfe auf Aktualisierungen... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Klicken, um die MomentJS-Dokumentation zu öffnen. Erlaubte Token: $1. Nutzen + Sie [eckige Klammern] um tatsächlichen Text zu schützen. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -576,7 +784,7 @@ msgIncognitoChanges: window. message: >- Im Inkognito-Modus getätigte Änderungen werden auch im Standard-Nutzerprofil - angewandt. + übernommen. msgInstalled: description: Message shown when a script is installed. message: Das Skript wurde installiert. @@ -599,11 +807,25 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Lade Abhängigkeiten... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Fehlende benötigte Ressourcen. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. - message: Namensraum-Konflikte! Bitte ändern Sie @name und @namespace des Skripts. + message: >- + Ein Skript wie dieses ist bereits installiert. + + Bitte wählen Sie hier einen anderen @name und @namespace oder editieren Sie + stattdessen das existierende Skript. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Ein Skript mit gleichem @name und @namespace ist bereits installiert. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -612,21 +834,36 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Keine Aktualisierung gefunden. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Fehler beim Aktualisieren von Skripten. Anklicken, um sie zu öffnen. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Bitte speichern oder installieren Sie diese(s) Skript(e) erneut:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (identischer Code) msgSavedBlacklist: description: Message shown when blacklist are saved. message: Blacklist wurde aktualisiert. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Benutzerdefiniertes CSS wurde aktualisiert. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Editoroptionen wurden aktualisiert. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: Benutzerdefinierte Skriptvorlage wurde aktualisiert. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Skript [$1] wurde aktualisiert!' + message: Skript [$1] wurde aktualisiert! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: Anzeigen/ausblenden @@ -639,12 +876,28 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Initialisierung fehlgeschlagen! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Noch nicht autorisiert. msgSyncReady: description: Message shown when sync will start soon. message: Sync startet in Kürze... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Sync in Arbeit... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Syntaxfehler? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + Das Skript existierte nicht oder matchte nicht mit der URL als die Seite + geladen wurde, dies passiert in Single-Page-Webanwendungen wie Facebook oder + Instagram, die keine echte Seitennavigation nutzen. Sie können den Tab neu + laden um das Skript auszuführen. Um das Skript zu reparieren, nutzen Sie + @match für die gesamte Seite und dann zum Erkennen von Änderungen + MutationObserver oder die window.navigation-API. msgUpdated: description: Message shown when a script is updated/reinstalled. message: Das Skript wurde aktualisiert. @@ -667,15 +920,90 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: Omnibox ausblenden +optionPopup: + description: Label of the popup menu section in settings. + message: Pop-up-Menü und Icon optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: Aktivierte zuerst +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Deaktivierte Skripte gruppieren + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Nach @run-at-Phasen gruppieren optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: Deaktivierte ausblenden + message: Deaktivierte Skripte ausblenden + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Deaktivierte Skripte anzeigen + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Aktivierte Skripte zuerst anzeigen +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'UI-Theme: ' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: Automatisch +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: Dunkel +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: Hell +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Aktualisierung +popupSettings: + description: Item inside the popup's "⋮" menu + message: Pop-up-Einstellungen +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Rechtsklick: Pop-up-Einstellungen' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Schreibgeschützt +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Skript mit automatischen Aktualisierungen ist schreibgeschützt, außer Sie + deaktivieren diese oder erlauben das Bearbeiten. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Bearbeitungen erlauben +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (nächstes Update wird sie überschreiben) +reinstall: + description: Button to reinstall a script + message: Re-installieren +reloadTab: + description: Label of action to reload the tab + message: Tab neu laden +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Falls aktiviert, wird der aktive Tab neu geladen, wenn Änderungen erkannt + werden und das Skript mit der Tab-URL matcht. +removeAllScripts: + description: Button to remove all scripts + message: Alle Skripte entfernen + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: >- + Alle Skripte in den Papierkorb verschieben? + + Dieser wird nach einer Weile automatisch geleert. Sie können ihn auch öffnen + und explizit leeren. + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Groß- und Kleinschreibung beachtend @@ -691,14 +1019,83 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Einstellungen -titleScriptUpdated: - description: Notification title for script updates. - message: Aktualisierung +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Seite ohne Benutzerskripte neu laden +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Sie haben Benutzerskripte auf dieser Seite deaktiviert. Um sie auszuführen, + laden Sie den Tab neu oder navigieren Sie in ihm. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Sortierungsreihenfolge:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Tracking stoppen +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Normale Badge-Farbe +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Badge-Farbe, wenn die Seite uninjizierbar ist (geblacklistet oder nicht + unterstützt) titleSearchHint: description: Hover title for search icon in dashboard. message: |- * -Taste fügt den Text zum Autovervollständigungs-Verlauf hinzu * RegExp-Syntax wird unterstützt: /re/ und /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Enter-Taste fügt den Text zur Autovervollständigungs-Chronik + hinzu + + * Alle Bedingungen sind Groß- und Kleinschreibung beachtend + + * Leerzeichen-separierte Bedingungen können kombiniert werden + + * Suche nach Metadaten: "Awesome Script" "Description" + + * Suche nach Tags: #tag1 #tag2 + + * Suche nach Skriptnamen: name:"awesome name" + + * Suche nach Skriptcode: code:"awesome code" + + * Negativsuche: !#tag2 !name:"unwanted" + + * Reguläre Ausdrücke: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re::"\w+? with space" +toggleInjection: + description: Label for a keyboard shortcut. + message: Benutzerskript-Injektion umschalten +trackEdits: + description: Button in a script installation dialog. + message: Externe Bearbeitungen tracken +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: >- + Lassen Sie diese Seite geöffnet, um Ihre Bearbeitungen der lokalen Datei zu + tracken +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Skript(e) aktualisieren ($1) +updateScript: + description: Button to update one script. + message: Aktualisierung +updateScriptsAll: + description: Command/button to update all scripts. + message: Alle Skripte aktualisieren +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Skripte des aktuellen Tabs aktualisieren valueLabelKey: description: Label for key of a script value. message: Schlüssel (String) @@ -708,6 +1105,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: Alle Werte (als JSON serialisiert) +valueLabelValueOr: + description: $1 is "[x] string" + message: Wert (serialisiert als JSON oder $1) +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: $1 String visitWebsite: description: Label for link to open Violentmonkey website. message: Webseite besuchen + touched: false diff --git a/src/_locales/el/messages.yml b/src/_locales/el/messages.yml index b98d51326f..d2f79ee52d 100644 --- a/src/_locales/el/messages.yml +++ b/src/_locales/el/messages.yml @@ -1,30 +1,48 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Εφαρμογή buttonCancel: description: Cancel button on dialog. message: Ακύρωση +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Έλεγχος για αναβαθμίσεις + touched: false buttonClose: description: Button to close window. message: Κλείσιμο buttonConfirmInstallation: description: Button to confirm installation of a script. message: Επιβεβαίωση εγκατάστασης + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: '' + touched: false buttonDisable: description: Button to disable a script. message: Απενεργοποίηση +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false buttonEdit: description: Button to edit a script. message: Επεξεργασία buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: '' + message: >- + Εναλλακτικά, μπορείτε να κάνετε δεξί κλικ, κλικ κρατώντας το πλήκτρο Ctrl + πατημένο ή κλικ με τη ροδέλα του ποντικιού στο όνομα του script. buttonEmptyRecycleBin: description: Button to empty the recycle bin. - message: Άδειασμα κάδου ανακύκλωσης τώρα! + message: Άδειασμα κάδου ανακύκλωσης buttonEnable: description: Button to enable a script. message: Ενεργοποίηση buttonExportData: description: Button to open the data export dialog. - message: Εξαγωγή σε zip + message: Εξαγωγή σε αρχείο zip buttonFilter: description: Button to show filters menu. message: Φίλτρα @@ -34,10 +52,10 @@ buttonHome: message: Αρχική σελίδα buttonImportData: description: Button to choose a file for data import. - message: Εισαγωγή από zip + message: Εισαγωγή από αρχείο zip buttonInstallFromURL: description: Button to ask for URL of a user script. - message: Εγκατάσταση από URL + message: Εγκατάσταση από διεύθυνση URL buttonInstallOptions: description: Button to show options of installation confirm page. message: Επιλογές @@ -47,10 +65,10 @@ buttonNew: message: Νέο buttonOK: description: OK button on dialog. - message: Εντάξει + message: ΟΚ buttonRecycleBin: description: Button to list scripts in recycle bin. - message: Κάδος Ανακύκλωσης + message: Κάδος ανακύκλωσης buttonRemove: description: Button to remove a script. message: Αφαίρεση @@ -63,6 +81,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Επαναφορά +buttonResetSettings: + description: Button in settings page to reset all settings + message: Επαναφορά όλων των ρυθμίσεων buttonRestore: description: Button to restore a removed script. message: Επαναφορά @@ -71,65 +92,136 @@ buttonSave: message: Αποθήκευση buttonSaveClose: description: Button to save modifications of a script and then close the editing page. - message: Αποθήκευση και Κλείσιμο + message: Αποθήκευση και κλείσιμο +buttonSaved: + description: Button text after saving. + message: Αποθηκεύτηκε buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. - message: Εμφάνιση κατάστασης επεξεργαστή κώδικα + message: Εμφάνιση κατάστασης επεξεργαστή κειμένου buttonSupport: description: Button to open support page. message: Βοήθεια +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. message: Αναίρεση - touched: false buttonUpdate: - description: Check a script for updates. - message: Έλεγχος για αναβαθμίσεις + description: Button to update a script. + message: Ενημέρωση buttonUpdateAll: description: Check all scripts for updates. message: Έλεγχος όλων για αναβαθμίσεις + touched: false buttonVacuum: description: Button to vacuum extension data. message: Εκκαθάριση βάσης δεδομένων buttonVacuumed: description: Message shown when data is vacuumed. - message: Δεδομένα διαγράφηκαν + message: Η εκκαθάριση ολοκληρώθηκε buttonVacuuming: description: Message shown when data vacuum is in progress. - message: Διαγραφή δεδομένων... + message: Εκκαθάριση... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: >- + Αυτό το script δεν ενημερώνεται αυτόματα! + + Κάντε κλικ στο κουμπί «ΟΚ» αν θέλετε να πραγματοποιήσετε την ενημέρωση ούτως + ή άλλως. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. - message: |- - 'Οι αλλαγές δεν αποθηκεύτηκαν!' - 'Πατήστε Εντάξει για να τις απορρίψετε ή Άκυρο για να παραμείνετε.' + message: >- + Οι αλλαγές δεν έχουν αποθηκευτεί! + + Κάντε κλικ στο κουμπί «ΟΚ» για να τις απορρίψετε ή στο κουμπί «Άκυρο» για να + παραμείνετε σε αυτή τη σελίδα. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Αναίρεση όλων των αλλαγών που πραγματοποιήθηκαν στη βάση δεδομένων + (εισαγωγή, ενημέρωση, επεξεργασία και προσαρμογή) descBlacklist: - description: HTML Description for the global blacklist. + description: Description for the global injection blacklist. + message: Injection blacklist (τα scripts δε θα τρέχουν στις παρακάτω ιστοσελίδες) +descBlacklistNet: + description: Description for the global network blacklist. message: >- - Στα URL που ταιριάζουν σε αυτήν την λίστα δεν θα εκτελούνται σενάρια - εντολών. + Blacklist δικτύου (τα scripts δε θα συνδέονται στις παρακάτω ιστοσελίδες + ούτε θα έχουν πρόσβαση στα cookies τους) descCustomCSS: description: Description of custom CSS section. message: >- - 'Προσαρμοσμένη CSS για την σελίδα των επιλογών και την σελίδα εγκατάστασης - σεναρίων. Αν δεν είστε σίγουρος(η) τι είναι αυτό, παρακαλώ μην το αλλάξετε.' + Προσαρμοσμένος κώδικας CSS για τη σελίδα ρυθμίσεων και την σελίδα + εγκατάστασης script. Αν δεν είστε σίγουρος(η) τι είναι αυτό, παρακαλώ μην το + αλλάξετε. descEditorOptions: description: Description of editor options JSON section. - message: '' + message: >- + Επιλογές για το CodeMirror και για πρόσθετα σε αντικείμενα JSON, όπως το + {"indentUnit":2, "smartIndent":true}. Μερικές από τις επιλογές + ενδέχεται να μη λειτουργούν με το Violentmonkey. Δείτε την πλήρη λίστα εδώ. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Μπορείτε να τροποποιήσετε τις τιμές αληθείας (boolean) κάνοντας διπλό κλικ + στην αντίστοιχη τιμή: true = ενεργοποιημένο, false + = απενεργοποιημένο. Οι αριθμητικές τιμές των επιλογών που λήγουν σε + Delay, Interval, Rate, + Time αναφέρονται σε χρόνο και μετριούνται σε χιλιοστά του + δευτερολέπτου (milliseconds). +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Άτυπες επιλογές: η τιμή της επιλογής autocompleteOnTyping είναι + η καθυστέρηση σε χιλιοστά του δευτερολέπτου πριν την εμφάνιση υποδείξεων + αυτόματης συμπλήρωσης (0 = απενεργοποίηση επιλογής), η επιλογή + killTrailingSpaceOnSave αφαιρεί αυτόματα μετά την αποθήκευση + τυχόν διαστήματα στο τέλος του κειμένου, ενώ τέλος η επιλογή + showTrailingSpace επιτρέπει την προβολή των διαστημάτων στο + τέλος του κειμένου ως τελείες. +descScriptTemplate: + description: Description of script template section. + message: >- + Υποστηριζόμενες μεταβλητές: <{{name}}>, <{{url}}>, <{{date}}>, + <{{date:format}}>. Η μορφή πρέπει να είναι συμβατή με το πακέτο MomentJS, + για παράδειγμα <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: ομαδοποίηση +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: απόκρυψη +disabledScriptsSelector: + description: Label of the option. + message: 'Απενεργοποιημένα script:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: εμφάνιση editHelpDocumention: description: Label in the editor help tab for the documentation link. - message: Οδηγίες για τα μεταδεδομένα και το GM API + message: Οδηγίες για τα μεταδεδομένα και τεκμηρίωση για το GM API editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. message: 'Συντομεύσεις πληκτρολογίου:' editHowToHint: description: The text of the how-to link in the editor header. - message: Θέλετε να χρησιμοποιήσετε έναν άλλο επεξεργαστή κώδικα; + message: Θέλετε να χρησιμοποιήσετε κάποιον άλλο επεξεργαστή κώδικα; editLabelMeta: description: Metadata section in settings tab of script editor. message: Προσαρμοσμένα μεταδεδομένα editLabelSettings: description: Settings section in settings tab of script editor. - message: Ρυθμίσεις δέσμης ενεργειών + message: Ρυθμίσεις script editLongLine: description: Shown in the editor in lines that were cut due to being too long message: Αυτή η γραμμή είναι πολύ μεγάλη @@ -137,9 +229,10 @@ editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long message: >- Αυτή η γραμμή περιέχει υπερβολικά πολλούς χαρακτήρες και δεν εμφανίζεται - ολόκληρη για να μην υπάρχουν καθυστερήσεις στην επεξεργασία. + ολόκληρη προκειμένου να μην υπάρξουν καθυστερήσεις στην επεξεργασία. - Μπορείτε να προσαρμόσετε το όριο στις προχωρημένες ρυθμίσεις, π.χ.: + Μπορείτε να τροποποιήσετε το όριο στις ρυθμίσεις για προχωρημένους, όπως στο + εξής παράδειγμα: "maxDisplayLength": 20000 editNavCode: @@ -154,69 +247,116 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: Όλα + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: Άκυρο -editValueSave: - description: Button to save modification of a script value. - message: Αποθήκευση + message: Εμφάνιση/επεξεργασία όλων των αποθηκευμένων τιμών του script extDescription: - description: 'Description for this extension, will be displayed in web store' - message: Προσφέρει υποστήριξη για σενάρια χρηστών για τους φυλλομετρητές. + description: Description for this extension, will be displayed in web store + message: >- + Μία ανοιχτού κώδικα επέκταση διαχείρισης userscript, η οποία προσφέρει + υποστήριξη για πολλά διαφορετικά προγράμματα περιήγησης extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' - message: '' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: Αποκλεισμένη από τις ρυθμίσεις του Violentmonkey failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) message: >- - Το Violentmonkey δεν μπορεί να εκτελέσει τα userscripts σε αυτήν τη σελίδα + Το Violentmonkey δεν μπορεί να εκτελέσει userscripts σε αυτήν τη σελίδα - (πιθανότατα βρίσκεστε σε μία σελίδα είτε του περιηγητή σας είτε μία - επέκτασης) + (πιθανότατα βρίσκεστε σε μία από τις εξής σελίδες: σελίδα διεπαφής του + προγράμματος περιήγησης, σελίδα μιας επέκτασης, σελίδα αποκλεισμένη από + κάποια πολιτική του προγράμματος περιήγησης, ιστοσελίδα μηχανής αναζήτησης + στο Opera) +failureReasonRestarted: + description: Shown in the popup. + message: >- + Έγινε επανεκκίνηση του Violentmonkey. Παρακαλούμε επαναφορτώστε την καρτέλα + προκειμένου να εκτελεστούν τα userscripts. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Υπάρχουν πολλοί τρόποι να εγκαταστήσετε ή να παρακολουθήσετε τις αλλαγές σε + ένα τοπικό αρχείο: <1>Μεταφέρετε και αποθέστε το αρχείο σε μία ανοιχτή + σελίδα του Violentmonkey, όπως αυτή εδώ. <2>Εγκαταστήστε έναν τοπικό + διακομιστή HTTP για αυτό το αρχείο και ανοίξτε τον μέσω της διεύθυνσης URL + http://localhost. <3>Μεταβείτε στη σελίδα chrome://extensions, κάντε κλικ + στο κουμπί «Λεπτομέρειες» κάτω από την επέκταση Violentmonkey και στη + συνέχεια ενεργοποιήστε την επιλογή «Να επιτρέπεται η πρόσβαση σε διευθύνσεις + URL αρχείων». Σημειώνουμε, πάντως, πως η ενεργοποίηση αυτής της ρύθμισης + είναι επικίνδυνη επειδή δίνει σε κάθε userscript τη δυνατότητα ανάγνωσης + όλων των τοπικών αρχείων. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: αλφαβητική σειρά + message: αλφαβητικά filterExecutionOrder: description: Label for option to sort scripts in execution order. message: σειρά εκτέλεσης filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: τελευταία ενημέρωση +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: Όλα + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Κώδικας + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: Όνομ + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: μέγεθος genericError: description: Label for generic error. message: Σφάλμα genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' - message: '' + description: To indicate something is turned off or disabled, similar to "no". + message: απενεργοποιημένο genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' - message: '' + description: To indicate something is turned on or enabled, similar to "yes". + message: ενεργοποιημένο genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. - message: '' + message: χρήση καθολικής ρύθμισης +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + Τα μοτίβα "#" λειτουργούν μόνο κατά το αρχικό άνοιγμα μιας ιστοσελίδας ή με + την επαναφόρτωση μιας καρτέλας: $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Κάδος Ανακύκλωσης +helpForLocalFile: + description: Label of the checkbox. + message: Εμφάνιση οδηγιών για τα τοπικά script που έχουν μεταφερθεί και αποτεθεί +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: θα εφαρμοστεί σε $1 script +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. - message: 'Εισαγωγή URL:' + message: 'Εισάγετε τη διεύθυνση URL:' hintRecycleBin: description: Hint for recycle bin. message: Τα scripts που διαγράφονται φαίνονται εδώ και διατηρούνται για 7 ημέρες. @@ -226,22 +366,25 @@ hintUseDownloadURL: hintVacuum: description: Hint for vacuuming data. message: >- - Απόρριψη των περιττών και προσπάθεια επαναφόρτωσης των πόρων που λείπουν - στην cache. + Απόρριψη των περιττών δεδομένων και προσπάθεια επαναφόρτωσης των πόρων που + λείπουν στη μνήμη cache. +install: + description: Label for button to install a script. + message: Εγκατάσταση installFrom: description: Label for button to install script from a userscript site. message: Εγκατάσταση από $1 installOptionClose: description: Option to close confirm window after installation. message: Κλείσιμο μετά την εγκατάσταση -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Παρακολούθηση του τοπικού αρχείου πριν αυτό το παράθυρο κλείσει + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before window is closed. - message: '' + message: >- + Η καρτέλα του πρωτότυπου αρχείου πρέπει να μένει ανοιχτή στις νεότερες + εκδόσεις του Firefox (68 και εξής). labelAbout: description: Label shown on top of the about page. message: Περί του Violentmonkey @@ -251,70 +394,87 @@ labelAdvanced: message: Για προχωρημένους labelAllowUpdate: description: Option to allow checking updates for a script. - message: Επιτρέψτε αναβαθμίσεις + message: Να επιτρέπεται ο έλεγχος για ενημερώσεις labelAuthor: description: Label of author shown in the details of a script. - message: 'Συγγραφέας: ' + message: 'Συντάκτης: ' labelAutoReloadCurrentTab: description: Option to reload current tab after a script is switched on or off from menu. message: >- Επαναφόρτωση τρέχουσας καρτέλας μετά την ενεργοποίηση/απενεργοποίηση ενός - σεναρίου από το μενού + script από το μενού labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: Αυτά τα scripts θα συνεχίσουν να τρέχουν έως ότου επαναφορτώσετε την καρτέλα + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates message: >- - Έλεγχος για ενημερώσεις κάθε $1 ημέρα/ημέρες, χρησιμοποιήστε 0 για - απενεργοποίηση + Έλεγχος για ενημερώσεις κάθε $1 ημέρα/ημέρες (πληκτρολογήστε 0 για + απενεργοποίηση) +labelBackup: + description: Label of the import/export section in settings. + message: '' + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Αντίγραφα ασφαλείας και συντήρηση labelBadge: description: Label for option to show number on badge. - message: 'Εμφάνιση στο εικονίδιο: ' + message: 'Σήμα:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Χρώματα σήματος: ' labelBadgeNone: description: Option to display nothing on badge. message: τίποτα labelBadgeTotal: description: Option to display total number of running scripts on badge. - message: αριθμός των σεναρίων που εκτελούνται + message: πλήθος script που εκτελούνται labelBadgeUnique: description: Option to display number of unique running scripts on badge. - message: αριθμός των μοναδικών σεναρίων που εκτελούνται + message: πλήθος μοναδικών script που εκτελούνται labelBlacklist: description: Label for global blacklist settings in security section. - message: Μαύρη λίστα + message: Blacklist labelContributors: description: Label for link to contributors. message: Συνεισφέροντες labelCurrentLang: description: Label of current language. - message: 'Τρέχουσα Γλώσσα: ' + message: 'Τρέχουσα γλώσσα: ' labelCustomCSS: description: Label for custom CSS section. - message: Προσαρμοσμένο Στυλ + message: Προσαρμοσμένο στυλ labelDataExport: description: Section title of data export. message: Εξαγωγή Δεδομένων + touched: false labelDataImport: description: Section title of data import. message: Εισαγωγή Δεδομένων + touched: false labelDonate: description: Label of link to donate page. message: Δωρίστε + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. - message: 'URL Λήψης:' + message: 'Διεύθυνση URL λήψης:' labelEditValue: description: Label shown in the panel to edit a script value. - message: '' + message: Επεξεργασία τιμής script labelEditValueAll: description: Label shown in the panel to edit the entire script value storage. message: Επεξεργασία αποθηκευτικού χώρου του script labelEditor: description: Label for Editor settings message: Επεξεργαστής κώδικα +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: Ενημέρωση μόνο των ενεργοποιημένων script labelExclude: description: Label of @exclude rules. message: Κανόνες @exclude @@ -323,19 +483,31 @@ labelExcludeMatch: message: Κανόνες @exclude-match labelExportScriptData: description: Option to export script data along with scripts. - message: Εξαγωγή δεδομένων σεναρίου + message: Εξαγωγή δεδομένων script labelExposeStatus: description: Option in advanced settings. - message: '' + message: >- + Εμφάνιση της εγκατεστημένης έκδοσης σε ιστοσελίδες με καταλόγους userscript: + $1 +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Εναλλακτική λειτουργία $1 για Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Ένας εναλλακτικός τρόπος εκτέλεσης για τα script που χρησιμοποιούν το + , διαθέσιμος στις νεότερες εκδόσεις του Firefox (59 και + εξής) και ο οποίος είναι γρηγορότερος από τον προεπιλεγμένο. Όπως και με την + επιλογή «Λειτουργία συγχρονισμένης σελίδας», η ρύθμιση αυτή έχει ως + αποτέλεσμα τη δέσμευση περισσότερης μνήμης. Σε περίπτωση, δηλαδή, που τα + script λειτουργούν κανονικά χωρίς αυτή τη ρύθμιση, ίσως είναι καλύτερο να + την απενεργοποιήσετε. labelFeedback: description: Label of link to feedback page. message: Σχόλια -labelFilterSort: - description: Label for sort filter. - message: Ταξινόμηση κατά $1 labelGeneral: description: Label for general settings. - message: Γενικές + message: Γενικές ρυθμίσεις labelHelpTranslate: description: Label for link to localization guide in about tab message: Βοηθήστε στη μετάφραση @@ -344,10 +516,13 @@ labelHomepage: message: Αρχική σελίδα labelHomepageURL: description: Label of script @homepageURL in custom meta data. - message: 'URL Αρχικής Σελίδας:' + message: 'Διεύθυνση URL αρχικής σελίδας:' +labelIconURL: + description: Label for the input. + message: 'Διεύθυνση URL εικονιδίου:' labelImportScriptData: description: Option to import script data along with scripts. - message: Εισαγωγή script + message: Εισαγωγή δεδομένων script labelImportSettings: description: Label for option to import settings from zip file. message: Ρυθμίσεις εισαγωγής @@ -355,14 +530,16 @@ labelInclude: description: Label of @include rules. message: Κανόνες @include labelInjectionMode: - description: Label for default option to inject scripts. - message: '' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Λειτουργία εφαρμογής (injection mode): ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. - message: Σενάριο εγκατάστασης + message: Γίνεται εγκατάσταση του script labelKeepOriginal: description: Option to keep the original match or ignore rules. - message: Κρατήστε τα αρχικά + message: Διατήρηση πρωτοτύπου labelLastUpdatedAt: description: Label shown on last updated time. message: Τελευταία ενημέρωση στις $1 @@ -375,31 +552,35 @@ labelMatch: labelName: description: Label of script name. message: 'Όνομα:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Εκτέλεση στα frames: ' labelNoName: description: Text as the name of a script when no @name is assigned. - message: Χωρίς Όνομα - touched: false + message: Χωρίς όνομα labelNoSearchScripts: description: Message shown when no script is found in search results. - message: Δεν βρέθηκε σενάριο. + message: Δε βρέθηκε κανένα script. labelNotifyThisUpdated: description: >- A per-script option in editor to enable notification when this script is updated. The text follows "Allow update" checkbox option so it's like a continuation of the phrase. - message: '' + message: ' Ειδοποιήσεις:' labelNotifyUpdates: description: Option to show notification when script is updated. - message: Ειδοποίηση ενημερώσεων σεναρίου + message: Εμφάνιση ειδοποιήσεων όταν υπάρχει διαθέσιμη ενημέρωση script labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. - message: '' -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Ταξινόμηση των scripts στο αναδυόμενο παράθυρο βάσει $1 + message: αγνόηση των ρυθμίσεων κάθε script για ειδοποιήσεις labelPrivacyPolicy: description: Label of link to privacy policy message: Πολιτική Απορρήτου +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Γίνεται επανεγκατάσταση του script labelRelated: description: Label of related links. message: 'Σχετικοί σύνδεσμοι: ' @@ -411,13 +592,13 @@ labelReplace: message: 'Αντικατάσταση με: ' labelRunAt: description: Label of script @run-at properties in custom meta data. - message: 'Εκτέλεση κατά: ' + message: 'Run-at: ' labelRunAtDefault: description: Shown when custom @run-at is not assigned. message: (Προκαθορισμένο) labelScriptTemplate: description: Label for custom script template. - message: '' + message: Προσαρμοσμένα πρότυπα script labelSearch: description: Label for search input in search box. message: 'Αναζήτηση για: ' @@ -427,6 +608,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Ρυθμίσεις +labelShowOrder: + description: Label for option in dashboard -> script list + message: Εμφάνιση της σειράς εκτέλεσης των script +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Συγχρονισμός @@ -435,138 +622,199 @@ labelSyncAnonymous: message: Χρήση ανώνυμου λογαριασμού labelSyncAuthorize: description: Label for button to authorize a service. - message: Εξουσιοδότησε + message: Εξουσιοδότηση labelSyncAuthorizing: description: Label for button when authorization is in progress. - message: Εξουσιοδότηση + message: Εξουσιοδότηση... +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. - message: Κανένας + message: Να μη γίνεται συγχρονισμός labelSyncPassword: description: Label for input to hold password. - message: 'Κωδικός:' + message: 'Κωδικός πρόσβασης:' labelSyncReauthorize: description: Option to reauthorize sync service when expired. message: Αυτόματη επανεξουσιοδότηση όταν λήξει touched: false labelSyncRevoke: description: Label for button to revoke authorization for a service. - message: Ανάκληση + message: Ανάκληση εξουσιοδότησης labelSyncScriptStatus: description: Label for option to sync script status. - message: Κατάσταση συγχρονισμού σεναρίου + message: Κατάσταση συγχρονισμού script labelSyncServerUrl: description: Label for input to hold server URL. - message: 'Διεύθυνση URL διακομιστή:' + message: 'Διεύθυνση URL διακομιστή: ' labelSyncService: description: Label for sync service select. message: Συγχρονισμός σε labelSyncUsername: description: Label for input to hold username. - message: 'Όνομα χρήστη:' + message: 'Όνομα χρήστη: ' +labelTags: + description: Label for custom tags. + message: 'Ετικέτες (διαχωρισμένες με διαστήματα):' +labelTheme: + description: Label for the visual theme option. + message: 'Θέμα: ' labelTranslator: description: Label of translator. message: 'Μεταφραστής: ' touched: false labelUpdateURL: description: Label of script @updateURL in custom meta data. - message: 'URL Ενημέρωσης:' + message: 'Διεύθυνση URL λήψης ενημερώσεων:' labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: Εμφάνιση σε μία στήλη labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. - message: '' + message: Εμφάνιση σε μορφή πίνακα +labelWidth: + description: Width. + message: 'Πλάτος:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Λειτουργία συγχρονισμένου $1 +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Ενεργοποιήστε αυτή τη ρύθμιση μόνο αν κάποιο script χρειάζεται να εκτελεστεί + πριν τη φόρτωση της σελίδας και προς το παρόν εκτελείται σε πολύ + μεταγενέστερο χρόνο. Όπως και στη λειτουργία Instant injection στην επέκταση + Tampermonkey, αυτή η επιλογή χρησιμοποιεί το συγχρονισμένο (synchronous) και + πλέον απαρχαιωμένο XHR. Αυτό έχει ως αποτέλεσμα την εμφάνιση προειδοποιήσεων + στην κονσόλα των εργαλείων για προγραμματιστές στο Chrome/Chromium, τις + οποίες όμως μπορείτε να αγνοήσετε με ασφάλεια, αφού η χρήση του XHR στη + συγκεκριμένη περίπτωση έχει αμελητέα επίδραση στις επιδόσεις του + προγράμματος περιήγησης. Μάλιστα, μπορείτε να αποκρύψετε όλες τις σχετικές + προειδοποιήσεις κάνοντας δεξί κλικ σε μια από αυτές. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: >- + (εκτός από καρτέλες ανώνυμης περιήγησης και ιστοσελίδες με απενεργοποιημένα + cookies) lastSync: description: Label for last sync timestamp. - message: Τελευταίος συγχρονισμός στις $1 + message: Ο τελευταίος συγχρονισμός πραγματοποιήθηκε στις $1 learnBlacklist: description: Refers to a link to introduce blacklist patterns. - message: Μάθετε περισσότερα για τα πρότυπα της μαύρης λίστας. + message: Μάθετε περισσότερα για τα πρότυπα της blacklist. learnInjectionMode: description: Refers to a link to introduce injection modes. - message: '' + message: >- + Μάθετε περισσότερα για της λειτουργίες εκτέλεσης των script (injection + modes) menuCommands: description: Menu item to list script commands. message: Εντολές σεναρίου touched: false menuDashboard: description: Label for menu item to open dashboard. - message: Άνοιγμα Ταμπλό + message: Άνοιγμα πίνακα ελέγχου menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: '' + message: Εξαίρεση... menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. - message: '' + message: >- + Αν έχετε ενεργοποιήσει αυτή την επιλογή στις γενικές ρυθμίσεις, θα + πραγματοποιηθεί αυτόματη επαναφόρτωση αυτής της καρτέλας. + + Για να εφαρμόσετε τις αλλαγές που έχετε πραγματοποιήσει σε όλες τις + υπόλοιπες καρτέλες, επαναφορτώστε καθεμία από αυτές. + + Χρησιμοποιήστε την καρτέλα «Ρυθμίσεις» του επεξεργαστή κώδικα για + περισσότερη ευελιξία. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Σχόλια menuFindScripts: description: Menu item to find scripts for a site. - message: Βρείτε σενάρια γι᾽ αυτήν την σελίδα + message: Βρείτε script για αυτή την σελίδα menuInjectionFailed: description: Injection error. - message: '' + message: Δεν ήταν δυνατή η εκτέλεση ορισμένων script menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' - message: '' + description: Injection error fix, shown in case the default mode is "page". + message: Εκ νέου προσπάθεια στη λειτουργία «auto» +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Απενεργοποιημένα script που αντιστοιχούν menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. - message: '' + message: Script που εκτελούνται σε sub-frames menuMatchedScripts: description: Label for menu listing matched scripts. - message: Σενάρια που ταιριάζουν + message: Script που αντιστοιχούν menuNewScript: description: Menu item to create a new script. message: Δημιουργία νέου script menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' - message: Σενάρια απενεργοποιήθηκαν + description: Menu item showing the status of Violentmonkey, when disabled. + message: Τα script απενεργοποιήθηκαν menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' - message: Σενάρια ενεργοποιήθηκαν + description: Menu item showing the status of Violentmonkey, when enabled. + message: Τα script ενεργοποιήθηκαν msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. - message: Έλεγχος για αναβαθμίσεις... + message: Έλεγχος για ενημερώσεις... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Κάντε κλικ εδώ για να ανοίξετε την τεκμηρίωση του MomentJS σε νέα καρτέλα. + Επιτρέπονται τα εξής tokens: $1. Χρησιμοποιήστε [αγκύλες] για να + περικλείσετε απλό κείμενο. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of the script. - message: Σφάλμα λήψης σεναρίου (resource)! + message: Δεν ήταν δυνατή η λήψη ενός πόρου του script! msgErrorFetchingScript: description: Message shown when Violentmonkey fails fetching a new version of the script. - message: Σφάλμα λήψης σεναρίου! + message: Δεν ήταν δυνατή η λήψη του script! msgErrorFetchingUpdateInfo: description: Message shown when Violentmonkey fails fetching version data of the script. - message: Αδυναμία λήψης πληροφοριών ενημέρωσης. + message: Δεν ήταν δυνατή η λήψη πληροφοριών ενημέρωσης. msgErrorLoadingData: description: >- Message shown on confirm page when the script to be installed cannot be loaded. - message: Σφάλμα φόρτωσης δεδομένων σεναρίων. + message: Δεν ήταν δυνατή η φόρτωση των δεδομένων του script. msgErrorLoadingDependency: description: Message shown when not all requirements are loaded successfully. - message: Σφάλμα φόρτωσης εξαρτήσεων. + message: Δεν ήταν δυνατή η φόρτωση των εξαρτήσεων (dependencies) του script. msgImported: description: >- Message shown after import. There is an argument referring to the count of scripts imported. - message: $1 αντικείμενο(α) εισήχθησαν. + message: $1 αντικείμενο(α) εισήχθη(σαν). msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito window. message: >- - Οι αλλαγές που θα κάνετε στην ανώνυμη περιήγηση θα εφαρμοστούν και στο - κυρίως προφίλ σας. + Οι αλλαγές που θα πραγματοποιήσετε στην ανώνυμη περιήγηση θα εφαρμοστούν και + στο κυρίως προφίλ σας. msgInstalled: description: Message shown when a script is installed. - message: Σενάριο εγκαταστάθηκε. + message: Το script εγκαταστάθηκε. msgInvalidScript: description: Message shown when script is invalid. - message: Μη έγκυρο σενάριο! + message: Μη έγκυρο script! msgLoadedData: description: >- Message shown in the confirm page when a javascript file to be installed is @@ -579,87 +827,214 @@ msgLoading: touched: false msgLoadingData: description: Message shown on confirm page when the script to be installed is loading. - message: Φόρτωση δεδομένων σεναρίου... + message: Φόρτωση δεδομένων script... msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Φόρτωση εξαρτήσεων... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Απουσιάζουν απαραίτητοι πόροι msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. message: >- - Σύγκρουση χώρου ονόματος σεναρίου! Παρακαλώ αλλάξτε το @name και το - @namespace. + Ένα script με ίδιο όνομα με αυτό είναι ήδη εγκατεστημένο. + + Παρακαλούμε είτε χρησιμοποιήστε διαφορετικό @name και @namespace είτε + επεξεργαστείτε το script. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Ένα script με ίδιο @name και @namespace είναι ήδη εγκατεστημένο. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @downloadURL is provided. - message: Βρέθηκε νέα έκδοση. + message: Μια νέα έκδοση είναι διαθέσιμη. msgNoUpdate: description: Message shown when there is no new version of a script. - message: Δεν βρέθηκε ενημέρωση. + message: Δε βρέθηκε καμία ενημέρωση. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Δεν ήταν δυνατή η ενημέρωση των script. Κάντε κλικ για να τα ανοίξετε. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Παρακαλούμε αποθηκεύστε ξανά ή εγκαταστήστε εκ νέου τα εξής script:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (ο κώδικας παρέμεινε ίδιος) msgSavedBlacklist: description: Message shown when blacklist are saved. message: Η Μαύρη λίστα ενημερώθηκε. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Το προσαρμοσμένο στυλ ενημερώθηκε. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Οι ρυθμίσεις του επεξεργαστή κώδικα ενημερώθηκαν. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: '' + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Το σενάριο [$1] είναι ενημερωμένο!' + message: Το script [$1] είναι ενημερωμένο! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: Εμφάνιση/απόκρυψη msgSyncError: description: Message shown when sync failed. - message: Σφάλμα συγχρονισμού! + message: Δεν ήταν δυνατός ο συγχρονισμός! msgSyncInit: description: Message shown when sync service is initializing. message: Αρχικοποίηση... msgSyncInitError: description: Message shown when sync fails in initialization. - message: Σφάλμα αρχικοποίησης! + message: Δεν ήταν δυνατή η αρχικοποίηση! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Δεν έχει παρασχεθεί εξουσιοδότηση. msgSyncReady: description: Message shown when sync will start soon. message: Ο συγχρονισμός θα ξεκινήσει σύντομα... + touched: false msgSyncing: description: Message shown when sync is in progress. - message: Συγχρονισμός σε εξέλιξη... + message: Ο συγχρονισμός βρίσκεται σε εξέλιξη... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Συντακτικό σφάλμα; +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + Το script δεν υπήρχε ή δεν υπήρξε αντιστοιχία με τη διεύθυνση URL της + ιστοσελίδας όταν αυτή φορτώθηκε αρχικά, γεγονός που συμβαίνει με τις + ιστοσελίδες της μορφής Single-Page Application (εφαρμογές μίας σελίδας) όπως + το Facebook και το Instagram, οι οποίες χρησιμοποιούν ένα είδος «ψεύτικης» + πλοήγησης. Μπορείτε να επαναφορτώσετε την καρτέλα για να εκτελέσετε το + script. Εναλλακτικά, μπορείτε να επεξεργαστείτε το script αλλάζοντας τον + κανόνα @match ώστε να περιλαμβάνει κάθε σελίδα του τομέα και τροποποιώντας + τον κώδικα ώστε τυχόν αλλαγές στη διεύθυνση URL να παρακολουθούνται - για + τον σκοπό αυτό, μπορείτε να χρησιμοποιήσετε την κλάση MutationObserver και + το API window.navigation. msgUpdated: description: Message shown when a script is updated/reinstalled. - message: Σενάριο ενημερώθηκε. + message: Το script ενημερώθηκε. msgUpdating: description: Message shown when a new version of script is being fetched. message: Ενημέρωση... noValues: description: Label shown when there is no value for current script. - message: '' + message: Καμία τιμή δεν έχει αποθηκευτεί στον χώρο αποθήκευσης του script optionEditorWindow: description: Label for the option in settings - message: Άνοιγμα επεξεργαστή από το αναδυόμενο παράθυρο σε νέο παράθυρο + message: Άνοιγμα επεξεργαστή κώδικα από το αναδυόμενο παράθυρο σε νέο παράθυρο optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support onBoundsChanged - message: '' + message: >- + Η θέση του παραθύρου του επεξεργαστή κώδικα θα αποθηκεύεται κάθε φορά που + αλλάζετε το μέγεθος του παραθύρου και κάθε φορά που κάνετε αποθήκευση το + script optionEditorWindowSimple: description: Label for the editor window type - message: '' + message: Απόκρυψη omnibox +optionPopup: + description: Label of the popup menu section in settings. + message: Αναδυόμενο παράθυρο μενού και εικονίδιο optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. + message: Πρώτα τα ενεργοποιημένα +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. message: '' + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Ομαδοποίηση με βάση την τιμή του @run-at optionPopupHideDisabled: description: Option to hide disabled scripts in popup. message: Απόκρυψη απενεργοποιημένων + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: '' + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. - message: Εμφάνιση των ενεργοποιημένων σεναρίων πρώτα + message: Εμφάνιση των ενεργοποιημένων script πρώτα +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Θέμα διεπαφής χρήστη: ' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: να εντοπίζεται αυτόματα +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: σκουρόχρωμο +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: ανοιχτόχρωμο +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Ενημέρωση +popupSettings: + description: Item inside the popup's "⋮" menu + message: Ρυθμίσεις αναδυόμενου παραθύρου +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Δεξί κλικ: ρυθμίσεις αναδυόμενου παραθύρου' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Μόνο για ανάγνωση +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Αυτό το script λαμβάνει ενημερώσεις αυτόματα και για τον λόγο αυτό βρίσκεται + σε λειτουργία μόνο ανάγνωσης. Για να απενεργοποιήσετε αυτή τη λειτουργία, + απενεργοποιήστε τον έλεγχο για ενημερώσεις σε αυτό το script η ενεργοποιήστε + την επιλογή «Να επιτρέπεται η επεξεργασία». +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Να επιτρέπεται η επεξεργασία +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: >- + (η εγκατάσταση της επόμενης ενημέρωσης θα αντικαταστήσει τυχόν αλλαγές που + θα πραγματοποιήσετε) +reinstall: + description: Button to reinstall a script + message: Επανεγκατάσταση +reloadTab: + description: Label of action to reload the tab + message: Επαναφόρτωση καρτέλας +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Εάν η επιλογή αυτή είναι ενεργοποιημένα, η ενεργή καρτέλα θα ανανεώνεται + κάθε φορά που εντοπίζονται αλλαγές στο script, υπό την προϋπόθεση πως η + διεύθυνση URL της καρτέλας ταιριάζει με τους κανόνες @match και @include του + script. +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Διάκριση πεζών-κεφαλαίων @@ -671,25 +1046,102 @@ sideMenuAbout: message: Σχετικά sideMenuInstalled: description: 'Side menu: Installed scripts' - message: Εγκατεστημένα σενάρια + message: Εγκατεστημένα script sideMenuSettings: description: 'Side menu: Settings' message: Ρυθμίσεις -titleScriptUpdated: - description: Notification title for script updates. - message: Ενημέρωση +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Επαναφορά της καρτέλας χωρίς userscripts +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Έχετε απενεργοποιήσει τα userscripts για αυτή τη σελίδα. Για να τα + εκτελέσετε ξανά, επαναφορτώστε την καρτέλα ή πλοηγηθείτε σε αυτήν. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Ταξινόμηση:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Διακοπή παρακολούθησης +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Κανονικό χρώμα σήματος +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Χρώμα σήματος όταν δεν εκτελούνται script επειδή η ιστοσελίδα είναι στη + λίστα blacklist ή επειδή δεν υποστηρίζεται η εκτέλεση script σε αυτήν titleSearchHint: description: Hover title for search icon in dashboard. message: '' + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Το πλήκτρο Enter προσθέτει το κείμενο στο ιστορικό αυτόματης + συμπλήρωσης + + * Σε όλους τους όρους γίνεται διάκριση πεζών-κεφαλαίων + + * Όροι που διαχωρίζονται με διαστήματα μπορούν να ενωθούν + + * Αναζήτηση με βάση τα μεταδεδομένα: "Awesome Script" + "Description" + + * Αναζήτηση με βάση της ετικέτες: #tag1 #tag2 + + * Αναζήτηση με βάση το όνομα του script: name:"awesome name" + + * Αναζήτηση με βάση τον κώδικα του script: code:"awesome code" + + * Αντίστροφη αναζήτηση: !#tag2 !name:"unwanted" + + * Regular expressions: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? with space" +toggleInjection: + description: Label for a keyboard shortcut. + message: Εναλλαγή εφαρμογής (injection) userscript +trackEdits: + description: Button in a script installation dialog. + message: Παρακολούθηση αλλαγών από άλλες πηγές +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: >- + Κρατήστε αυτή τη σελίδα ανοικτή για να παρακολουθείτε τις αλλαγές που κάνετε + σε τοπικό αρχείο +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Ενημέρωση script ($1) +updateScript: + description: Button to update one script. + message: Ενημέρωση +updateScriptsAll: + description: Command/button to update all scripts. + message: Ενημέρωση όλων των scripts +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Ενημέρωση των script που εκτελούνται σε αυτήν την καρτέλα valueLabelKey: description: Label for key of a script value. - message: '' + message: Κλειδί (συμβολοσειρά) valueLabelValue: description: Label for value of a script value. message: Τιμή (σε μορφή JSON) valueLabelValueAll: description: Label for input of entire script value storage. message: Όλες οι τιμές (σε μορφή JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: Επισκεφτείτε την ιστοσελίδα + touched: false diff --git a/src/_locales/en/messages.yml b/src/_locales/en/messages.yml index a169f20acd..55a3f523d8 100644 --- a/src/_locales/en/messages.yml +++ b/src/_locales/en/messages.yml @@ -1,21 +1,37 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Apply buttonCancel: description: Cancel button on dialog. message: Cancel +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Check for updates + touched: false buttonClose: description: Button to close window. message: Close buttonConfirmInstallation: description: Button to confirm installation of a script. message: Confirm installation + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Confirm re-installation + touched: false buttonDisable: description: Button to disable a script. message: Disable +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: Download themes + touched: false buttonEdit: description: Button to edit a script. message: Edit buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: 'You can also Right-click, Ctrl-click, Wheel-click on the script name.' + message: You can also Right-click, Ctrl-click, Wheel-click on the script name. buttonEmptyRecycleBin: description: Button to empty the recycle bin. message: Empty recycle bin now! @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Reset +buttonResetSettings: + description: Button in settings page to reset all settings + message: Reset settings buttonRestore: description: Button to restore a removed script. message: Restore @@ -72,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Save & Close +buttonSaved: + description: Button text after saving. + message: Saved buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Show editor state buttonSupport: description: Button to open support page. message: Support page +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: Pull once to local +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: Push once to remote buttonUndo: description: Button to undo removement of a script. message: Undo - touched: false buttonUpdate: - description: Check a script for updates. - message: Check for updates + description: Button to update a script. + message: Update buttonUpdateAll: description: Check all scripts for updates. message: Check all for updates + touched: false buttonVacuum: description: Button to vacuum extension data. message: Vacuum database @@ -97,14 +125,31 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Vacuuming data... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Auto-update is disabled for this script! + Click OK to update it anyway. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Modifications are not saved! Click OK to discard them or cancel to stay. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Revert all changes made to the database (importing, updating, editing, + customizing) descBlacklist: - description: HTML Description for the global blacklist. - message: URL matched in this list will not be injected by scripts. + description: Description for the global injection blacklist. + message: Injection blacklist (scripts won't run in the matching sites) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Network blacklist (scripts won't connect to the matching sites and their + cookies) descCustomCSS: description: Description of custom CSS section. message: >- @@ -117,11 +162,39 @@ descEditorOptions: {"indentUnit":2, "smartIndent":true} however note that some of them may not work in Violentmonkey. See full list. To use a custom CodeMirror theme, - specify its file name like "theme": "3024-day" here and paste - the actual theme's CSS in "Custom - Style" input below. + rel="noopener noreferrer">full list. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Boolean options may be toggled by double-clicking on the value: + true = enabled, false = disabled. Numeric options + ending with Delay, Interval, Rate, + Time are in milliseconds. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Nonstandard options: autocompleteOnTyping is a delay in + milliseconds to display autocomplete hints after typing (0 = + disable), killTrailingSpaceOnSave automatically removes + trailing spaces in each line when saving, showTrailingSpace + shows the trailing spaces as dots. +descScriptTemplate: + description: Description of script template section. + message: >- + Supported variables: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + with MomentJS-compatible format, for example <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: group +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: hide +disabledScriptsSelector: + description: Label of the option. + message: 'Disabled scripts:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: show editHelpDocumention: description: Label in the editor help tab for the documentation link. message: 'Documentation on userscript metadata block and GM API:' @@ -158,64 +231,106 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: All + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: Show/edit the entire script value storage -editValueCancel: - description: Button to cancel modification of a script value. - message: Cancel -editValueSave: - description: Button to save modification of a script value. - message: Save extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: An open source userscript manager that supports a lot of browsers extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: Blacklisted in Violentmonkey's settings failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) - message: |- + message: >- Violentmonkey cannot run userscripts in this page - (common examples: browser UI or an extension) + + (common examples: browser UI, an extension, blocked via policies, a search + engine site in Opera) +failureReasonRestarted: + description: Shown in the popup. + message: Violentmonkey was restarted. Please reload the tab to run userscripts. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + There are several methods to install or track edits in a local file: <1> + Drag'n'drop the file into an open Violentmonkey page or popup, including + this one. <2> Install a local HTTP server for this file and open it via + http://localhost. <3> Enable "Allow access to file URLs" in details for + Violentmonkey in chrome://extensions page. This is dangerous because any + userscript may read any local file. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: alphabetical order + message: alphabetical filterExecutionOrder: description: Label for option to sort scripts in execution order. - message: execution order + message: execution filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: last update time +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: last visit time +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: The last time you visited any site targeted by this script. filterScopeAll: description: Option in dashboard's search scope filter. message: All + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Code + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: Name + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: size genericError: description: Label for generic error. message: Error genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: 'off' genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: 'on' genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: use global setting +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + "#" patterns work only when initially opening the site or reloading the tab: + $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Recycle Bin +helpForLocalFile: + description: Label of the checkbox. + message: Show the instruction for drag'n'dropped local scripts +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: for $1 matching scripts +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: |- + A sandboxed script cannot change these global properties: $1. + To fix the script either add `@grant none` to disable the sandbox, + or use `unsafeWindow` instead of `window`. hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Input URL:' @@ -228,15 +343,16 @@ hintUseDownloadURL: hintVacuum: description: Hint for vacuuming data. message: Discard the redundancy and try to reload the missing resources in cache. +install: + description: Label for button to install a script. + message: Install installFrom: description: Label for button to install script from a userscript site. message: Install from $1 installOptionClose: description: Option to close confirm window after installation. message: Close after installation -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Track local file before this window is closed + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -263,12 +379,23 @@ labelAutoReloadCurrentTabDisabled: Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: Current scripts will keep running until you reload the tab + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: 'Check for script updates every $1 day(s), use 0 to disable' + message: Check for script updates every $1 day(s), use 0 to disable +labelBackup: + description: Label of the import/export section in settings. + message: Backup + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Backup and maintenance labelBadge: description: Label for option to show number on badge. - message: 'Display on badge: ' + message: 'Badge:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Badge colors: ' labelBadgeNone: description: Option to display nothing on badge. message: none @@ -293,12 +420,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Data Export + touched: false labelDataImport: description: Section title of data import. message: Data Import + touched: false labelDonate: description: Label of link to donate page. message: Donate + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'Download URL:' @@ -311,6 +441,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: Editor +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: enabled scripts only labelExclude: description: Label of @exclude rules. message: '@exclude rules' @@ -323,12 +456,19 @@ labelExportScriptData: labelExposeStatus: description: Option in advanced settings. message: 'Expose installed version on userscript catalog sites: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Alternative $1 mode in Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + An alternative injection method for scripts since Firefox + 59, faster than the default mode. Similarly to "Synchronous page mode" it + also increases memory consumption, so you may want to disable it if your + scripts work correctly without this option. labelFeedback: description: Label of link to feedback page. message: Feedback -labelFilterSort: - description: Label for sort filter. - message: Sort by $1 labelGeneral: description: Label for general settings. message: General @@ -341,6 +481,9 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'Homepage URL:' +labelIconURL: + description: Label for the input. + message: 'Icon URL:' labelImportScriptData: description: Option to import script data along with scripts. message: Import script data @@ -351,8 +494,10 @@ labelInclude: description: Label of @include rules. message: '@include rules' labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Default injection mode: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Injection mode: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: Installing script @@ -371,10 +516,12 @@ labelMatch: labelName: description: Label of script name. message: 'Name:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Run in frames: ' labelNoName: description: Text as the name of a script when no @name is assigned. message: No Name - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: No script is found. @@ -390,12 +537,14 @@ labelNotifyUpdates: labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: ignore per-script notification ("settings" tab in editor) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Sort scripts in popup by $1 labelPrivacyPolicy: description: Label of link to privacy policy message: Privacy Policy +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Re-installing script labelRelated: description: Label of related links. message: 'Related links: ' @@ -423,6 +572,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Settings +labelShowOrder: + description: Label for option in dashboard -> script list + message: Show execution order positions +labelShowVisited: + description: Label for option in dashboard -> script list + message: Show last visit time labelSync: description: Label for sync options. message: Sync @@ -435,6 +590,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Authorizing +labelSyncAutomatically: + description: Label for option to sync automatically. + message: Sync automatically labelSyncDisabled: description: Label for option to disable sync service. message: None @@ -460,6 +618,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: 'Username: ' +labelTags: + description: Label for custom tags. + message: 'Tags (space separated):' +labelTheme: + description: Label for the visual theme option. + message: 'Theme: ' labelTranslator: description: Label of translator. message: 'Translator: ' @@ -475,6 +639,26 @@ labelViewSingleColumn: labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. message: Table view +labelWidth: + description: Width. + message: 'Width:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Synchronous $1 mode +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Enable only if you have a script that needs to run before the page starts + loading and currently it's running too late. Just like Instant injection + mode in Tampermonkey, this option is using the deprecated synchronous XHR, + so in Chrome/Chromium you'll see warnings in devtools console, although you + can safely ignore them as the adverse affects are negligible in this case. + You can hide the warnings for good by right-clicking one. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (except incognito and sites with disabled cookies) lastSync: description: Label for last sync timestamp. message: Last sync at $1 @@ -505,6 +689,11 @@ menuExcludeHint: To apply changes to all other tabs please reload them manually. Use "Settings" tab of the editor for more flexibility. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Feedback menuFindScripts: description: Menu item to find scripts for a site. message: Find scripts for this site @@ -512,8 +701,13 @@ menuInjectionFailed: description: Injection error. message: Could not inject some scripts. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: Retry in "auto" mode +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Matched disabled scripts menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. message: Sub-frames only scripts @@ -524,14 +718,19 @@ menuNewScript: description: Menu item to create a new script. message: Create a new script menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Scripts disabled menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Scripts enabled msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Checking for updates... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Click to open MomentJS documentation. Allowed tokens: $1. Use [square + brackets] to protect literal text. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -583,11 +782,25 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Loading dependencies... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Missing required resources. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. - message: Script namespace conflicts! Please modify @name and @namespace. + message: >- + A script like this is already installed. + + Please either use a different @name and @namespace here or edit that script + instead. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: A script with the same @name and @namespace is already installed. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -596,21 +809,36 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: No update found. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Error updating scripts. Click to open them. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Please re-save or reinstall these script(s):' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: ' (code is the same)' msgSavedBlacklist: description: Message shown when blacklist are saved. message: Blacklist updated. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Custom style is updated. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Editor options are updated. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: Custom script template is updated. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Script [$1] is updated!' + message: Script [$1] is updated! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: Show/hide @@ -623,12 +851,27 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Initializing error! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Not authorized yet. msgSyncReady: description: Message shown when sync will start soon. message: Sync will start soon... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Sync in progress... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Syntax error? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + The script didn't exist or didn't match the URL when the page loaded, which + happens in Single-Page Application sites like fb or instagram that are using + fake navigation. You can reload the tab to run the script. To fix the script + use @match for the entire site and then detect changes using + MutationObserver or window.navigation API. msgUpdated: description: Message shown when a script is updated/reinstalled. message: Script updated. @@ -649,15 +892,90 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: Hide omnibox +optionPopup: + description: Label of the popup menu section in settings. + message: Popup menu and icon optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: Enabled first +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Group disabled scripts + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Group by @run-at stage optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: Hide disabled + message: Hide disabled scripts + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Show disabled scripts + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Show enabled scripts first +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'UI theme: ' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: automatic +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: dark +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: light +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Update +popupSettings: + description: Item inside the popup's "⋮" menu + message: Popup settings +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Right-click: popup settings' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Read-only +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Auto-updated script is read-only unless you disable updates or allow + editing. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Allow edits +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (next update will overwrite them) +reinstall: + description: Button to reinstall a script + message: Reinstall +reloadTab: + description: Label of action to reload the tab + message: Reload tab +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + If enabled, the active tab will be reloaded when changes are detected and + this script matches the tab's URL. +removeAllScripts: + description: Button to remove all scripts + message: Remove all scripts + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: >- + Move all scripts to "Recycle Bin"? + + It will be emptied automatically after a while. You can also open it and + empty it explicitly. + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Case sensitive @@ -673,14 +991,78 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Settings -titleScriptUpdated: - description: Notification title for script updates. - message: Update +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Reload page without userscripts +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + You disabled userscripts for this page. To run them, reload or navigate the + tab. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Sort order:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Stop tracking +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Normal badge color +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: Badge color when the site is non-injectable (blacklisted or unsupported) titleSearchHint: description: Hover title for search icon in dashboard. message: |- * key adds the text to autocomplete history * RegExp syntax is supported: /re/ and /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Enter key adds the text to autocomplete history + + * All conditions are case-insensitive + + * Space separated conditions can be combined + + * Search by metadata: "Awesome Script" "Description" + + * Search by tags: #tag1 #tag2 + + * Search by script name: name:"awesome name" + + * Search by script code: code:"awesome code" + + * Negative search: !#tag2 !name:"unwanted" + + * Regular expressions: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? with space" +toggleInjection: + description: Label for a keyboard shortcut. + message: Toggle userscript injection +trackEdits: + description: Button in a script installation dialog. + message: Track external edits +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: Keep this page open to track your edits in local file +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Update scripts ($1) +updateScript: + description: Button to update one script. + message: Update +updateScriptsAll: + description: Command/button to update all scripts. + message: Update all scripts +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Update current tab's scripts valueLabelKey: description: Label for key of a script value. message: Key (string) @@ -690,6 +1072,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: All values (serialized as JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: Value (serialized as JSON or $1) +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: $1 string visitWebsite: description: Label for link to open Violentmonkey website. message: Visit Website + touched: false diff --git a/src/_locales/es/messages.yml b/src/_locales/es/messages.yml index d3669228d9..d1d6e0f316 100644 --- a/src/_locales/es/messages.yml +++ b/src/_locales/es/messages.yml @@ -1,548 +1,761 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Aplicar buttonCancel: description: Cancel button on dialog. - message: '' + message: Cancelar +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Comprobar actualizaciones + touched: false buttonClose: description: Button to close window. - message: '' + message: Cerrar buttonConfirmInstallation: description: Button to confirm installation of a script. - message: '' + message: Confirmar instalación + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Confirmar reinstalación + touched: false buttonDisable: description: Button to disable a script. + message: Desactivar +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. message: '' + touched: false buttonEdit: description: Button to edit a script. - message: '' + message: Editar buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: '' + message: >- + También puedes hacer clic con el botón derecho del ratón, con la tecla Ctrl + o con la rueda del ratón en el nombre del script. buttonEmptyRecycleBin: description: Button to empty the recycle bin. - message: '' + message: ¡Vaciar la papelera de reciclaje ahora! buttonEnable: description: Button to enable a script. - message: '' + message: Activar buttonExportData: description: Button to open the data export dialog. - message: '' + message: Exportar a zip buttonFilter: description: Button to show filters menu. message: '' touched: false buttonHome: description: Button to open homepage. - message: '' + message: Página principal buttonImportData: description: Button to choose a file for data import. - message: '' + message: Importar desde zip buttonInstallFromURL: description: Button to ask for URL of a user script. - message: '' + message: Instalar desde URL buttonInstallOptions: description: Button to show options of installation confirm page. message: '' touched: false buttonNew: description: Button to create a new script. - message: '' + message: Nuevo buttonOK: description: OK button on dialog. - message: '' + message: OK buttonRecycleBin: description: Button to list scripts in recycle bin. - message: '' + message: Papelera de reciclaje buttonRemove: description: Button to remove a script. - message: '' + message: Eliminar buttonReplace: description: Button to replace the current match. - message: '' + message: Reemplazar buttonReplaceAll: description: Button to replace all matches. - message: '' + message: Todo buttonReset: description: Button to reset to default values. - message: '' + message: Restablecer +buttonResetSettings: + description: Button in settings page to reset all settings + message: Restablecer configuración buttonRestore: description: Button to restore a removed script. - message: '' + message: Restaurar buttonSave: description: Button to save modifications of a script. - message: '' + message: Guardar buttonSaveClose: description: Button to save modifications of a script and then close the editing page. + message: Guardar y cerrar +buttonSaved: + description: Button text after saving. message: '' buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. - message: '' + message: Mostrar estado del editor buttonSupport: description: Button to open support page. + message: Página de soporte +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once message: '' buttonUndo: description: Button to undo removement of a script. message: '' - touched: false buttonUpdate: - description: Check a script for updates. - message: '' + description: Button to update a script. + message: Actualización buttonUpdateAll: description: Check all scripts for updates. - message: '' + message: Buscar todas las actualizaciones + touched: false buttonVacuum: description: Button to vacuum extension data. - message: '' + message: Vaciar base de datos buttonVacuumed: description: Message shown when data is vacuumed. - message: '' + message: Datos vaciados buttonVacuuming: description: Message shown when data vacuum is in progress. + message: Vaciando datos... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. message: '' confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. + message: |- + ¡Las modificaciones no se han guardado! + Haz clic en OK para descartarlas o en Cancelar para matenerlas. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. message: '' descBlacklist: - description: HTML Description for the global blacklist. + description: Description for the global injection blacklist. + message: Las URLs que coincidan en esta lista no serán inyectadas por los scripts. +descBlacklistNet: + description: Description for the global network blacklist. message: '' descCustomCSS: description: Description of custom CSS section. - message: '' + message: >- + CSS personalizado para la página de opciones y la página de instalación de + scripts. Si no estás seguro de para qué sirve, por favor, no lo edites. descEditorOptions: description: Description of editor options JSON section. - message: '' + message: >- + Opciones personalizadas para CodeMirror y complementos en objetos JSON como + {"indentUnit":2, "smartIndent":true} sin embargo ten en cuenta + que algunas de ellas pueden no funcionar en Violentmonkey. Ver lista completa. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: '' +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: '' +descScriptTemplate: + description: Description of script template section. + message: >- + Variables compatibles: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + con un formato compatible con MomentJS, por ejemplo <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: agrupar +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: ocultar +disabledScriptsSelector: + description: Label of the option. + message: 'Scripts deshabilitados:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: mostrar editHelpDocumention: description: Label in the editor help tab for the documentation link. - message: '' + message: >- + Documentación sobre el bloque de metadatos de userscript y la API de + GM: editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. - message: '' + message: 'Atajos de teclado:' editHowToHint: description: The text of the how-to link in the editor header. - message: '' + message: ¿Usas otro editor? editLabelMeta: description: Metadata section in settings tab of script editor. - message: '' + message: Metadatos personalizados editLabelSettings: description: Settings section in settings tab of script editor. - message: '' + message: Configuración del script editLongLine: description: Shown in the editor in lines that were cut due to being too long - message: '' + message: La línea es demasiado larga editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long - message: '' + message: >- + Esta línea es demasiado larga, y el texto estará colapsado para evitar + retrasos en la edición. + + Puedes ajustar el límite en la configuración avanzada, por ejemplo: + + "maxDisplayLength": 20000 editNavCode: description: Label of code tab in script editor. - message: '' + message: Código editNavSettings: description: Label of settings tab in script editor. - message: '' + message: Configuración editNavValues: description: Label of values tab in script editor. - message: '' + message: Valores editValueAll: description: Button to show/edit the entire script value storage. message: '' + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: '' -editValueSave: - description: Button to save modification of a script value. - message: '' + message: Mostrar/editar todos los valores de almacenamiento del script extDescription: - description: 'Description for this extension, will be displayed in web store' - message: '' + description: Description for this extension, will be displayed in web store + message: >- + Un administrador de userscripts de código abierto que admite muchos + navegadores extName: description: Name of this extension. - message: '' + message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' - message: '' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: En la lista negra en la configuración de Violentmonkey failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) + message: >- + Violentmonkey no puede ejecutar usercripts en esta página + + (ejemplos comunes: interfaz del navegador, una extensión, bloqueado a través + de políticas, un sitio de motor de búsqueda en Opera) +failureReasonRestarted: + description: Shown in the popup. + message: '' +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. message: '' filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: '' + message: orden alfabético filterExecutionOrder: description: Label for option to sort scripts in execution order. - message: '' + message: orden de ejecución filterLastUpdateOrder: description: Label for option to sort scripts by last update time. + message: tiempo de actualización más reciente +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. message: '' filterScopeAll: description: Option in dashboard's search scope filter. - message: '' + message: Todo + touched: false filterScopeCode: description: Option in dashboard's search scope filter. - message: '' + message: Código + touched: false filterScopeName: description: Option in dashboard's search scope filter. - message: '' + message: Nombre + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: tamaño genericError: description: Label for generic error. - message: '' + message: Error genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' - message: '' + description: To indicate something is turned off or disabled, similar to "no". + message: desactivado genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' - message: '' + description: To indicate something is turned on or enabled, similar to "yes". + message: activado genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. + message: usar configuración global +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). message: '' headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. + message: Papelera de reciclaje +helpForLocalFile: + description: Label of the checkbox. + message: '' +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: '' +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. - message: '' + message: 'Introduce la URL:' hintRecycleBin: description: Hint for recycle bin. - message: '' + message: Los scripts eliminados se enumeran aquí y se mantendrán durante 7 días. hintUseDownloadURL: description: Shown as a place holder for @updateURL when it is not assigned - message: '' + message: Usar @downloadURL hintVacuum: description: Hint for vacuuming data. + message: >- + Descarta la redundancia e intenta volver a cargar los recursos que faltan en + la memoria caché. +install: + description: Label for button to install a script. message: '' installFrom: description: Label for button to install script from a userscript site. - message: '' + message: Instalar desde $1 installOptionClose: description: Option to close confirm window after installation. - message: '' -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: '' + message: Cerrar después de la instalación + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before window is closed. - message: '' + message: >- + La pestaña del archivo fuente debe mantenerse abierta en Firefox 68 y + versiones posteriores. labelAbout: description: Label shown on top of the about page. message: '' touched: false labelAdvanced: description: Label for button to show advanced settings. - message: '' + message: Avanzado labelAllowUpdate: description: Option to allow checking updates for a script. - message: '' + message: Permitir actualización labelAuthor: description: Label of author shown in the details of a script. - message: '' + message: 'Autor:' labelAutoReloadCurrentTab: description: Option to reload current tab after a script is switched on or off from menu. - message: '' + message: >- + Recargar la pestaña actual después de activar/desactivar un script desde el + menú labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. - message: '' + message: Los scripts actuales seguirán ejecutándose hasta que recargues la pestaña + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: '' + message: Buscar actualizaciones de los scripts cada $1 día(s), usa 0 para desactivar +labelBackup: + description: Label of the import/export section in settings. + message: Respaldo + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Copia de seguridad y mantenimiento labelBadge: description: Label for option to show number on badge. - message: '' + message: 'Mostrar en el icono de la extensión:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Colores del icono:' labelBadgeNone: description: Option to display nothing on badge. - message: '' + message: nada labelBadgeTotal: description: Option to display total number of running scripts on badge. - message: '' + message: número de scripts en ejecución labelBadgeUnique: description: Option to display number of unique running scripts on badge. - message: '' + message: número de scripts en ejecución únicos labelBlacklist: description: Label for global blacklist settings in security section. - message: '' + message: Lista negra labelContributors: description: Label for link to contributors. - message: '' + message: Colaboradores labelCurrentLang: description: Label of current language. - message: '' + message: 'Idioma actual:' labelCustomCSS: description: Label for custom CSS section. - message: '' + message: Estilo personalizado labelDataExport: description: Section title of data export. message: '' + touched: false labelDataImport: description: Section title of data import. message: '' + touched: false labelDonate: description: Label of link to donate page. - message: '' + message: Donar + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. - message: '' + message: 'URL de descarga:' labelEditValue: description: Label shown in the panel to edit a script value. - message: '' + message: Editar el valor del script labelEditValueAll: description: Label shown in the panel to edit the entire script value storage. - message: '' + message: Editar almacenamiento del script labelEditor: description: Label for Editor settings - message: '' + message: Editor +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: solo scripts activos labelExclude: description: Label of @exclude rules. - message: '' + message: Reglas @exclude labelExcludeMatch: description: Label of @exclude-match rules. - message: '' + message: Reglas @exclude-match labelExportScriptData: description: Option to export script data along with scripts. - message: '' + message: Exportar datos del script labelExposeStatus: description: Option in advanced settings. + message: 'Exponer la versión instalada en los sitios del catálogo de userscripts: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: '' +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. message: '' labelFeedback: description: Label of link to feedback page. - message: '' -labelFilterSort: - description: Label for sort filter. - message: '' + message: Comentarios labelGeneral: description: Label for general settings. - message: '' + message: General labelHelpTranslate: description: Label for link to localization guide in about tab - message: '' + message: Ayudar con la traducción labelHomepage: description: Label for home page in about tab. - message: '' + message: Página principal labelHomepageURL: description: Label of script @homepageURL in custom meta data. + message: 'URL de la página principal:' +labelIconURL: + description: Label for the input. message: '' labelImportScriptData: description: Option to import script data along with scripts. - message: '' + message: Importar datos del script labelImportSettings: description: Label for option to import settings from zip file. - message: '' + message: Importar configuración labelInclude: description: Label of @include rules. - message: '' + message: Reglas @include labelInjectionMode: - description: Label for default option to inject scripts. - message: '' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Modo de inyección: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. - message: '' + message: Instalando script labelKeepOriginal: description: Option to keep the original match or ignore rules. - message: '' + message: Mantener el original labelLastUpdatedAt: description: Label shown on last updated time. - message: '' + message: Última actualización en $1 labelLineNumber: description: Label for line number jumper. - message: '' + message: 'Número de línea: ' labelMatch: description: Label of @match rules. - message: '' + message: Reglas @match labelName: description: Label of script name. - message: '' + message: 'Nombre:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Ejecutar en frames:' labelNoName: description: Text as the name of a script when no @name is assigned. - message: '' - touched: false + message: Sin nombre labelNoSearchScripts: description: Message shown when no script is found in search results. - message: '' + message: No se encontraron scripts. labelNotifyThisUpdated: description: >- A per-script option in editor to enable notification when this script is updated. The text follows "Allow update" checkbox option so it's like a continuation of the phrase. - message: '' + message: ', después notificar:' labelNotifyUpdates: description: Option to show notification when script is updated. - message: '' + message: Notificar actualizaciones de scripts labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. - message: '' -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: '' + message: ignorar notificación por script (pestaña "configuración" en el editor) labelPrivacyPolicy: description: Label of link to privacy policy - message: '' + message: Política de privacidad +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Reinstalar el script labelRelated: description: Label of related links. - message: '' + message: 'Enlaces relacionados:' labelRemovedAt: description: Label for the time when the script is removed. - message: '' + message: Eliminado en $1 labelReplace: description: Label for replace input in search box. - message: '' + message: 'Remplazar con:' labelRunAt: description: Label of script @run-at properties in custom meta data. - message: '' + message: 'Ejecutar en:' labelRunAtDefault: description: Shown when custom @run-at is not assigned. - message: '' + message: (Predeterminado) labelScriptTemplate: description: Label for custom script template. - message: '' + message: Plantilla de script personalizada labelSearch: description: Label for search input in search box. - message: '' + message: 'Buscar:' labelSearchScript: description: Placeholder for script search box. - message: '' + message: Buscar scripts... labelSettings: description: Label shown on the top of settings page + message: Configuración +labelShowOrder: + description: Label for option in dashboard -> script list + message: Mostrar posiciones de las órdenes de ejecución +labelShowVisited: + description: Label for option in dashboard -> script list message: '' labelSync: description: Label for sync options. - message: '' + message: Sincronizar labelSyncAnonymous: description: Label for using anonymous account. - message: '' + message: Usar cuenta anónima labelSyncAuthorize: description: Label for button to authorize a service. - message: '' + message: Autorizar labelSyncAuthorizing: description: Label for button when authorization is in progress. + message: Autorizando +labelSyncAutomatically: + description: Label for option to sync automatically. message: '' labelSyncDisabled: description: Label for option to disable sync service. - message: '' + message: Nada labelSyncPassword: description: Label for input to hold password. - message: '' + message: 'Contraseña:' labelSyncReauthorize: description: Option to reauthorize sync service when expired. message: '' touched: false labelSyncRevoke: description: Label for button to revoke authorization for a service. - message: '' + message: Revocar labelSyncScriptStatus: description: Label for option to sync script status. - message: '' + message: Estado de la sincronización de scripts labelSyncServerUrl: description: Label for input to hold server URL. - message: '' + message: 'URL del servidor:' labelSyncService: description: Label for sync service select. - message: '' + message: Sincronizar a labelSyncUsername: description: Label for input to hold username. + message: 'Nombre de usuario:' +labelTags: + description: Label for custom tags. message: '' +labelTheme: + description: Label for the visual theme option. + message: 'Tema:' labelTranslator: description: Label of translator. message: '' touched: false labelUpdateURL: description: Label of script @updateURL in custom meta data. - message: '' + message: 'URL de actualización:' labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: Columna única labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. + message: Vista de tabla +labelWidth: + description: Width. + message: 'Ancho:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Modo $1 sincrónico +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Actívalo solo si tienes un script que necesita ejecutarse antes de que la + página comience a cargar y actualmente se está ejecutando demasiado tarde. + Al igual que el modo de inyección instantánea en Tampermonkey, esta opción + utiliza el obsoleto XHR sincrónico, por lo que en Chrome/Chromium verás + advertencias en la consola devtools, aunque puedes ignorarlas con seguridad, + ya que los efectos adversos son insignificantes en este caso. Puedes ocultar + las advertencias para siempre haciendo clic con el botón derecho del ratón + en una de ellas. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. message: '' lastSync: description: Label for last sync timestamp. - message: '' + message: Última sincronización en $1 learnBlacklist: description: Refers to a link to introduce blacklist patterns. - message: '' + message: Aprende más sobre los patrones de la lista negra. learnInjectionMode: description: Refers to a link to introduce injection modes. - message: '' + message: Aprende más sobre los modos de inyección. menuCommands: description: Menu item to list script commands. message: '' touched: false menuDashboard: description: Label for menu item to open dashboard. - message: '' + message: Abrir panel de control menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: '' + message: Excluir... menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. - message: '' + message: >- + La pestaña actual se recargará automáticamente si activaste esta opción en + la configuración general. + + Para aplicar los cambios a todas las demás pestañas, recargarlas + manualmente. + + Utiliza la pestaña "Configuración" del editor para mayor flexibilidad. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Comentarios menuFindScripts: description: Menu item to find scripts for a site. - message: '' + message: Buscar scripts para este sitio menuInjectionFailed: description: Injection error. - message: '' + message: No se pudieron inyectar algunos scripts. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' - message: '' + description: Injection error fix, shown in case the default mode is "page". + message: Reintentar en modo "automático" +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Scripts coincidentes deshabilitados menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. - message: '' + message: Solo scripts sub-frames menuMatchedScripts: description: Label for menu listing matched scripts. - message: '' + message: Scripts coincidentes menuNewScript: description: Menu item to create a new script. - message: '' + message: Crear un nuevo script menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' - message: '' + description: Menu item showing the status of Violentmonkey, when disabled. + message: Scripts deshabilitados menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' - message: '' + description: Menu item showing the status of Violentmonkey, when enabled. + message: Scripts activos msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. - message: '' + message: Buscando actualizaciones... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Haz clic para abrir la documentación de MomentJS. Tokens permitidos: $1. + Utiliza [corchetes] para proteger el texto literal. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of the script. - message: '' + message: ¡Error al obtener el recurso! msgErrorFetchingScript: description: Message shown when Violentmonkey fails fetching a new version of the script. - message: '' + message: ¡Error al obtener el script! msgErrorFetchingUpdateInfo: description: Message shown when Violentmonkey fails fetching version data of the script. - message: '' + message: No se pudo obtener la información de actualización. msgErrorLoadingData: description: >- Message shown on confirm page when the script to be installed cannot be loaded. - message: '' + message: Error al cargar los datos del script. msgErrorLoadingDependency: description: Message shown when not all requirements are loaded successfully. - message: '' + message: Error al cargar dependencias. msgImported: description: >- Message shown after import. There is an argument referring to the count of scripts imported. - message: '' + message: $1 elemento(s) importado(s). msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito window. - message: '' + message: >- + Los cambios que realices en el modo incógnito también se aplican a tu perfil + principal. msgInstalled: description: Message shown when a script is installed. - message: '' + message: Script instalado. msgInvalidScript: description: Message shown when script is invalid. - message: '' + message: ¡Script inválido! msgLoadedData: description: >- Message shown in the confirm page when a javascript file to be installed is @@ -555,115 +768,278 @@ msgLoading: touched: false msgLoadingData: description: Message shown on confirm page when the script to be installed is loading. - message: '' + message: Cargando datos del script... msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. - message: '' + message: Cargando dependencias... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Recursos necesarios faltantes. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. - message: '' + message: >- + Un script como este ya está instalado. + + Por favor, utilice un @name y @namespace diferente aquí o, en su lugar, + edite ese script. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Un script con el mismo @name y @namespace ya está instalado. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @downloadURL is provided. - message: '' + message: Nueva versión encontrada. msgNoUpdate: description: Message shown when there is no new version of a script. - message: '' + message: No encontraron actualizaciones. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Error al actualizar scripts. Haz clic para abrirlos. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Por favor, vuelva a guardar o reinstalar esto(s) script(s):' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (el código es el mismo) msgSavedBlacklist: description: Message shown when blacklist are saved. - message: '' + message: Lista negra actualizada. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. - message: '' + message: El estilo personalizado está actualizado. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. - message: '' + message: Las opciones del editor están actualizadas. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. - message: '' + message: La plantilla de script personalizado está actualizada. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: '' + message: ¡El script [$1] está actualizado! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff - message: '' + message: Mostrar/ocultar msgSyncError: description: Message shown when sync failed. - message: '' + message: ¡Error de sincronización! msgSyncInit: description: Message shown when sync service is initializing. - message: '' + message: Inicializando... msgSyncInitError: description: Message shown when sync fails in initialization. - message: '' + message: ¡Error de inicialización! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: No autorizado aún. msgSyncReady: description: Message shown when sync will start soon. - message: '' + message: La sincronización iniciará pronto... + touched: false msgSyncing: description: Message shown when sync is in progress. + message: Sincronización en progreso... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: '' +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. message: '' msgUpdated: description: Message shown when a script is updated/reinstalled. - message: '' + message: Script actualizado. msgUpdating: description: Message shown when a new version of script is being fetched. - message: '' + message: Actualizando... noValues: description: Label shown when there is no value for current script. - message: '' + message: No se almacena ningún valor optionEditorWindow: description: Label for the option in settings - message: '' + message: Abrir editor desde una ventana emergente en una ventana nueva optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support onBoundsChanged - message: '' + message: >- + La posición de la ventana del editor solo se recordará al redimensionar o + guardar optionEditorWindowSimple: description: Label for the editor window type - message: '' + message: Ocultar omnibox +optionPopup: + description: Label of the popup menu section in settings. + message: Menú emergente e icono optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. - message: '' + message: Activos primero +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Agrupar scripts desactivados + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Agrupar por etapa @run-at optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: '' + message: Esconder scripts desactivados + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Mostrar scripts desactivados + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. + message: Mostrar scripts activos primero +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: Tema de la interfaz +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: automático +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: oscuro +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: claro +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Actualizar +popupSettings: + description: Item inside the popup's "⋮" menu + message: '' +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: '' +readonlyNote: + description: Warning when trying to type in the editor. + message: '' +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Permitir ediciones +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (la próxima actualización las sobrescribirá) +reinstall: + description: Button to reinstall a script + message: '' +reloadTab: + description: Label of action to reload the tab + message: '' +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. message: '' +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search - message: '' + message: 'Distinguir entre mayúsculas y minúsculas ' searchUseRegex: description: Option to perform a regular expression search - message: '' + message: Usar expresiones regulares (Regex) sideMenuAbout: description: 'Side menu: About' - message: '' + message: Acerca de sideMenuInstalled: description: 'Side menu: Installed scripts' - message: '' + message: Scripts instalados sideMenuSettings: description: 'Side menu: Settings' - message: '' -titleScriptUpdated: - description: Notification title for script updates. - message: '' + message: Configuración +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: '' +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: '' +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: '' +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Color normal del icono +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Color del icono cuando el sitio no es inyectable (en lista negra o no + soportado) titleSearchHint: description: Hover title for search icon in dashboard. + message: |- + * La clave añade el texto al historial de autocompletar + * Soporte para sintaxis RegExp: /re/ y /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: '' +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: '' +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: '' +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: '' +updateScript: + description: Button to update one script. + message: Actualizar +updateScriptsAll: + description: Command/button to update all scripts. + message: Actualizar todos los scripts +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. message: '' valueLabelKey: description: Label for key of a script value. - message: '' + message: Clave (cadena) valueLabelValue: description: Label for value of a script value. - message: '' + message: Valor (serializado como JSON) valueLabelValueAll: description: Label for input of entire script value storage. + message: Todos los valores (serializados como JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' message: '' visitWebsite: description: Label for link to open Violentmonkey website. - message: '' + message: Visitar Sitio Web + touched: false diff --git a/src/_locales/es_419/messages.yml b/src/_locales/es_419/messages.yml index 54bdd1827b..b7af58064d 100644 --- a/src/_locales/es_419/messages.yml +++ b/src/_locales/es_419/messages.yml @@ -1,23 +1,39 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Aplicar buttonCancel: description: Cancel button on dialog. message: Cancelar +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Buscar actualizaciones + touched: false buttonClose: description: Button to close window. message: Cerrar buttonConfirmInstallation: description: Button to confirm installation of a script. message: Confirmar instalación + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Confirmar reinstalación + touched: false buttonDisable: description: Button to disable a script. message: Deshabilitar +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: Descargar temas + touched: false buttonEdit: description: Button to edit a script. message: Editar buttonEditClickHint: description: Tooltip for the Edit button in popup. message: >- - También puedes hacer clic derecho, clic en Ctrl, clic de la rueda en el - nombre del script. + También puedes hacer clic derecho, Ctrl + clic o clic con la rueda del mouse + en el nombre del script. buttonEmptyRecycleBin: description: Button to empty the recycle bin. message: ¡Vaciar papelera de reciclaje ahora! @@ -65,6 +81,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Restablecer +buttonResetSettings: + description: Button in settings page to reset all settings + message: Restablecer configuración buttonRestore: description: Button to restore a removed script. message: Restaurar @@ -74,22 +93,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Guardar y cerrar +buttonSaved: + description: Button text after saving. + message: Guardado buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Mostrar estado del editor buttonSupport: description: Button to open support page. message: Página de soporte +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: Extraer una vez a local +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: Enviar una vez a remoto buttonUndo: description: Button to undo removement of a script. message: Deshacer - touched: false buttonUpdate: - description: Check a script for updates. - message: Buscar actualizaciones + description: Button to update a script. + message: Actualizar buttonUpdateAll: description: Check all scripts for updates. message: Buscar todas las actualizaciones + touched: false buttonVacuum: description: Button to vacuum extension data. message: Vaciar base de datos @@ -99,14 +127,33 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Vaciando datos... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + ¡La actualización automática está deshabilitada para este script! + Haz clic en Aceptar para actualizarlo de todos modos. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- ¡Las modificaciones no fueron guardadas! Haga clic en Aceptar para descartarlas o en Cancelar para mantenerlas. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Revertir todos los cambios realizados en la base de datos (importaciones, + actualizaciones, ediciones y personalizaciones) descBlacklist: - description: HTML Description for the global blacklist. - message: Las URLs que coincidan en esta lista no serán inyectadas por los scripts. + description: Description for the global injection blacklist. + message: >- + Lista negra de inyección (los scripts no se ejecutarán en los sitios que + coincidan) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Lista negra de red (los scripts no se conectarán a los sitios que coincidan + ni a sus cookies) descCustomCSS: description: Description of custom CSS section. message: >- @@ -119,12 +166,40 @@ descEditorOptions: {"indentUnit":2, "smartIndent":true} sin embargo ten en cuenta que algunas de ellas pueden no funcionar en Violentmonkey. Ver lista completa. Para usar un tema - personalizado de CodeMirror, especifica su nombre de archivo como - "theme": "3024-day" aquí y pega el CSS del tema actual en la - entrada "Estilo personalizado" de abajo. + rel="noopener noreferrer">lista completa. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Las opciones booleanas pueden alternarse haciendo doble clic sobre el valor: + true = habilitado, false = deshabilitado. Las + opciones numéricas que terminan en Delay, + Interval, Rate, Time están expresadas + en milisegundos. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Opciones no estándar: autocompleteOnTyping es un retardo en + milisegundos para mostrar sugerencias de autocompletado después de escribir + (0 = deshabilitar), killTrailingSpaceOnSave + elimina automáticamente los espacios al final de cada línea al guardar, + showTrailingSpace muestra los espacios finales como puntos. +descScriptTemplate: + description: Description of script template section. + message: >- + Variables compatibles: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + con un formato compatible con MomentJS, por ejemplo <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: agrupar +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: ocultar +disabledScriptsSelector: + description: Label of the option. + message: 'Scripts deshabilitados:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: mostrar editHelpDocumention: description: Label in the editor help tab for the documentation link. message: >- @@ -166,17 +241,12 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: Todo + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: Mostrar/editar todo los valores de almacenamiento del script -editValueCancel: - description: Button to cancel modification of a script value. - message: Cancelar -editValueSave: - description: Button to save modification of a script value. - message: Guardar + message: Mostrar/editar todos los valores almacenados del script extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: >- Un administrador de userscripts de código abierto que admite muchos navegadores @@ -184,48 +254,96 @@ extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: En la lista negra en la configuración de Violentmonkey failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) - message: |- - Violentmonkey no puede ejecutar userscripts en esta página - (casos comunes: interfaz de usuario del navegador o una extensión) + message: >- + Violentmonkey no puede ejecutar usercripts en esta página + + (ejemplos comunes: interfaz del navegador, una extensión, bloqueado a través + de políticas, un sitio de motor de búsqueda en Opera) +failureReasonRestarted: + description: Shown in the popup. + message: Violentmonkey se reinició. Recarga la pestaña para ejecutar los userscripts. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Hay varios métodos para instalar o rastrear ediciones en un archivo local: + <1> Arrastra y suelta el archivo en una página abierta o menú emergente de + Violentmonkey (incluida esta). <2> Instala un servidor HTTP local para ese + archivo y ábrelo desde http://localhost. <3> Activa la opción “Permitir el + acceso a las URL del archivo” en los detalles de Violentmonkey en la página + chrome://extensions. Esto es peligroso, ya que cualquier userscript podría + leer cualquier archivo local. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: orden alfabético + message: alfabéticamente filterExecutionOrder: description: Label for option to sort scripts in execution order. message: orden de ejecución filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: tiempo de actualización más reciente +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: hora de la última visita +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: La última vez que visitaste algún sitio al que apunta este script. filterScopeAll: description: Option in dashboard's search scope filter. message: Todo + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Código + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: Nombre + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: tamaño genericError: description: Label for generic error. message: Error genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: deshabilitado genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: habilitado genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: usar configuración global +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + Los patrones con "#" funcionan únicamente al abrir el sitio por primera vez + o al recargar la pestaña: $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Papelera de reciclaje +helpForLocalFile: + description: Label of the checkbox. + message: Mostrar las instrucciones para scripts locales arrastrados y soltados +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: para $1 scripts coincidentes +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: |- + Un script en sandbox no puede cambiar estas propiedades globales: $1. + Para corregir el script, agrega `@grant none` para desactivar el sandbox, + o usa `unsafeWindow` en lugar de `window`. hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Ingrese la URL:' @@ -240,15 +358,16 @@ hintVacuum: message: >- Descarte la redundancia e intente volver a cargar los recursos que faltan en la memoria caché. +install: + description: Label for button to install a script. + message: Instalar installFrom: description: Label for button to install script from a userscript site. message: Instalar desde $1 installOptionClose: description: Option to close confirm window after installation. message: Cerrar después de la instalación -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Rastrear el archivo local antes de que se cierre esta ventana + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -279,14 +398,25 @@ labelAutoReloadCurrentTabDisabled: Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: Los scripts actuales seguirán ejecutándose hasta que recargues la pestaña + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates message: >- Buscar actualizaciones de los scripts cada $1 día(s), use 0 para deshabilitar +labelBackup: + description: Label of the import/export section in settings. + message: Respaldo + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Copia de seguridad y mantenimiento labelBadge: description: Label for option to show number on badge. - message: 'Mostrar en el icono de la extensión: ' + message: 'Icono:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Colores del icono: ' labelBadgeNone: description: Option to display nothing on badge. message: nada @@ -311,12 +441,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Exportar datos + touched: false labelDataImport: description: Section title of data import. message: Importar datos + touched: false labelDonate: description: Label of link to donate page. message: Donar + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'URL de descarga:' @@ -329,6 +462,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: Editor +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: solo scripts habilitados labelExclude: description: Label of @exclude rules. message: Reglas @exclude @@ -341,12 +477,19 @@ labelExportScriptData: labelExposeStatus: description: Option in advanced settings. message: 'Exponer la versión instalada en los sitios del catálogo de userscripts: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Modo $1 alternativo en Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Un método de inyección alternativo para scripts disponible + desde Firefox 59, más rápido que el modo predeterminado. Al igual que el + "modo page sincrónico", también incrementa el uso de memoria, por lo que + podrías desactivarlo si tus scripts funcionan correctamente sin esta opción. labelFeedback: description: Label of link to feedback page. message: Feedback -labelFilterSort: - description: Label for sort filter. - message: Ordenar por $1 labelGeneral: description: Label for general settings. message: General @@ -359,6 +502,9 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'URL de la página principal:' +labelIconURL: + description: Label for the input. + message: 'URL del ícono:' labelImportScriptData: description: Option to import script data along with scripts. message: Importar datos del script @@ -369,8 +515,10 @@ labelInclude: description: Label of @include rules. message: Reglas @include labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Modo de inyección predeterminado: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Modo de inyección: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: Instalando script @@ -389,10 +537,12 @@ labelMatch: labelName: description: Label of script name. message: 'Nombre:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Ejecutar en frames:' labelNoName: description: Text as the name of a script when no @name is assigned. message: Sin nombre - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: No se encontraron scripts. @@ -408,12 +558,14 @@ labelNotifyUpdates: labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: ignorar notificación por script (pestaña "configuración" en el editor) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Ordenar los scripts en ventana emergente por $1 labelPrivacyPolicy: description: Label of link to privacy policy message: Política de privacidad +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Reinstalando script labelRelated: description: Label of related links. message: 'Enlaces relacionados: ' @@ -441,6 +593,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Configuración +labelShowOrder: + description: Label for option in dashboard -> script list + message: Mostrar las posiciones del orden de ejecución +labelShowVisited: + description: Label for option in dashboard -> script list + message: Mostrar hora de última visita labelSync: description: Label for sync options. message: Sincronizar @@ -453,6 +611,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Autorizando +labelSyncAutomatically: + description: Label for option to sync automatically. + message: Sincronizar automáticamente labelSyncDisabled: description: Label for option to disable sync service. message: Nada @@ -478,6 +639,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: 'Nombre de usuario: ' +labelTags: + description: Label for custom tags. + message: 'Etiquetas (separadas por un espacio):' +labelTheme: + description: Label for the visual theme option. + message: 'Tema:' labelTranslator: description: Label of translator. message: 'Traductor: ' @@ -489,10 +656,32 @@ labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: Columna única + message: Una sola columna labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. message: Vista de tabla +labelWidth: + description: Width. + message: 'Ancho:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Modo $1 sincrónico +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Habilítalo solo si tienes un script que necesita ejecutarse antes de que la + página comience a cargar y actualmente se está ejecutando demasiado tarde. + Al igual que el modo de inyección instantánea en Tampermonkey, esta opción + utiliza el obsoleto XHR sincrónico, por lo que en Chrome/Chromium verás + advertencias en la consola devtools, aunque puedes ignorarlas con seguridad + ya que los efectos adversos son insignificantes en este caso. Puedes ocultar + las advertencias para siempre haciendo clic con el botón derecho del ratón + en una de ellas. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (excepto en modo incógnito y en sitios con cookies deshabilitadas) lastSync: description: Label for last sync timestamp. message: Última sincronización en $1 @@ -520,10 +709,14 @@ menuExcludeHint: La pestaña actual se recargará automáticamente si habilitaste esta opción en la configuración general. - Para aplicar los cambios a todas las demás pestañas, recargarlas - manualmente. + Para aplicar los cambios en las demás pestañas, recárgalas manualmente. Utiliza la pestaña "Configuración" del editor para mayor flexibilidad. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Comentario menuFindScripts: description: Menu item to find scripts for a site. message: Buscar scripts para este sitio @@ -531,8 +724,13 @@ menuInjectionFailed: description: Injection error. message: No se pudieron inyectar algunos scripts. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: Reintentar en modo "automático" +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Scripts coincidentes deshabilitados menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. message: Solo scripts sub-frames @@ -543,14 +741,19 @@ menuNewScript: description: Menu item to create a new script. message: Crear un nuevo script menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Scripts deshabilitados menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Scripts habilitados msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Buscar actualizaciones... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Haz clic para abrir la documentación de MomentJS. Tokens permitidos: $1. + Utiliza [corchetes] para proteger el texto literal. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -604,6 +807,11 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Cargando dependencias... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Faltan recursos necesarios. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent @@ -611,6 +819,11 @@ msgNamespaceConflict: message: >- ¡Conflictos de espacio de nombres del script! Por favor, modifique @name y @namespace. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Ya hay un script instalado con el mismo @name y @namespace. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -619,21 +832,36 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: No se han encontrado actualizaciones. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Error al actualizar los scripts. Haz clic para abrirlos. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Por favor, vuelva a guardar o reinstalar esto(s) script(s):' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: ' (el código es el mismo)' msgSavedBlacklist: description: Message shown when blacklist are saved. message: Lista negra actualizada. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: El estilo personalizado está actualizado. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Las opciones del editor están actualizadas. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: La plantilla de script personalizado está actualizada. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: '¡El script [$1] está actualizado!' + message: ¡El script [$1] está actualizado! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: Mostrar/ocultar @@ -646,12 +874,27 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: ¡Error al iniciar la sincronización! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Todavía no está autorizado. msgSyncReady: description: Message shown when sync will start soon. message: La sincronización iniciará pronto... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Sincronización en progreso... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: ¿Error de sintaxis? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + El script no existía o no coincidía con la URL cuando la página se cargó, lo + cual ocurre en sitios de Single-Page Application como Facebook o Instagram + que utilizan navegación simulada. Puedes recargar la pestaña para ejecutar + el script. Para corregir el script usa @match para todo el sitio y luego + detecta los cambios utilizando MutationObserver o la API window.navigation. msgUpdated: description: Message shown when a script is updated/reinstalled. message: Script actualizado. @@ -663,7 +906,7 @@ noValues: message: No se almacena ningún valor optionEditorWindow: description: Label for the option in settings - message: Abrir editor desde ventana emergente en una ventana nueva + message: Abrir el editor desde el menú emergente en una nueva ventana optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support @@ -674,15 +917,86 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: Ocultar omnibox +optionPopup: + description: Label of the popup menu section in settings. + message: Menú emergente e icono optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: Habilitados primero +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Grupo de scripts deshabilitados + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Agrupar por etapa @run-at optionPopupHideDisabled: description: Option to hide disabled scripts in popup. message: Ocultar deshabilitados + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Mostrar scripts deshabilitados + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Mostrar scripts habilitados primero +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Tema de la interfaz:' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: automático +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: oscuro +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: claro +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Actualizar +popupSettings: + description: Item inside the popup's "⋮" menu + message: Configuración del menú emergente +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Clic derecho: configuración del menú emergente' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Solo lectura +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Este script con actualizaciones automáticas es de solo lectura, a menos que + desactives las actualizaciones o habilites la edición. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Permitir ediciones +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (la próxima actualización las sobrescribirá) +reinstall: + description: Button to reinstall a script + message: Reinstalar +reloadTab: + description: Label of action to reload the tab + message: Recargar página +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Si está habilitado, se recargará la pestaña actual cuando se detecten + cambios y este script coincida con la URL de la pestaña. +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Distinguir mayúsculas y minúsculas @@ -698,14 +1012,80 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Configuración -titleScriptUpdated: - description: Notification title for script updates. - message: Actualización +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Recargar la página sin userscripts +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Has desactivado los userscripts para esta página. Para ejecutarlos, recarga + o navega en la pestaña. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Ordenar por:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Dejar de rastrear +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Color normal del icono +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Color del icono cuando el sitio no es inyectable (en lista negra o sin + soporte) titleSearchHint: description: Hover title for search icon in dashboard. message: |- * La clave añade texto para auto-completar el historial * Soporte para sintaxis RegExp: /re/ y /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * La tecla Enter agrega el texto al historial de autocompletado + + * Todas las condiciones no distinguen mayúsculas/minúsculas + + * Las condiciones separadas por espacios se pueden combinar + + * Búsqueda por metadatos: "Awesome Script" "Description" + + * Búsqueda por etiquetas: #tag1 #tag2 + + * Búsqueda por nombre del script: name:"awesome name" + + * Búsqueda por código del script: code:"awesome code" + + * Búsqueda excluyendo resultados: !#tag2 !name:"unwanted" + + * Expresiones regulares: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? with space" +toggleInjection: + description: Label for a keyboard shortcut. + message: Activar o desactivar inyección de userscripts +trackEdits: + description: Button in a script installation dialog. + message: Rastrear ediciones externas +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: Mantén esta página abierta para rastrear tus ediciones en el archivo local +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Actualizar scripts ($1) +updateScript: + description: Button to update one script. + message: Actualizar +updateScriptsAll: + description: Command/button to update all scripts. + message: Actualizar todos los scripts +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Actualizar scripts de la pestaña actual valueLabelKey: description: Label for key of a script value. message: Clave (cadena) @@ -715,6 +1095,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: Todos los valores (serializados como JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: Valor (serializado como JSON o $1) +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: $1 cadena visitWebsite: description: Label for link to open Violentmonkey website. message: Visitar sitio web + touched: false diff --git a/src/_locales/fi/messages.yml b/src/_locales/fi/messages.yml index 2baaec5ca0..2a6addf7cd 100644 --- a/src/_locales/fi/messages.yml +++ b/src/_locales/fi/messages.yml @@ -1,15 +1,31 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: '' buttonCancel: description: Cancel button on dialog. message: Peruuta +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Tarkista päivitykset + touched: false buttonClose: description: Button to close window. message: Sulje buttonConfirmInstallation: description: Button to confirm installation of a script. message: Vahvista asennus + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: '' + touched: false buttonDisable: description: Button to disable a script. message: Poista käytöstä +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false buttonEdit: description: Button to edit a script. message: Muokkaa @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: '' +buttonResetSettings: + description: Button in settings page to reset all settings + message: '' buttonRestore: description: Button to restore a removed script. message: '' @@ -72,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Tallenna & sulje +buttonSaved: + description: Button text after saving. + message: '' buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: '' buttonSupport: description: Button to open support page. message: Tukisivu +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. message: Peruuta - touched: false buttonUpdate: - description: Check a script for updates. - message: Tarkista päivitykset + description: Button to update a script. + message: Päivitys buttonUpdateAll: description: Check all scripts for updates. message: Tarkista kaikki päivitykset + touched: false buttonVacuum: description: Button to vacuum extension data. message: Imuroi tietokanta @@ -97,14 +125,25 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Imuroidaan dataa... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: '' confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Muokkauksia ei ole tallennettu! Klikkaa OK hylätäksesi ne tai Peruuta pysyäksesi tässä. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: '' descBlacklist: - description: HTML Description for the global blacklist. + description: Description for the global injection blacklist. message: Listan osoitteissa ei ajeta skriptejä. +descBlacklistNet: + description: Description for the global network blacklist. + message: '' descCustomCSS: description: Description of custom CSS section. message: >- @@ -113,6 +152,27 @@ descCustomCSS: descEditorOptions: description: Description of editor options JSON section. message: '' +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: '' +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: '' +descScriptTemplate: + description: Description of script template section. + message: '' +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: '' +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: '' +disabledScriptsSelector: + description: Label of the option. + message: '' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: '' editHelpDocumention: description: Label in the editor help tab for the documentation link. message: '' @@ -146,29 +206,30 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: '' + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: Peruuta -editValueSave: - description: Button to save modification of a script value. - message: Tallenna extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: Lisää selaimiin tuen käyttäjäskripteille. extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: '' failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) message: '' +failureReasonRestarted: + description: Shown in the popup. + message: '' +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: '' filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. message: aakkosjärjestys @@ -178,30 +239,58 @@ filterExecutionOrder: filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: '' +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: '' + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: '' + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: '' + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: '' genericError: description: Label for generic error. message: '' genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: '' genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: '' genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: '' +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: '' headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: '' +helpForLocalFile: + description: Label of the checkbox. + message: '' +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: '' +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Syötä osoite:' @@ -214,15 +303,16 @@ hintUseDownloadURL: hintVacuum: description: Hint for vacuuming data. message: Hylkää redundanssi ja yritä ladata puuttuvat resurssit välimuistiin. +install: + description: Label for button to install a script. + message: '' installFrom: description: Label for button to install script from a userscript site. message: Asenna lähteestä $1 installOptionClose: description: Option to close confirm window after installation. message: Sulje asennuksen jälkeen -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Seuraa paikallista tiedostoa ennen ikkunan sulkemista + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -249,12 +339,23 @@ labelAutoReloadCurrentTabDisabled: Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: '' + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates message: '' +labelBackup: + description: Label of the import/export section in settings. + message: '' + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: '' labelBadge: description: Label for option to show number on badge. message: 'Näytä lätkässä: ' +labelBadgeColors: + description: Label for option group to set badge colors. + message: '' labelBadgeNone: description: Option to display nothing on badge. message: ei mitään @@ -279,12 +380,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Datan vienti + touched: false labelDataImport: description: Section title of data import. message: Datan tuonti + touched: false labelDonate: description: Label of link to donate page. message: Lahjoita + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'Latausosoite:' @@ -297,6 +401,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: Editori +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: '' labelExclude: description: Label of @exclude rules. message: '@exclude-säännöt' @@ -309,12 +416,15 @@ labelExportScriptData: labelExposeStatus: description: Option in advanced settings. message: '' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: '' +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: '' labelFeedback: description: Label of link to feedback page. message: Palaute -labelFilterSort: - description: Label for sort filter. - message: 'Järjestä $1:n mukaan' labelGeneral: description: Label for general settings. message: Yleiset @@ -327,6 +437,9 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'Kotisivun osoite:' +labelIconURL: + description: Label for the input. + message: '' labelImportScriptData: description: Option to import script data along with scripts. message: '' @@ -337,7 +450,9 @@ labelInclude: description: Label of @include rules. message: '@include-säännöt' labelInjectionMode: - description: Label for default option to inject scripts. + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. message: '' labelInstall: description: Shown in the title of the confirm page while trying to install a script. @@ -357,10 +472,12 @@ labelMatch: labelName: description: Label of script name. message: 'Nimi:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: '' labelNoName: description: Text as the name of a script when no @name is assigned. message: Ei nimeä - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: Skriptejä ei löytynyt. @@ -376,12 +493,14 @@ labelNotifyUpdates: labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: '' -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: '' labelPrivacyPolicy: description: Label of link to privacy policy message: '' +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: '' labelRelated: description: Label of related links. message: 'Linkkejä aiheesta: ' @@ -409,6 +528,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Asetukset +labelShowOrder: + description: Label for option in dashboard -> script list + message: '' +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Synkronointi @@ -421,6 +546,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Vahvistetaan +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: Ei mihinkään @@ -446,6 +574,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: '' +labelTags: + description: Label for custom tags. + message: '' +labelTheme: + description: Label for the visual theme option. + message: '' labelTranslator: description: Label of translator. message: 'Kääntäjä: ' @@ -461,6 +595,20 @@ labelViewSingleColumn: labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. message: '' +labelWidth: + description: Width. + message: '' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: '' +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: '' +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: '' lastSync: description: Label for last sync timestamp. message: Synkronoitu viimeksi $1 @@ -485,6 +633,11 @@ menuExclude: menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. message: '' +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Palaute menuFindScripts: description: Menu item to find scripts for a site. message: Etsi skriptejä tälle sivustolle @@ -492,7 +645,12 @@ menuInjectionFailed: description: Injection error. message: '' menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". + message: '' +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. message: '' menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. @@ -504,14 +662,17 @@ menuNewScript: description: Menu item to create a new script. message: '' menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Skriptit poissa käytöstä menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Skriptit käytössä msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Tarkistetaan päivityksiä... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: '' msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -563,6 +724,11 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Ladataan riippuvuuksia... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: '' msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent @@ -570,6 +736,11 @@ msgNamespaceConflict: message: >- Konflikti skriptien nimiavaruudessa! Ole hyvä ja muokkaa @name:a and @namespace:a. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: '' msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -578,21 +749,36 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Päivitystä ei löytynyt. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: '' +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: '' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: '' msgSavedBlacklist: description: Message shown when blacklist are saved. message: Musta lista päivitetty. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Oma tyyli päivitetty. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: '' + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: Oma skriptipohja on päivitetty. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Skripti [$1] on päivitetty!' + message: Skripti [$1] on päivitetty! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: '' @@ -605,12 +791,22 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Alustusvirhe! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: '' msgSyncReady: description: Message shown when sync will start soon. message: Synkronointi alkaa pian... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Synkronointi käynnissä... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: '' +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: '' msgUpdated: description: Message shown when a script is updated/reinstalled. message: Skripti päivitetty. @@ -631,15 +827,82 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: '' +optionPopup: + description: Label of the popup menu section in settings. + message: '' optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: '' +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: '' + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: '' optionPopupHideDisabled: description: Option to hide disabled scripts in popup. message: '' + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: '' + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Näytä käytössä olevat skriptit ensin +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: '' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: '' +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Päivitys +popupSettings: + description: Item inside the popup's "⋮" menu + message: '' +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: '' +readonlyNote: + description: Warning when trying to type in the editor. + message: '' +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: '' +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: '' +reinstall: + description: Button to reinstall a script + message: '' +reloadTab: + description: Label of action to reload the tab + message: '' +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: '' +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Merkkikokoriippuvainen @@ -655,12 +918,56 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Asetukset -titleScriptUpdated: - description: Notification title for script updates. - message: Päivitys +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: '' +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: '' +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: '' +titleBadgeColor: + description: Tooltip for option to set badge color. + message: '' +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: '' titleSearchHint: description: Hover title for search icon in dashboard. message: '' + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: '' +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: '' +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: '' +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: '' +updateScript: + description: Button to update one script. + message: Päivitys +updateScriptsAll: + description: Command/button to update all scripts. + message: '' +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: '' valueLabelKey: description: Label for key of a script value. message: Avain (merkkijono) @@ -670,6 +977,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: '' +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: '' + touched: false diff --git a/src/_locales/fr/messages.yml b/src/_locales/fr/messages.yml index 080d61a6c4..aa9691041d 100644 --- a/src/_locales/fr/messages.yml +++ b/src/_locales/fr/messages.yml @@ -1,26 +1,42 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Appliquer buttonCancel: description: Cancel button on dialog. message: Annuler +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Vérifier les mises à jour + touched: false buttonClose: description: Button to close window. message: Fermer buttonConfirmInstallation: description: Button to confirm installation of a script. message: Confirmer l'installation + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Confirmer la réinstallation + touched: false buttonDisable: description: Button to disable a script. message: Désactiver +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false buttonEdit: description: Button to edit a script. - message: Éditer + message: Modifier buttonEditClickHint: description: Tooltip for the Edit button in popup. message: >- - Vous pouvez faire un clic droit, ctrl-clic ou clic molette sur le nom du - script. + Vous pouvez également faire un clic droit, ctrl-clic ou clic molette sur le + nom du script. buttonEmptyRecycleBin: description: Button to empty the recycle bin. - message: Vider la corbeille + message: Vider la corbeille maintenant! buttonEnable: description: Button to enable a script. message: Activer @@ -65,31 +81,43 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Réinitialiser +buttonResetSettings: + description: Button in settings page to reset all settings + message: Réinitialiser la configuration buttonRestore: description: Button to restore a removed script. - message: Remettre le script + message: Restaurer buttonSave: description: Button to save modifications of a script. message: Sauvegarder buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Sauvegarder et fermer +buttonSaved: + description: Button text after saving. + message: Sauvegardé buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Afficher la configuration de l’éditeur buttonSupport: description: Button to open support page. message: Page de support +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. message: Annuler - touched: false buttonUpdate: - description: Check a script for updates. - message: Vérifier les mises à jour + description: Button to update a script. + message: Mettre à jour buttonUpdateAll: description: Check all scripts for updates. message: Vérifier toutes les mises à jour + touched: false buttonVacuum: description: Button to vacuum extension data. message: Nettoyer la base de données @@ -99,18 +127,37 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Nettoyage des données… +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Les mises à jour automatiques sont désactivées pour ce script ! + Cliquez sur OK pour le mettre à jour quand même. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Les modifications n’ont pas étés sauvegardées ! Cliquez sur OK pour les rejeter ou annuler pour rester +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Annuler toutes les modifications apportées à la base de données + (importation, mise à jour, édition, personnalisation) descBlacklist: - description: HTML Description for the global blacklist. - message: Les scripts se seront pas injectés dans les URL filtrées par cette liste. + description: Description for the global injection blacklist. + message: >- + Liste noire d'injection (les scripts ne seront pas exécutés dans les sites + correspondants) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Liste noire du réseau (les scripts ne se connecteront pas aux sites + correspondants et à leurs cookies) descCustomCSS: description: Description of custom CSS section. message: >- - CSS personnalisée pour la page d’options et pour la page d’installation de + CSS personnalisé pour la page d’options et pour la page d’installation de script. Si vous n'êtes pas certain·e de ce que vous faites, veuillez ne pas l’éditer. descEditorOptions: @@ -118,7 +165,7 @@ descEditorOptions: message: >- Options personnalisées pour CodeMirror et extensions dans un objet JSON comme{"indentUnit":2, "smartIndent":true}, cependant notez que - certaines peuvent ne pas marcher dans Violentmonkey. Voir laliste complète. Pour utiliser un thème CodeMirror, spécifiez son nom de fichier comme "theme": @@ -126,12 +173,46 @@ descEditorOptions: href="https://github.com/codemirror/CodeMirror/tree/master/theme" target="_blank" rel="noopener noreferrer">CSS du thème en lui-même dans le champ « Style personnalisé » ci-dessous. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Les options booléennes peuvent être activées en double-cliquant sur la + valeur : true = activé, false = désactivé. Les + options numériques se terminant par Delay, + Interval, Rate, Time sont exprimées + en millisecondes. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Options non standard : autocompleteOnTyping est un délai + exprimé en millisecondes pour l'affichage des indices de la saisie + semi-automatique après la frappe (0 = désactivé), + killTrailingSpaceOnSave supprime automatiquement les espaces de + fin de ligne lors de l'enregistrement, showTrailingSpace + affiche les espaces de fin de ligne sous forme de points. +descScriptTemplate: + description: Description of script template section. + message: >- + Variables supportées : <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + avec un format compatible avec MomentJS, par exemple <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: grouper +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: masquer +disabledScriptsSelector: + description: Label of the option. + message: 'Scripts désactivés :' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: montrer editHelpDocumention: description: Label in the editor help tab for the documentation link. message: 'Documentation on userscript metadata block and GM API:' editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. - message: "Raccourcis clavier\_:" + message: "Raccourcis clavier\_: " editHowToHint: description: The text of the how-to link in the editor header. message: "Vous utilisez un autre éditeur de texte\_?" @@ -165,65 +246,106 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: Tout + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: Montrer/éditer toutes les données stockées du script -editValueCancel: - description: Button to cancel modification of a script value. - message: Annuler -editValueSave: - description: Button to save modification of a script value. - message: Sauvegarder extDescription: - description: 'Description for this extension, will be displayed in web store' - message: Un gestionnaire de scipts utilisateurs qui supporte beaucoup de navigateurs + description: Description for this extension, will be displayed in web store + message: Un gestionnaire de scripts utilisateur qui supporte beaucoup de navigateurs extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: Sur la liste noire des paramètres de Violentmonkey failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) - message: "Violentmonkey ne peut pas lancer de scripts utilisateurs\nsur cette page (exemples courants\_: page du navigateur\nou d’une extension)" + message: "Violentmonkey ne peut pas lancer de scripts utilisateur\nsur cette page (exemples courants\_: page du navigateur\nou d’une extension)" +failureReasonRestarted: + description: Shown in the popup. + message: >- + Violentmonkey a été redémarré. Veuillez recharger l'onglet pour exécuter les + scripts utilisateur. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Il existe plusieurs méthodes pour installer ou suivre les modifications dans + un fichier local : <1> Glissez-déposez le fichier dans une page ou une + fenêtre contextuelle ouverte de Violentmonkey, y compris celle-ci. <2> + Installez un serveur HTTP local pour ce fichier et ouvrez-le via + http://localhost. <3> Activez l'option « Autoriser l'accès aux URL de + fichiers » dans les détails de Violentmonkey sur la page + chrome://extensions. Ceci est dangereux car n'importe quel script + utilisateur peut lire n'importe quel fichier local. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: ordre alphabétique + message: alphabétique filterExecutionOrder: description: Label for option to sort scripts in execution order. - message: ordre d’exécution + message: exécution filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: dernière mise à jour +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: Tous + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Code + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: Nom + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: taille genericError: description: Label for generic error. message: Erreur genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: 'off' genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: 'on' genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: utiliser le paramètre global +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + Les motifs « # » ne fonctionnent que lors de l'ouverture initiale du site ou + du rechargement de l'onglet : $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Corbeille +helpForLocalFile: + description: Label of the checkbox. + message: Montrer les instructions pour les scripts locaux glissés-déposés +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: pour les $1 scripts correspondants +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. - message: "URL source\_:" + message: "URL source\_: " hintRecycleBin: description: Hint for recycle bin. message: Les scripts supprimés sont listés ici et conservés pendant 7 jours. @@ -235,15 +357,16 @@ hintVacuum: message: >- Éliminer la redondance et essayer de recharger les ressources manquantes dans le cache. +install: + description: Label for button to install a script. + message: Installer installFrom: description: Label for button to install script from a userscript site. message: Installer depuis $1 installOptionClose: description: Option to close confirm window after installation. message: Fermer après l’installation -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Vérifier le fichier local avant de fermer la fenêtre + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -273,15 +396,28 @@ labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. - message: '' + message: >- + Les scripts actuels vont continuer à s'exécuter jusqu'à ce que vous + rafraîchissez l'onglet + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates message: >- Vérifier les mises à jour de scripts tous les $1 jour(s), renseigner 0 pour désactiver +labelBackup: + description: Label of the import/export section in settings. + message: Backup + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Sauvegarde et maintenance labelBadge: description: Label for option to show number on badge. - message: "Montrer sur le badge\_: " + message: 'Badge :' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Couleurs des badges\_: ' labelBadgeNone: description: Option to display nothing on badge. message: Rien @@ -306,15 +442,18 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Export de données + touched: false labelDataImport: description: Section title of data import. message: Import de données + touched: false labelDonate: description: Label of link to donate page. message: Dons + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. - message: "URL de téléchargement\_:" + message: "URL de téléchargement\_: " labelEditValue: description: Label shown in the panel to edit a script value. message: Éditer la valeur du script @@ -324,6 +463,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: Éditeur +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: Uniquement les scripts activés labelExclude: description: Label of @exclude rules. message: Règles @exclude @@ -335,13 +477,21 @@ labelExportScriptData: message: Exporter les données du script labelExposeStatus: description: Option in advanced settings. - message: "Exposer la version installée sur les sites de scripts utilisateurs\_: $1" + message: "Exposer la version installée sur les sites de scripts utilisateur\_: $1" +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Mode alternatif $1 sur Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Méthode d'injection alternative pour les scripts depuis + Firefox 59, offrant de meilleures performances que le mode par défaut. Tout + comme le mode "page synchrone", cette méthode consomme plus de mémoire, vous + voudrez donc peut-être désactiver cette option si vos scripts fonctionnent + mieux sans cette option. labelFeedback: description: Label of link to feedback page. message: Retour d’expérience -labelFilterSort: - description: Label for sort filter. - message: Trier par $1 labelGeneral: description: Label for general settings. message: Général @@ -353,19 +503,24 @@ labelHomepage: message: Page d’accueil labelHomepageURL: description: Label of script @homepageURL in custom meta data. - message: "URL de la page d’accueil\_:" + message: "URL de la page d’accueil\_: " +labelIconURL: + description: Label for the input. + message: 'URL de l''icône :' labelImportScriptData: description: Option to import script data along with scripts. - message: '' + message: Importer les données du script labelImportSettings: description: Label for option to import settings from zip file. - message: Importer les paramètres + message: Importer une configuration labelInclude: description: Label of @include rules. message: Règles @include labelInjectionMode: - description: Label for default option to inject scripts. - message: "Mode d’injection par défaut\_:" + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: "Mode d’injection\_: " labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: Installation du script @@ -383,11 +538,13 @@ labelMatch: message: Règles @match labelName: description: Label of script name. - message: "Nom\_:" + message: "Nom\_: " +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Exécution dans les fenêtres :' labelNoName: description: Text as the name of a script when no @name is assigned. message: Pas de nom - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: Pas de script trouvé @@ -396,7 +553,7 @@ labelNotifyThisUpdated: A per-script option in editor to enable notification when this script is updated. The text follows "Allow update" checkbox option so it's like a continuation of the phrase. - message: ", puis envoyer une notification\_:" + message: ", puis envoyer une notification\_: " labelNotifyUpdates: description: Option to show notification when script is updated. message: Notification de mise à jour de script @@ -405,12 +562,14 @@ labelNotifyUpdatesGlobal: message: >- ignorer le paramètre de notification par script (onglet "Configuration" dans l'éditeur) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Trier les scripts dans la popup par $1 labelPrivacyPolicy: description: Label of link to privacy policy message: Politique de confidentialité +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Réinstallation du script labelRelated: description: Label of related links. message: "Liens utiles\_: " @@ -438,6 +597,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Configuration +labelShowOrder: + description: Label for option in dashboard -> script list + message: Afficher les positions des ordres d'exécution +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Synchronisation @@ -450,6 +615,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Autorisation +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: Aucun @@ -475,21 +643,49 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: "Nom d’utilisateur\_: " +labelTags: + description: Label for custom tags. + message: 'Tags (séparés par des espaces) :' +labelTheme: + description: Label for the visual theme option. + message: 'Thème :' labelTranslator: description: Label of translator. message: 'Traducteur·ice·s: ' touched: false labelUpdateURL: description: Label of script @updateURL in custom meta data. - message: "URL de mise à jour\_:" + message: "URL de mise à jour\_: " labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: Colonne unique labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. - message: '' + message: Vue du mode tableau +labelWidth: + description: Width. + message: 'Largeur :' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Mode $1 synchrone +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Activez seulement si vous avez un script qui nécessite de se lancer avant + que la page commence à charger et qu'il se lance trop tard. Comme le mode + Instant Injection sur Tampermonkey, cette option utilisée le XHR synchrone + déprécié, donc dans Chrome/Chromium vous verrez des avertissements dans la + console des développeurs, bien que vous pouvez sans risques les ignorer car + les effets négatifs sont négligeables dans ce cas. Vous pouvez masquer + définitivement les avertissements en faisant un clic droit sur l'un d'entre + eux. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (sauf incognito et sites avec cookies désactivés) lastSync: description: Label for last sync timestamp. message: Dernière synchronisation le $1 @@ -510,10 +706,22 @@ menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: '' + message: Exclure... menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. - message: '' + message: >- + L'onglet actuel va se rafraichir automatiquement si vous avez activé cette + option dans les options générales. + + Pour appliquer les changements à tous les autres onglets merci de les + rafraichir manuellement. + + Utilisez l'onglet "Paramètres" de l'éditeur pour plus de flexibilité. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Retour d’expérience menuFindScripts: description: Menu item to find scripts for a site. message: Chercher des scripts pour ce site @@ -521,26 +729,36 @@ menuInjectionFailed: description: Injection error. message: Impossible d’injecter certains scripts. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: Réessayer en mode « auto » +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Correspond à un script désactivé menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. message: Scripts uniquement pour les sub-frames menuMatchedScripts: description: Label for menu listing matched scripts. - message: Scripts associés + message: 'Scripts associés ' menuNewScript: description: Menu item to create a new script. message: Créer un nouveau script menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Scripts désactivés menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Scripts activés msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Vérification des mises à jour… +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Cliquez pour ouvrir la documentation de MomentJS. Variables autorisées : $1. + Utilisez [les crochets] pour protéger le texte littéral. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -577,7 +795,7 @@ msgInstalled: message: Script installé. msgInvalidScript: description: Message shown when script is invalid. - message: "Script invalide\_!" + message: Script invalide! msgLoadedData: description: >- Message shown in the confirm page when a javascript file to be installed is @@ -594,11 +812,21 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Chargement des dépendances… ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Ressources requises manquantes. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. message: "Conflit d’espace de nom du script\_! Veuillez modifier @name et @namespace" +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Un autre script avec le même nom et le même namespace est déjà installé. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -607,24 +835,39 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Pas de mise à jour trouvée. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Erreur lors de la mise à jour des scripts. Cliquez pour les ouvrir. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Merci de réenregistrer ou de réinstaller ce(s) script(s) :' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (le code est identique) msgSavedBlacklist: description: Message shown when blacklist are saved. message: Liste noire mise à jour. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Style personnalisé mis à jour. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Les options de l’éditeur de texte sont mises à jour. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: Le modèle de script personnalisé a été mis à jour. + touched: false msgScriptUpdated: description: Notification message for script updates. message: "Script [$1] mis à jour\_!" msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff - message: '' + message: Afficher/masquer msgSyncError: description: Message shown when sync failed. message: "Erreur de synchronisation\_!" @@ -634,12 +877,28 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: "Erreur d’initialisation\_!" +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Pas encore autorisé. msgSyncReady: description: Message shown when sync will start soon. message: La synchronisation va bientôt démarrer… + touched: false msgSyncing: description: Message shown when sync is in progress. message: Synchronisation en cours… +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Erreur de syntaxe +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + Le script n'existait pas ou ne correspondait pas à l'URL lorsque la page a + été chargée, ce qui se produit sur les sites à page unique comme Facebook ou + Instagram qui utilisent une fausse navigation. Vous pouvez recharger + l'onglet pour exécuter le script. Pour corriger le script, utilisez @match + pour l'ensemble du site, puis détectez les changements à l'aide de + MutationObserver ou de l'API window.navigation. msgUpdated: description: Message shown when a script is updated/reinstalled. message: Script mis à jour. @@ -662,15 +921,87 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: Cacher l’omnibox +optionPopup: + description: Label of the popup menu section in settings. + message: Menu et icône popup optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: Scripts activés en premier +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Regrouper les scripts désactivés + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Grouper en fonction de l'étape d'exécution (@run-at) optionPopupHideDisabled: description: Option to hide disabled scripts in popup. message: Cacher les scripts désactivés + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Afficher les scripts désactivés. + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Montrer les scripts activés en premier +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Thème UI :' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: automatique +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: sombre +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: clair +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Mettre à jour +popupSettings: + description: Item inside the popup's "⋮" menu + message: Paramètres des fenêtres popup +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Clic droit : paramètres de la fenêtre popup' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Lecture seule +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Les scripts mis à jour automatiquement sont en lecture seule, sauf si vous + désactivez les mises à jour ou si vous autorisez la modification. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Autoriser les modifications +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (les prochaines mises à jour les écraseront) +reinstall: + description: Button to reinstall a script + message: Réinstaller +reloadTab: + description: Label of action to reload the tab + message: Recharger l'onglet +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Si cette option est activée, l'onglet actif sera rechargé lorsque des + changements seront détectés et que ce script correspondra à l'URL de + l'onglet. +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Sensible à la casse @@ -686,12 +1017,89 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Configuration -titleScriptUpdated: - description: Notification title for script updates. - message: Mise à jour +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Recharger la page sans script utilisateur +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Vous avez désactivé les scripts utilisateur pour cette page. Pour les + exécuter, rechargez la page ou naviguez dans l'onglet. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Ordre de tri :' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Arrêter le suivi +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Couleur normale du badge +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Couleur du badge lorsque le site est non-injectable (blacklisté ou non pris + en charge) titleSearchHint: description: Hover title for search icon in dashboard. message: "* La touche ajoute le texte à l'historique d'autocomplétion\n* La syntaxe pour les expressions régulières est supportée\_: /re/ et /re/flags" + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * La touche Entrée ajoute le texte à l'historique de + l'autocomplétion. + + + * Toutes les conditions sont insensibles à la casse + + + * Les conditions séparées par des espaces peuvent être combinées + + + * Recherche par métadonnées : "Awesome Script" "Description" + + + * Recherche par tags : #tag1 #tag2 + + + * Recherche par nom de script : name : "awesome name" + + + * Recherche par code du script : code : "awesome code" + + + * Recherche négative : !#tag2 !name : "unwanted" + + + * Expressions régulières : /\w+?/, /\w+?/gi, + name:/\w+?/, name+re :"\w+ ? avec espace" +toggleInjection: + description: Label for a keyboard shortcut. + message: Activer/désactiver l'injection de scripts utilisateur +trackEdits: + description: Button in a script installation dialog. + message: Suivre les modifications externes +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: >- + Gardez cette page ouverte pour suivre vos modifications dans le fichier + local. +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Mettre à jour les scripts ($1) +updateScript: + description: Button to update one script. + message: Mettre à jour +updateScriptsAll: + description: Command/button to update all scripts. + message: Mettre à jour tous les scripts +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Mettre à jour les scripts de l'onglet actuel valueLabelKey: description: Label for key of a script value. message: Clé (chaine de caractères) @@ -701,6 +1109,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: Toutes les valeurs (sérialisées en JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: Aller sur le site web + touched: false diff --git a/src/_locales/hr/messages.yml b/src/_locales/hr/messages.yml index 3c6df982c4..d97305a749 100644 --- a/src/_locales/hr/messages.yml +++ b/src/_locales/hr/messages.yml @@ -1,15 +1,31 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: '' buttonCancel: description: Cancel button on dialog. message: Otkaži +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Provjeri za obnove + touched: false buttonClose: description: Button to close window. message: Zatvori buttonConfirmInstallation: description: Button to confirm installation of a script. message: Potvrdi instalaciju + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: '' + touched: false buttonDisable: description: Button to disable a script. message: Onemogući +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false buttonEdit: description: Button to edit a script. message: Uredi @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: '' +buttonResetSettings: + description: Button in settings page to reset all settings + message: '' buttonRestore: description: Button to restore a removed script. message: '' @@ -72,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Sačuvaj & Zatvori +buttonSaved: + description: Button text after saving. + message: '' buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: '' buttonSupport: description: Button to open support page. message: '' +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. message: '' - touched: false buttonUpdate: - description: Check a script for updates. - message: Provjeri za obnove + description: Button to update a script. + message: Obnovi buttonUpdateAll: description: Check all scripts for updates. message: Provjeri obnove za sve skripte + touched: false buttonVacuum: description: Button to vacuum extension data. message: Vakuumiraj databazu @@ -97,14 +125,25 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Vakuumiranje podataka... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: '' confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Modifikacije nisu sačuvane! Klikni OK da ih odbaciš ili poništi kako bi ostao. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: '' descBlacklist: - description: HTML Description for the global blacklist. + description: Description for the global injection blacklist. message: Link koji se slaže s onim u ovoj listi neće biti ubačen u skripte. +descBlacklistNet: + description: Description for the global network blacklist. + message: '' descCustomCSS: description: Description of custom CSS section. message: >- @@ -113,6 +152,27 @@ descCustomCSS: descEditorOptions: description: Description of editor options JSON section. message: '' +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: '' +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: '' +descScriptTemplate: + description: Description of script template section. + message: '' +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: '' +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: '' +disabledScriptsSelector: + description: Label of the option. + message: '' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: '' editHelpDocumention: description: Label in the editor help tab for the documentation link. message: '' @@ -146,29 +206,30 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: '' + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: '' -editValueSave: - description: Button to save modification of a script value. - message: '' extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: Omogućava korištenje korisničkih skripti u web pregledniku. extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: '' failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) message: '' +failureReasonRestarted: + description: Shown in the popup. + message: '' +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: '' filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. message: '' @@ -178,30 +239,58 @@ filterExecutionOrder: filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: '' +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: '' + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: '' + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: '' + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: '' genericError: description: Label for generic error. message: '' genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: '' genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: '' genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: '' +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: '' headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: '' +helpForLocalFile: + description: Label of the checkbox. + message: '' +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: '' +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Input URL:' @@ -214,15 +303,16 @@ hintUseDownloadURL: hintVacuum: description: Hint for vacuuming data. message: Discard the redundancy i pokušaj ponovo učitati nedostajuće resurse u cache. +install: + description: Label for button to install a script. + message: '' installFrom: description: Label for button to install script from a userscript site. message: Instaliraj sa $1 installOptionClose: description: Option to close confirm window after installation. message: Zatvori nakon instalacije -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Track lokalni fajl prije no što se ovaj prozor zatvori + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -249,12 +339,23 @@ labelAutoReloadCurrentTabDisabled: Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: '' + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates message: '' +labelBackup: + description: Label of the import/export section in settings. + message: '' + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: '' labelBadge: description: Label for option to show number on badge. message: '' +labelBadgeColors: + description: Label for option group to set badge colors. + message: '' labelBadgeNone: description: Option to display nothing on badge. message: '' @@ -279,12 +380,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Izvoz Podataka + touched: false labelDataImport: description: Section title of data import. message: Uvoz Podataka + touched: false labelDonate: description: Label of link to donate page. message: Doniraj + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'Download URL:' @@ -297,6 +401,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: '' +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: '' labelExclude: description: Label of @exclude rules. message: '@exclude rules' @@ -309,12 +416,15 @@ labelExportScriptData: labelExposeStatus: description: Option in advanced settings. message: '' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: '' +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: '' labelFeedback: description: Label of link to feedback page. message: Feedback -labelFilterSort: - description: Label for sort filter. - message: '' labelGeneral: description: Label for general settings. message: Generalni @@ -327,6 +437,9 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'Homepage URL:' +labelIconURL: + description: Label for the input. + message: '' labelImportScriptData: description: Option to import script data along with scripts. message: '' @@ -337,7 +450,9 @@ labelInclude: description: Label of @include rules. message: '@include rules' labelInjectionMode: - description: Label for default option to inject scripts. + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. message: '' labelInstall: description: Shown in the title of the confirm page while trying to install a script. @@ -357,10 +472,12 @@ labelMatch: labelName: description: Label of script name. message: 'Ime:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: '' labelNoName: description: Text as the name of a script when no @name is assigned. message: Nema imena - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: Nijedna skripta nije nađena. @@ -376,12 +493,14 @@ labelNotifyUpdates: labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: '' -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: '' labelPrivacyPolicy: description: Label of link to privacy policy message: '' +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: '' labelRelated: description: Label of related links. message: 'Povezani linkovi: ' @@ -409,6 +528,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Postavke +labelShowOrder: + description: Label for option in dashboard -> script list + message: '' +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Sinkronizacija @@ -421,6 +546,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Autoriziram +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: Nijedan @@ -446,6 +574,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: '' +labelTags: + description: Label for custom tags. + message: '' +labelTheme: + description: Label for the visual theme option. + message: '' labelTranslator: description: Label of translator. message: 'Prevoditelj: ' @@ -461,6 +595,20 @@ labelViewSingleColumn: labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. message: '' +labelWidth: + description: Width. + message: '' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: '' +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: '' +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: '' lastSync: description: Label for last sync timestamp. message: Posljednja sinkronizacija $1 @@ -485,6 +633,11 @@ menuExclude: menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. message: '' +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Feedback menuFindScripts: description: Menu item to find scripts for a site. message: Nađi skripte za ovaj sajt @@ -492,7 +645,12 @@ menuInjectionFailed: description: Injection error. message: '' menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". + message: '' +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. message: '' menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. @@ -504,14 +662,17 @@ menuNewScript: description: Menu item to create a new script. message: '' menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Skripta je onemogućena menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Skripte omogućene msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Tražim obnavljanja... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: '' msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -563,11 +724,21 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Učitavam dependencije... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: '' msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. message: Script namespace conflicts! Molimo modificirajte @name i @namespace. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: '' msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -576,21 +747,36 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Obnavljanja nisu nađena. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: '' +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: '' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: '' msgSavedBlacklist: description: Message shown when blacklist are saved. message: Crna Lista obnovljena. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Prilagođeni stil je obnovljen. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: '' + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: '' + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Skripta [$1] je obnovljena!' + message: Skripta [$1] je obnovljena! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: '' @@ -603,12 +789,22 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Greška pri inicijalizaciji! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: '' msgSyncReady: description: Message shown when sync will start soon. message: Sinkronizacija će uskoro početi... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Sinkronizacija u tijeku... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: '' +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: '' msgUpdated: description: Message shown when a script is updated/reinstalled. message: Skripta je obnovljena. @@ -629,15 +825,82 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: '' +optionPopup: + description: Label of the popup menu section in settings. + message: '' optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: '' +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: '' + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: '' optionPopupHideDisabled: description: Option to hide disabled scripts in popup. message: '' + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: '' + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: '' +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: '' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: '' +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Obnovi +popupSettings: + description: Item inside the popup's "⋮" menu + message: '' +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: '' +readonlyNote: + description: Warning when trying to type in the editor. + message: '' +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: '' +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: '' +reinstall: + description: Button to reinstall a script + message: '' +reloadTab: + description: Label of action to reload the tab + message: '' +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: '' +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: '' @@ -653,12 +916,56 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Postavke -titleScriptUpdated: - description: Notification title for script updates. - message: Obnovi +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: '' +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: '' +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: '' +titleBadgeColor: + description: Tooltip for option to set badge color. + message: '' +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: '' titleSearchHint: description: Hover title for search icon in dashboard. message: '' + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: '' +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: '' +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: '' +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: '' +updateScript: + description: Button to update one script. + message: Obnovi +updateScriptsAll: + description: Command/button to update all scripts. + message: '' +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: '' valueLabelKey: description: Label for key of a script value. message: '' @@ -668,6 +975,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: '' +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: '' + touched: false diff --git a/src/_locales/hu/messages.yml b/src/_locales/hu/messages.yml new file mode 100644 index 0000000000..0fcd3194d6 --- /dev/null +++ b/src/_locales/hu/messages.yml @@ -0,0 +1,1093 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Alkalmaz +buttonCancel: + description: Cancel button on dialog. + message: Mégsem +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Frissítések keresése + touched: false +buttonClose: + description: Button to close window. + message: Bezárás +buttonConfirmInstallation: + description: Button to confirm installation of a script. + message: Telepítés megerősítése + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Újratelepítés megerősítése + touched: false +buttonDisable: + description: Button to disable a script. + message: Kikapcsol +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false +buttonEdit: + description: Button to edit a script. + message: Szerkeszt +buttonEditClickHint: + description: Tooltip for the Edit button in popup. + message: >- + Használhatod a jobb klikket, a Ctrl+klikket és a görgő klikket a szkript + nevén. +buttonEmptyRecycleBin: + description: Button to empty the recycle bin. + message: Kuka kiűrítése most! +buttonEnable: + description: Button to enable a script. + message: Bekapcsol +buttonExportData: + description: Button to open the data export dialog. + message: Exportálás zip-ként +buttonFilter: + description: Button to show filters menu. + message: '' + touched: false +buttonHome: + description: Button to open homepage. + message: Kezdőlap +buttonImportData: + description: Button to choose a file for data import. + message: Importálás zip-ből +buttonInstallFromURL: + description: Button to ask for URL of a user script. + message: Telepítés URL-ről +buttonInstallOptions: + description: Button to show options of installation confirm page. + message: '' + touched: false +buttonNew: + description: Button to create a new script. + message: Új +buttonOK: + description: OK button on dialog. + message: Oké +buttonRecycleBin: + description: Button to list scripts in recycle bin. + message: Kuka +buttonRemove: + description: Button to remove a script. + message: Eltávolít +buttonReplace: + description: Button to replace the current match. + message: Lecserél +buttonReplaceAll: + description: Button to replace all matches. + message: Összes +buttonReset: + description: Button to reset to default values. + message: Visszaállít +buttonResetSettings: + description: Button in settings page to reset all settings + message: Beállítások alaphelyzetbe állítása +buttonRestore: + description: Button to restore a removed script. + message: Helyreállít +buttonSave: + description: Button to save modifications of a script. + message: Mentés +buttonSaveClose: + description: Button to save modifications of a script and then close the editing page. + message: Mentés & Bezárás +buttonSaved: + description: Button text after saving. + message: Elmentve +buttonShowEditorState: + description: Button to show the list of currently used CodeMirror options. + message: Szerkesztő állapotának mutatása +buttonSupport: + description: Button to open support page. + message: Támogatói oldal +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' +buttonUndo: + description: Button to undo removement of a script. + message: Visszavonás +buttonUpdate: + description: Button to update a script. + message: Frissítés +buttonUpdateAll: + description: Check all scripts for updates. + message: Frissítések keresése mindhez + touched: false +buttonVacuum: + description: Button to vacuum extension data. + message: Adatbázis takarítása +buttonVacuumed: + description: Message shown when data is vacuumed. + message: Adatok feltakarítva +buttonVacuuming: + description: Message shown when data vacuum is in progress. + message: Adatok feltakarítása... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Az automatikus frissítés le van tiltva ennél a szkriptnél. + Nyomj az "Oké"-ra, hogy mégis frissítsd. +confirmNotSaved: + description: Confirm message shown when there are unsaved script modifications. + message: >- + A módosítások nincsenek mentve! + + Kattints az "Oké"-ra, hogy elvesd őket, vagy a "Mégse"-re, ha maradni akarsz + a szerkesztőben. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Az adatbázisban végzett összes módosítás visszaállítása (importálás, + frissítés, szerkesztés, testreszabás) +descBlacklist: + description: Description for the global injection blacklist. + message: >- + Az URL-ek, amik egyeznek ebben a listában, nem lesznek injektálva + szkriptekkel. +descBlacklistNet: + description: Description for the global network blacklist. + message: '' +descCustomCSS: + description: Description of custom CSS section. + message: >- + Egyedi CSS az itt található, kiegészítőn belüli oldalakra. Ha nem vagy + biztos benne, hogy ez mire való akkor kérlek ne módosítsd. +descEditorOptions: + description: Description of editor options JSON section. + message: >- + Egyedi beállítások a CodeMirror és bővítményei számára JSON objektumként, + mint például: {"indentUnit":2, "smartIndent":true} Megeshet, + hogy van amelyik nem működik a Violentmonkey-n belül. Itt a teljes lista. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: '' +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: '' +descScriptTemplate: + description: Description of script template section. + message: >- + Támogatott változók: <{{name}}>, <{{url}}>, <{{date}}> és + <{{date:formátum}}>, ami a MomentJS formátumaival kompatibilis, pl. + <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: '' +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: '' +disabledScriptsSelector: + description: Label of the option. + message: '' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: '' +editHelpDocumention: + description: Label in the editor help tab for the documentation link. + message: 'Dokumentáció a userscript metaadat blokkjához és a GM API-hoz:' +editHelpKeyboard: + description: Label in the editor help tab for the keyboard shortcuts. + message: 'Gyorsbillentyűk:' +editHowToHint: + description: The text of the how-to link in the editor header. + message: Másik szerkesztőt szeretnél használni? +editLabelMeta: + description: Metadata section in settings tab of script editor. + message: Egyedi metaadat +editLabelSettings: + description: Settings section in settings tab of script editor. + message: Szkript beállítások +editLongLine: + description: Shown in the editor in lines that were cut due to being too long + message: Túl hosszú a sor +editLongLineTooltip: + description: Tooltip shown in the editor in lines that were cut due to being too long + message: >- + Ez a sor túl hosszú, ezért a szöveget összecsuktuk, hogy ne lassuljon le a + szerkesztő. + + A haladó beállításokban átállíthatod a korlátot az alábbi érték + módosításával, például: + + "maxDisplayLength": 20000 +editNavCode: + description: Label of code tab in script editor. + message: Kód +editNavSettings: + description: Label of settings tab in script editor. + message: Beállítások +editNavValues: + description: Label of values tab in script editor. + message: Értékek +editValueAll: + description: Button to show/edit the entire script value storage. + message: '' + touched: false +editValueAllHint: + description: Tooltip for the 'all' button. + message: A teljes értéktár mutatása/szerkesztése +extDescription: + description: Description for this extension, will be displayed in web store + message: Egy nyílt forráskódú userscript menedzser, ami rengeteg böngészőt támogat! +extName: + description: Name of this extension. + message: Violentmonkey +failureReasonBlacklisted: + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: Ez az oldal szerepel a Violentmonkey beállításainak feketelistáján +failureReasonNoninjectable: + description: >- + Shown for URLs that cannot be processed (same places as + failureBlacklistedUrl) + message: >- + A Violentmonkey nem tud futtatni userscript-et ezen az oldalon + + (gyakori példák erre: böngésző felhasználói felülete, egy bővítményé, + házirend által blokkolt, keresőmotor oldala Opera-ban) +failureReasonRestarted: + description: Shown in the popup. + message: '' +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Több módja is létezik helyi fájloknak a telepítésére, vagy szerkesztésük + követésére: <1> Húzd a fájlt egy megnyitott Violentmonkey oldalra vagy + felugró menüre, beleértve ezt is. <2> Telepíts egy helyi HTTP szervert ehhez + a fájlhoz és nyisd meg azt a http://localhost-on keresztül. <3> Engedélyezd + a "Fájl URL-ek elérése"-t a Violentmonkey részleteiben a böngésző + 'Kiegészítők' oldalán. Ez veszélyes, mert így bármelyik szkript elérhet + bármilyen helyi fájlt. +filterAlphabeticalOrder: + description: Label for option to sort scripts in alphabetical order. + message: ábécésorrend szerint +filterExecutionOrder: + description: Label for option to sort scripts in execution order. + message: futtatási sorrend szerint +filterLastUpdateOrder: + description: Label for option to sort scripts by last update time. + message: utolsó módosítás szerint +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' +filterScopeAll: + description: Option in dashboard's search scope filter. + message: Mindenhol + touched: false +filterScopeCode: + description: Option in dashboard's search scope filter. + message: Kódban + touched: false +filterScopeName: + description: Option in dashboard's search scope filter. + message: Névben + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: méret alapján +genericError: + description: Label for generic error. + message: Hiba +genericOff: + description: To indicate something is turned off or disabled, similar to "no". + message: ki +genericOn: + description: To indicate something is turned on or enabled, similar to "yes". + message: be +genericUseGlobal: + description: To indicate some per-script option will use its analog in global settings. + message: globális beállítás használata +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: '' +headerRecycleBin: + description: Text shown in the header when scripts in recycle bin are listed. + message: Kuka +helpForLocalFile: + description: Label of the checkbox. + message: Az oldalra húzott helyi szkriptekre vonatkozó instrukciók mutatása +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: '' +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' +hintInputURL: + description: Hint for a prompt box to input URL of a user script. + message: 'Add meg az URL-t:' +hintRecycleBin: + description: Hint for recycle bin. + message: Itt találhatóak a törölt szkriptek, amik 7 nap után végleg törlődnek. +hintUseDownloadURL: + description: Shown as a place holder for @updateURL when it is not assigned + message: Használd a @downloadURL-t +hintVacuum: + description: Hint for vacuuming data. + message: Redundanciák elhagyása és a gyorsítótárból hiányzó erőforrások újratöltése. +install: + description: Label for button to install a script. + message: Telepítés +installFrom: + description: Label for button to install script from a userscript site. + message: 'Telepítés innen: $1' +installOptionClose: + description: Option to close confirm window after installation. + message: Bezárás telepítés után + touched: false +installOptionTrackTooltip: + description: >- + Tooltip in Firefox 68+ for the option to track the loading local file before + window is closed. + message: >- + Ehhez a forrásfájl lapjának nyitva kell maradnia Firefox 68 és újabb + verzióknál. +labelAbout: + description: Label shown on top of the about page. + message: '' + touched: false +labelAdvanced: + description: Label for button to show advanced settings. + message: Haladó +labelAllowUpdate: + description: Option to allow checking updates for a script. + message: Frissítés engedélyezése +labelAuthor: + description: Label of author shown in the details of a script. + message: 'Készítő:' +labelAutoReloadCurrentTab: + description: Option to reload current tab after a script is switched on or off from menu. + message: Aktuális lap újratöltése egy szkript ki/bekapcsolása után a menüben +labelAutoReloadCurrentTabDisabled: + description: >- + Tooltip in menu after Violentmonkey is switched off if auto-reload is + disabled. + message: A jelenlegi szkriptek futni fognak addig, amíg újra nem lesz töltve a lap + touched: false +labelAutoUpdate: + description: Option to allow automatically checking scripts for updates + message: Szkript frissítések keresése $1 naponta. Állítsd 0-ra, hogy kikapcsold. +labelBackup: + description: Label of the import/export section in settings. + message: Biztonsági mentés + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Visszaállítás és karbantartás +labelBadge: + description: Label for option to show number on badge. + message: 'Jelvényen mutat: ' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Jelvény színek: ' +labelBadgeNone: + description: Option to display nothing on badge. + message: egyik sem +labelBadgeTotal: + description: Option to display total number of running scripts on badge. + message: futó szkriptek száma +labelBadgeUnique: + description: Option to display number of unique running scripts on badge. + message: egyedi futó szkriptek száma +labelBlacklist: + description: Label for global blacklist settings in security section. + message: Feketelista +labelContributors: + description: Label for link to contributors. + message: Közreműködők +labelCurrentLang: + description: Label of current language. + message: 'Jelenlegi nyelv: ' +labelCustomCSS: + description: Label for custom CSS section. + message: Egyedi stílus +labelDataExport: + description: Section title of data export. + message: '' + touched: false +labelDataImport: + description: Section title of data import. + message: '' + touched: false +labelDonate: + description: Label of link to donate page. + message: Támogass minket + touched: false +labelDownloadURL: + description: Label of script @downloadURL in custom meta data. + message: 'Letöltési URL:' +labelEditValue: + description: Label shown in the panel to edit a script value. + message: Szkript érték szerkesztése +labelEditValueAll: + description: Label shown in the panel to edit the entire script value storage. + message: Szkript értéktár szerkesztése +labelEditor: + description: Label for Editor settings + message: Szerkesztő +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: csak bekapcsolt szkriptek +labelExclude: + description: Label of @exclude rules. + message: '@exclude szabályok' +labelExcludeMatch: + description: Label of @exclude-match rules. + message: '@exclude-match szabályok' +labelExportScriptData: + description: Option to export script data along with scripts. + message: Szkript adatok exportálása +labelExposeStatus: + description: Option in advanced settings. + message: 'Telepített verzió felfedése userscript katalógus oldalakon: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Alternatív $1 módszer Firefox-ban +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Egy alternatív injektálási módszer szkriptekhez Firefox + 59-től, ami gyorsabb, mint az alapértelmezett. A "Szinkron page módhoz" + hasonlóan ez is megnöveli a memória használatot, így ha a szkripted enélkül + is megfelelően fut, akkor nem tanácsos ezt használni. +labelFeedback: + description: Label of link to feedback page. + message: Visszajelzés +labelGeneral: + description: Label for general settings. + message: Általános +labelHelpTranslate: + description: Label for link to localization guide in about tab + message: Segíts a fordításban! +labelHomepage: + description: Label for home page in about tab. + message: Honlap +labelHomepageURL: + description: Label of script @homepageURL in custom meta data. + message: 'Weboldal:' +labelIconURL: + description: Label for the input. + message: '' +labelImportScriptData: + description: Option to import script data along with scripts. + message: Szkript adatok importálása +labelImportSettings: + description: Label for option to import settings from zip file. + message: Beállítások importálása +labelInclude: + description: Label of @include rules. + message: '@include szabályok' +labelInjectionMode: + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Injektálási mód: ' +labelInstall: + description: Shown in the title of the confirm page while trying to install a script. + message: 'Szkript telepítése:' +labelKeepOriginal: + description: Option to keep the original match or ignore rules. + message: Eredeti megtartása +labelLastUpdatedAt: + description: Label shown on last updated time. + message: 'Utoljára frissítve: $1' +labelLineNumber: + description: Label for line number jumper. + message: 'Sor száma: ' +labelMatch: + description: Label of @match rules. + message: '@match szabályok' +labelName: + description: Label of script name. + message: 'Név:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Futás keretekben: ' +labelNoName: + description: Text as the name of a script when no @name is assigned. + message: Hiányzik a név +labelNoSearchScripts: + description: Message shown when no script is found in search results. + message: Egy szkript sem található. +labelNotifyThisUpdated: + description: >- + A per-script option in editor to enable notification when this script is + updated. The text follows "Allow update" checkbox option so it's like a + continuation of the phrase. + message: ', majd értesítés:' +labelNotifyUpdates: + description: Option to show notification when script is updated. + message: Értesítés szkript frissítéséről +labelNotifyUpdatesGlobal: + description: Option to prioritize global notification option over script's setting. + message: >- + szkriptenkénti szabályok figyelmen kívül hagyása ("Beállítások" fül a + szerkesztőben) +labelPrivacyPolicy: + description: Label of link to privacy policy + message: Adatvédelmi irányelvek +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: 'Szkript újratelepítése:' +labelRelated: + description: Label of related links. + message: 'Kapcsolódó linkek: ' +labelRemovedAt: + description: Label for the time when the script is removed. + message: 'Törölve: $1' +labelReplace: + description: Label for replace input in search box. + message: 'Csere erre: ' +labelRunAt: + description: Label of script @run-at properties in custom meta data. + message: 'Futtatás ekkor: ' +labelRunAtDefault: + description: Shown when custom @run-at is not assigned. + message: (Alapértelmezett) +labelScriptTemplate: + description: Label for custom script template. + message: Egyedi Szkript Sablon +labelSearch: + description: Label for search input in search box. + message: 'Keresés erre: ' +labelSearchScript: + description: Placeholder for script search box. + message: Keresés a szkriptekben... +labelSettings: + description: Label shown on the top of settings page + message: Beállítások +labelShowOrder: + description: Label for option in dashboard -> script list + message: Futtatási sorrend számozásának mutatása +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' +labelSync: + description: Label for sync options. + message: Szinkronizálás +labelSyncAnonymous: + description: Label for using anonymous account. + message: Névtelen fiók használata +labelSyncAuthorize: + description: Label for button to authorize a service. + message: Hitelesít +labelSyncAuthorizing: + description: Label for button when authorization is in progress. + message: Hitelesítés +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' +labelSyncDisabled: + description: Label for option to disable sync service. + message: Egyik sem +labelSyncPassword: + description: Label for input to hold password. + message: 'Jelszó: ' +labelSyncReauthorize: + description: Option to reauthorize sync service when expired. + message: '' + touched: false +labelSyncRevoke: + description: Label for button to revoke authorization for a service. + message: Visszavonás +labelSyncScriptStatus: + description: Label for option to sync script status. + message: Szkript állapot szinkronizálása +labelSyncServerUrl: + description: Label for input to hold server URL. + message: 'Szerver URL: ' +labelSyncService: + description: Label for sync service select. + message: 'Szinkronizálás a következővel:' +labelSyncUsername: + description: Label for input to hold username. + message: 'Felhasználónév: ' +labelTags: + description: Label for custom tags. + message: 'Címkék (szóközzel tagolva):' +labelTheme: + description: Label for the visual theme option. + message: 'Téma: ' +labelTranslator: + description: Label of translator. + message: '' + touched: false +labelUpdateURL: + description: Label of script @updateURL in custom meta data. + message: 'Frissítési URL:' +labelViewSingleColumn: + description: >- + Label for option in dashboard script list to show the scripts in single + column. + message: Egy oszlop +labelViewTable: + description: Label for option in dashboard script list to show the scripts as a table. + message: Táblázat nézet +labelWidth: + description: Width. + message: '' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Szinkron $1 mód +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Csak akkor engedélyezd, hogyha van olyan szkripted, aminek már akkor futnia + kéne, amikor az oldal elkezd betölteni; illetve az jelenleg túl későn kezd + el futni. Csak úgy, mint a Tampermonkey Azonnali injektálási módjánál, ez a + opció is elavult szinkron XHR-t használ, vagyis Chrome/Chromium böngészőkben + figyelmeztetéseket láthatsz a fejlesztői-eszközök konzolban, noha ezeket + nyugodtan figyelmen kívül hagyhatod, mivel ebben az esetben ez semmilyen + valódi kellemetlenséget nem okoz. Tartósan el is rejtheted ezeket a + figyelmeztetéseket jobb gombbal rájuk kattintva. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (kivéve inkognitóban és oldalakon, ahol le vannak tiltva a sütik) +lastSync: + description: Label for last sync timestamp. + message: 'Utoljára szinkronizálva: $1' +learnBlacklist: + description: Refers to a link to introduce blacklist patterns. + message: Tudj meg többet a feketelista mintákról. +learnInjectionMode: + description: Refers to a link to introduce injection modes. + message: Tudj meg többet az injektálási módokról. +menuCommands: + description: Menu item to list script commands. + message: '' + touched: false +menuDashboard: + description: Label for menu item to open dashboard. + message: Irányítópult megnyitása +menuExclude: + description: >- + Shown in popup menu after clicking script's "..." dropdown so try to keep + the label short. + message: Kizárás... +menuExcludeHint: + description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. + message: >- + A jelenlegi lap újra fog töltődni, ha engedélyezted ezt az általános + beállításokban. + + Ahhoz, hogy érvényre jusson a változtatás az összes többi lapon, ahhoz + kérlek frissítsd őket manuálisan. + + Használd a "Beállítások" fület a szerkesztőben a nagyobb rugalmasság + érdekében. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Visszajelzés +menuFindScripts: + description: Menu item to find scripts for a site. + message: Szkriptek keresése ehhez az oldalhoz +menuInjectionFailed: + description: Injection error. + message: Néhány szkript injektálása nem sikerült. +menuInjectionFailedFix: + description: Injection error fix, shown in case the default mode is "page". + message: Újrapróbálás "auto" móddal +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Egyező kikapcsolt szkriptek +menuMatchedFrameScripts: + description: Label for menu listing matching scripts in sub-frames. + message: Csak alkeretben futó szkriptek +menuMatchedScripts: + description: Label for menu listing matched scripts. + message: Egyező szkriptek +menuNewScript: + description: Menu item to create a new script. + message: Új szkript készítése +menuScriptDisabled: + description: Menu item showing the status of Violentmonkey, when disabled. + message: Szkript kikapcsolva +menuScriptEnabled: + description: Menu item showing the status of Violentmonkey, when enabled. + message: Szkript bekapcsolva +msgCheckingForUpdate: + description: Message shown when a script is being checked for updates by version numbers. + message: Frissítések keresése... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Kattints a MomentJS dokumentáció megnyitásához. Érvényes tokenek: $1. + Használj [szögletes zárójeleket] az egyszerű szöveg megóvásához. +msgErrorFetchingResource: + description: >- + Message shown when Violentmonkey fails fetching a resource/require/icon of + the script. + message: Hiba az erőforrás lekérésekor! +msgErrorFetchingScript: + description: Message shown when Violentmonkey fails fetching a new version of the script. + message: Hiba a szkript lekérésekor! +msgErrorFetchingUpdateInfo: + description: Message shown when Violentmonkey fails fetching version data of the script. + message: A frissítési információk lekérése nem sikerült. +msgErrorLoadingData: + description: >- + Message shown on confirm page when the script to be installed cannot be + loaded. + message: Hiba a szkript adatok betöltésekor. +msgErrorLoadingDependency: + description: Message shown when not all requirements are loaded successfully. + message: Hiba a függőségek betöltésekor. +msgImported: + description: >- + Message shown after import. There is an argument referring to the count of + scripts imported. + message: $1 elem importálva. +msgIncognitoChanges: + description: >- + Message shown in popup and installation tab when opened in an incognito + window. + message: >- + A változtatások, amiket inkognitó módban végzel érvényesek lesznek a fő + profilodban is. +msgInstalled: + description: Message shown when a script is installed. + message: Szkript telepítve. +msgInvalidScript: + description: Message shown when script is invalid. + message: Érvénytelen szkript! +msgLoadedData: + description: >- + Message shown in the confirm page when a javascript file to be installed is + loaded. + message: '' + touched: false +msgLoading: + description: Message shown in the options page before script list is loaded. + message: '' + touched: false +msgLoadingData: + description: Message shown on confirm page when the script to be installed is loading. + message: Szkript adatok betötése... +msgLoadingDependency: + description: Message shown on confirm page when the requirements are being downloaded. + message: Függőségek betöltése... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Szükséges erőforrások hiányoznak. +msgNamespaceConflict: + description: >- + Message shown when namespace of the new script conflicts with an existent + one. + message: >- + Egy ugyanilyen szkript már telepítve van. + + Kérlek vagy használj más @name, vagy @namespace értékeket itt, vagy + szerkeszd a meglévőt. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Egy szkript ugyanezzel a @name és @namespace értékkel már telepítve van. +msgNewVersion: + description: >- + Message shown when a new version of script is found by @updateURL, but no + @downloadURL is provided. + message: Új verzió érhető el. +msgNoUpdate: + description: Message shown when there is no new version of a script. + message: Nem található frissítés. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Hiba a szkriptek frissítésekor. Kattints a megnyitásukhoz. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: >- + Kérlek mentsd le újra, vagy telepítsd újra ez(eke)t a szkript(ek)et, mert + hiányos(ak): +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: ' (a kód azonos)' +msgSavedBlacklist: + description: Message shown when blacklist are saved. + message: Feketelista frissítve. + touched: false +msgSavedCustomCSS: + description: Message shown when custom CSS is saved. + message: Egyedi stílus frissítve. + touched: false +msgSavedEditorOptions: + description: Message shown when editor options are saved. + message: Szerkesztő beállítások frissítve. + touched: false +msgSavedScriptTemplate: + description: Message shown when custom script template is saved. + message: Egyedi szkript sablon frissítve. + touched: false +msgScriptUpdated: + description: Notification message for script updates. + message: A(z) [$1] szkript frissítve! +msgShowHide: + description: Tooltip or text shown on a toggle that shows/hides stuff + message: Mutat/elrejt +msgSyncError: + description: Message shown when sync failed. + message: Szinkronizálási hiba! +msgSyncInit: + description: Message shown when sync service is initializing. + message: Inicializálás... +msgSyncInitError: + description: Message shown when sync fails in initialization. + message: Hiba inicializáláskor! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Még nincs hitelesítve. +msgSyncReady: + description: Message shown when sync will start soon. + message: A szinkronizálás hamarosan megkezdődik... + touched: false +msgSyncing: + description: Message shown when sync is in progress. + message: Szinkronizálás folyamatban... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Szintaktikai hiba? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: '' +msgUpdated: + description: Message shown when a script is updated/reinstalled. + message: Szkript frissítve. +msgUpdating: + description: Message shown when a new version of script is being fetched. + message: Frissítés... +noValues: + description: Label shown when there is no value for current script. + message: Nincs érték eltárolva +optionEditorWindow: + description: Label for the option in settings + message: Szerkesztő megnyitása a felugró menüből új ablakban +optionEditorWindowHint: + description: >- + Tooltip for optionEditorWindow in case the browser doesn't support + onBoundsChanged + message: >- + A szerkesztő ablak mérete csak átméretezéskor vagy mentéskor lesz + megjegyezve +optionEditorWindowSimple: + description: Label for the editor window type + message: Omnibox elrejtése +optionPopup: + description: Label of the popup menu section in settings. + message: Felugró menü és ikon +optionPopupEnabledFirst: + description: Option to show enabled scripts first in popup. + message: Bekapcsoltak elöl +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Kikapcsolt szkriptek csoportosítása + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Csoportosítás @run-at szerint +optionPopupHideDisabled: + description: Option to hide disabled scripts in popup. + message: Kikapcsolt szkriptek elrejtése + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Kikapcsolt szkriptek mutatása + touched: false +optionShowEnabledFirst: + description: Option to show enabled scripts first in alphabetical order. + message: Bekapcsolt szkriptek mutatása elöl +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Felület témája: ' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: automatikus +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: sötét +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: világos +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Frissítés +popupSettings: + description: Item inside the popup's "⋮" menu + message: '' +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Csak olvasható +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Az automatikusan frissülő szkriptek csak olvashatóak, hacsak nem tiltod a + frissítéseket, vagy engedélyezed a szerkesztést. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Szerkesztés engedélyezése +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (a következő frissítés felül fogja írni) +reinstall: + description: Button to reinstall a script + message: Újratelepítés +reloadTab: + description: Label of action to reload the tab + message: Lap frissítése +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Ha engedélyezve van, akkor az aktív lap frissülni fog változások + észlelésekor, hogyha az URL-e összeillik a szkripttel. +removeAllScripts: + description: Button to remove all scripts + message: Összes szkript eltávolítása + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: >- + Az összes szkriptet a Kukába helyezed? A Kuka automatikusan ki lesz ürítve + bizonyos idő elteltével, illetve te is megnyithatod és kiürítheted + közvetlenül. + touched: false +searchCaseSensitive: + description: Option to perform a case-sensitive search + message: Kis- és nagybetűk megkülönböztetése +searchUseRegex: + description: Option to perform a regular expression search + message: Regex használata +sideMenuAbout: + description: 'Side menu: About' + message: Rólunk +sideMenuInstalled: + description: 'Side menu: Installed scripts' + message: Telepített szkriptek +sideMenuSettings: + description: 'Side menu: Settings' + message: Beállítások +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Oldal újratöltése userscript-ek nélkül +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + A userscript-ek le lettek tiltva ezen az oldalon. Hogy futtasd őket + frissítsd az oldalt, vagy navigálj egy másikra. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Nyomon követés leállítása +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Normál jelvény szín +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Jelvény szín, amikor az oldal nem injektálható (feketelistázott vagy nem + támogatott) +titleSearchHint: + description: Hover title for search icon in dashboard. + message: >- + * az billentyűvel hozzáadódik a keresés az automata-kiegészítés + történethez + + * RegExp szintaxis támogatott: /re/ és /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Az Enter billentyűvel hozzáadódik a keresés az + automata-kiegészítés előzményekhez + + * Minden keresési feltétel kis- és nagybetű érzékeny + + * A szóközzel elválasztott feltételek kombinálhatóak + + * Keresés metadatokra: "Lenyűgöző szkript" "Leírás" + + * Keresés címkékre: #címke1 #címke2 + + * Keresés szkript nevére: name:"lenyűgöző név" + + * Keresés szkript kódra: code:"lenyűgöző kód" + + * Kizáró keresés: !#címke2 !name:"nemkívánt" + + * Reguláris kifejezések: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? szóközzel elválasztva" +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: Külső módosítások nyomon követése +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: Tartsd nyitva ezt az oldalt a helyi fájl módosításainak követéséhez +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Szkriptek frissítése ($1) +updateScript: + description: Button to update one script. + message: Frissítés +updateScriptsAll: + description: Command/button to update all scripts. + message: '' +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: '' +valueLabelKey: + description: Label for key of a script value. + message: Kulcs (sztring) +valueLabelValue: + description: Label for value of a script value. + message: Érték (JSON-ként sorosítva) +valueLabelValueAll: + description: Label for input of entire script value storage. + message: Összes érték (JSON-ként sorosítva) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' +visitWebsite: + description: Label for link to open Violentmonkey website. + message: Látogasd meg a honlapot + touched: false diff --git a/src/_locales/id/messages.yml b/src/_locales/id/messages.yml index ea807d07bb..18405514ea 100644 --- a/src/_locales/id/messages.yml +++ b/src/_locales/id/messages.yml @@ -1,21 +1,37 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Terapkan buttonCancel: description: Cancel button on dialog. message: Batal +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Periksa pembaruan + touched: false buttonClose: description: Button to close window. message: Tutup buttonConfirmInstallation: description: Button to confirm installation of a script. message: Konfirmasi pemasangan + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Konfirmasi pemasangan ulang + touched: false buttonDisable: description: Button to disable a script. message: Nonaktifkan +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: Unduh tema + touched: false buttonEdit: description: Button to edit a script. message: Sunting buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: 'Anda juga bisa Klik-kanan, Ctrl-klik, Wheel-klik pada nama skrip.' + message: Anda juga bisa Klik-kanan, Ctrl-klik, Wheel-klik pada nama skrip. buttonEmptyRecycleBin: description: Button to empty the recycle bin. message: Kosongkan keranjang sampah sekarang! @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Setel ulang +buttonResetSettings: + description: Button in settings page to reset all settings + message: Setel ulang pengaturan buttonRestore: description: Button to restore a removed script. message: Pulihkan @@ -72,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Simpan & Tutup +buttonSaved: + description: Button text after saving. + message: Tersimpan buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Tampilkan status penyunting buttonSupport: description: Button to open support page. message: Halaman dukungan +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. - message: '' - touched: false + message: Urung buttonUpdate: - description: Check a script for updates. - message: Periksa pembaruan + description: Button to update a script. + message: Perbarui buttonUpdateAll: description: Check all scripts for updates. message: Periksa semua pembaruan + touched: false buttonVacuum: description: Button to vacuum extension data. message: Vakum basis data @@ -97,14 +125,31 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Melakukan vakum data... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: >- + Pembaruan otomatis dinonaktifkan untuk skrip ini! Klik OK untuk tetap + memperbarui. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Modifikasi belum disimpan! Klik OKE untuk menghapusnya atau batal untuk tinggal. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Mengembalikan semua perubahan yang dibuat pada basis data (mengimpor, + memperbarui, mengedit, menyesuaikan) descBlacklist: - description: HTML Description for the global blacklist. - message: URL yang cocok di dalam daftar ini tidak akan diinjeksi skrip. + description: Description for the global injection blacklist. + message: Daftar hitam injeksi (skrip tidak akan berjalan di situs yang cocok) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Daftar hitam jaringan (skrip tidak akan terhubung ke situs yang cocok dan + cookie mereka) descCustomCSS: description: Description of custom CSS section. message: >- @@ -123,6 +168,32 @@ descEditorOptions: href="https://github.com/codemirror/CodeMirror/tree/master/theme" target="_blank" rel="noopener noreferrer">kode CSS tema di dalam kotak "Gaya Khusus" di bawah. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Opsi Boolean dapat dialihkan dengan mengklik dua kali pada nilai: 1true1 = + diaktifkan, 2false2 = dinonaktifkan. Opsi numerik yang diakhiri dengan + 3Delay3, 4Interval4, 5Rate5, 6Time6 dalam milidetik. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: '' +descScriptTemplate: + description: Description of script template section. + message: >- + Variabel yang didukung: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + dengan format yang kompatibel dengan MomentJS, contoh <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: '' +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: '' +disabledScriptsSelector: + description: Label of the option. + message: '' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: '' editHelpDocumention: description: Label in the editor help tab for the documentation link. message: 'Dokumentasi tentang blok data meta skrip dan API GM:' @@ -161,23 +232,18 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: Semua + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: Tampilkan/sunting keseluruhan penyimpanan nilai skrip -editValueCancel: - description: Button to cancel modification of a script value. - message: Batal -editValueSave: - description: Button to save modification of a script value. - message: Simpan extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: Pengelola userscript sumber terbuka yang mendukung banyak peramban extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: Dalam daftar hitam di pengaturan Violentmonkey failureReasonNoninjectable: description: >- @@ -186,6 +252,14 @@ failureReasonNoninjectable: message: |- Violentmonkey tidak bisa menjalankan userscript di halaman ini (contoh: UI peramban atau ekstensi) +failureReasonRestarted: + description: Shown in the popup. + message: >- + Violentmonkey telah dimulai ulang. Silakan muat ulang tab untuk menjalankan + skrip. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: '' filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. message: urutan abjad @@ -195,30 +269,58 @@ filterExecutionOrder: filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: terakhir diperbarui +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: Semua + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Kode + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: Nama + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: ukuran genericError: description: Label for generic error. message: Galat genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: nonaktif genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: aktif genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: gunakan pengaturan global +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: '' headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Keranjang Sampah +helpForLocalFile: + description: Label of the checkbox. + message: '' +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: '' +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Masukkan URL:' @@ -233,15 +335,16 @@ hintVacuum: message: >- Buang redundansi dan coba memuat ulang sumber daya yang hilang di dalam cache. +install: + description: Label for button to install a script. + message: Pasang installFrom: description: Label for button to install script from a userscript site. message: Pasang dari $1 installOptionClose: description: Option to close confirm window after installation. message: Tutup setelah pemasangan -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Menelusuri berkas lokal sebelum jendela ini ditutup + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -270,12 +373,23 @@ labelAutoReloadCurrentTabDisabled: Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: Skrip konten akan terus berjalan sampai Anda memuat ulang tab + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: 'Periksa pembaruan skrip setiap $1 hari, gunakan 0 untuk menonaktifkan' + message: Periksa pembaruan skrip setiap $1 hari, gunakan 0 untuk menonaktifkan +labelBackup: + description: Label of the import/export section in settings. + message: Cadangkan + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Pencadangan dan Pemeliharaan labelBadge: description: Label for option to show number on badge. message: 'Ditampilkan pada lencana: ' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Warna lencana:' labelBadgeNone: description: Option to display nothing on badge. message: tidak ada @@ -300,12 +414,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Ekspor Data + touched: false labelDataImport: description: Section title of data import. message: Impor Data + touched: false labelDonate: description: Label of link to donate page. message: Donasi + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'URL Unduhan:' @@ -318,6 +435,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: Penyunting +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: hanya skrip aktif labelExclude: description: Label of @exclude rules. message: Aturan @exclude @@ -330,12 +450,19 @@ labelExportScriptData: labelExposeStatus: description: Option in advanced settings. message: 'Bagikan versi skrip terpasang pada situs katalog userscript: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Mode $1alternatif di Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Metode injeksi alternatif untuk skrip sejak Firefox 59, + lebih cepat daripada mode default. Sama halnya dengan "Mode halaman + sinkron", mode ini juga meningkatkan konsumsi memori, jadi Anda mungkin + ingin menonaktifkannya jika skrip Anda bekerja dengan baik tanpa opsi ini. labelFeedback: description: Label of link to feedback page. message: Umpanbalik -labelFilterSort: - description: Label for sort filter. - message: Urutkan menurut $1 labelGeneral: description: Label for general settings. message: Umum @@ -348,6 +475,9 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'URL Situs Web:' +labelIconURL: + description: Label for the input. + message: 'URL Ikon:' labelImportScriptData: description: Option to import script data along with scripts. message: Impor data skrip @@ -358,8 +488,10 @@ labelInclude: description: Label of @include rules. message: Aturan @include labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Mode injeksi baku: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Mode injeksi: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: Memasang skrip @@ -378,10 +510,12 @@ labelMatch: labelName: description: Label of script name. message: 'Nama:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Berjalan di dalam frame:' labelNoName: description: Text as the name of a script when no @name is assigned. message: Tanpa Nama - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: Tidak ada skrip yang ditemukan. @@ -397,12 +531,14 @@ labelNotifyUpdates: labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: abaikan notifikasi per skrip (tab "pengaturan" di dalam penyunting) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Urutkan skrip di popup menurut $1 labelPrivacyPolicy: description: Label of link to privacy policy message: Kebijakan Privasi +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Memasang ulang skrip labelRelated: description: Label of related links. message: 'Tautan terkait: ' @@ -430,6 +566,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Pengaturan +labelShowOrder: + description: Label for option in dashboard -> script list + message: Tampilkan posisi urutan dijalankan +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Sinkronisasi @@ -442,6 +584,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Mengizinkan +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: Nihil @@ -467,6 +612,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: 'Nama pengguna: ' +labelTags: + description: Label for custom tags. + message: 'Tag (dipisah spasi):' +labelTheme: + description: Label for the visual theme option. + message: 'Tema:' labelTranslator: description: Label of translator. message: 'Penerjemah: ' @@ -482,6 +633,27 @@ labelViewSingleColumn: labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. message: Tampilan tabel +labelWidth: + description: Width. + message: '' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Mode $1 sinkronis +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Hanya aktifkan jika Anda memiliki skrip yang perlu dijalankan sebelum + halaman mulai dimuat dan saat ini terlambat dijalankan. Seperti mode injeksi + instan di Tampermonkey, opsi ini menggunakan XHR sinkronis yang sudah usang, + sehingga di Chrome/Chromium Anda akan melihat peringatan di dalam konsol + devtool, meskipun Anda masih bisa mengabaikannya dengan aman karena pengaruh + buruknya masih bisa diabaikan untuk saat ini. Anda bisa menyembunyikan + peringatan ini memlalui klik kanan pada teks tersebut. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (kecuali mode privat dan situs dengan kuki yang dinonaktifkan) lastSync: description: Label for last sync timestamp. message: Sinkron terakhir pada $1 @@ -512,6 +684,11 @@ menuExcludeHint: Untuk menerapkan perubahan ke semua tab, silakan muat ulang secara manual. Gunakan tab "Pengaturan" dari penyunting untuk opsi lainnya. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Umpanbalik menuFindScripts: description: Menu item to find scripts for a site. message: Temukan skrip untuk situs ini @@ -519,8 +696,13 @@ menuInjectionFailed: description: Injection error. message: Tidak bisa menginjeksi beberapa skrip. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: Ulangi dalam mode "otomatis" +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Cocok skrip nonaktif menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. message: Skrip hanya sub-frame @@ -531,14 +713,19 @@ menuNewScript: description: Menu item to create a new script. message: Buat skrip baru menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Skrip dinonaktifkan menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Skrip diaktifkan msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Memeriksa pembaruan... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Klik untuk membuka dokumentasi MomentJS. Token yang diizinkan: $1. Gunakan + [tanda kurung siku] untuk melindungi teks harfiah. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -592,11 +779,25 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Memuat dependensi... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Kehilangan sumber daya yang dibutuhkan. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. - message: Namespace skrip konflik! Silakan ubah @name dan @namespace. + message: >- + Skrip ini sudah terpasang. + + Silakan gunakan @name dan @namespace yang berbeda di sini atau sunting skrip + tersebut. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Skrip dengan @name dan @namespace yang sama sudah terpasang. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -605,21 +806,36 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Pembaruan tidak tersedia. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Galat saat memperbarui skrip. Klik untuk membuka. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Harap simpan atau pasang ulang skrip ini:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (kode sama) msgSavedBlacklist: description: Message shown when blacklist are saved. message: Daftar hitam diperbarui. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Gaya khusus diperbarui. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Opsi penyunting diperbarui. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: Templat skrip khusus diperbarui. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Skrip [$1] diperbarui!' + message: Skrip [$1] diperbarui! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: Tampilkan/Sembunyikan @@ -632,12 +848,22 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Kesalahan saat memulai! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Belum diotorisasi. msgSyncReady: description: Message shown when sync will start soon. message: Sinkronisasi akan segera dimulai... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Dalam proses sinkronisasi... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Galat sintaksis? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: '' msgUpdated: description: Message shown when a script is updated/reinstalled. message: Skrip diperbarui. @@ -660,15 +886,90 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: Sembunyikan omnibox +optionPopup: + description: Label of the popup menu section in settings. + message: Ikon dan menu popup optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: Aktif di atas +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Grup skrip nonaktif + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Kelompokkan menurut tahap @run-at optionPopupHideDisabled: description: Option to hide disabled scripts in popup. message: Sembunyikan nonaktif + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Tampilkan skrip nonaktif + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Tampilkan skrip aktif di atas +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Tema UI:' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: otomatis +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: gelap +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: terang +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Perbarui +popupSettings: + description: Item inside the popup's "⋮" menu + message: '' +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Hanya-baca +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Skrip yang diperbarui otomatis dikunci dalam mode hanya-baca, kecuali Anda + menonaktifkan pembaruan skrip dan mengizinkan penyuntingan. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Izinkan penyuntingan +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (pembaruan berikutnya akan menimpanya) +reinstall: + description: Button to reinstall a script + message: Pasang ulang +reloadTab: + description: Label of action to reload the tab + message: Muat ulang tab +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Jika diaktifkan, tab yang aktif akan dimuat ulang saat mendeteksi adanya + perubahan dan dengan URL yang sama. +removeAllScripts: + description: Button to remove all scripts + message: Hapus semua skrip + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: >- + Hapus semua skrip ke "Keranjang Sampah"? + + Keranjang Sampah akan dikosongkan secara permanen setelah beberapa saat. + Anda juga bisa membukanya dan mengosongkannya sekarang juga. + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Sesuai besar kecil huruf @@ -684,23 +985,80 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Pengaturan -titleScriptUpdated: - description: Notification title for script updates. - message: Perbarui +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Muat ulang halaman tanpa userscript +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Anda menonaktifkan userscript untuk halaman ini. Untuk menjalankannya, muat + ulang atau kunjungi tab tersebut. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Setop pelacakan +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Warna lencana normal +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Warna lencana untuk situs yang tidak bisa diinjeksi (daftar hitam atau tidak + didukung) titleSearchHint: description: Hover title for search icon in dashboard. message: |- * menambahkan teks ke riwayat autocomplete * Sintaksis RegExp didukung: /re/ dan /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: '' +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: Lacak penyuntingan eksternal +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: >- + Biarkan halaman ini tetap terbuka untuk melacak perubahan yang Anda buat + dalam berkas lokal +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Perbarui skrip ($1) +updateScript: + description: Button to update one script. + message: Perbarui +updateScriptsAll: + description: Command/button to update all scripts. + message: Perbarui semua skrip +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Perbarui skrip tab aktif saat ini valueLabelKey: description: Label for key of a script value. - message: '' + message: Key (string) valueLabelValue: description: Label for value of a script value. - message: '' + message: Nilai (serialisasi sebagai JSON) valueLabelValueAll: description: Label for input of entire script value storage. + message: Semua nilai (serialisasi sebagai JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: Kunjungi Situs Web + touched: false diff --git a/src/_locales/it/messages.yml b/src/_locales/it/messages.yml index f16ac18e91..b1295eeffc 100644 --- a/src/_locales/it/messages.yml +++ b/src/_locales/it/messages.yml @@ -1,15 +1,31 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Applica buttonCancel: description: Cancel button on dialog. message: Annulla +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Controlla aggiornamenti + touched: false buttonClose: description: Button to close window. message: Chiudi buttonConfirmInstallation: description: Button to confirm installation of a script. message: Conferma installazione + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Conferma reinstallazione + touched: false buttonDisable: description: Button to disable a script. - message: Disabilita + message: Disattiva +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: Scarica temi + touched: false buttonEdit: description: Button to edit a script. message: Modifica @@ -23,17 +39,17 @@ buttonEmptyRecycleBin: message: Svuota il cestino ora! buttonEnable: description: Button to enable a script. - message: Abilita + message: Attiva buttonExportData: description: Button to open the data export dialog. - message: Esporta a zip + message: Esporta in formato zip buttonFilter: description: Button to show filters menu. message: Filtri touched: false buttonHome: description: Button to open homepage. - message: Homepage + message: Pagina principale buttonImportData: description: Button to choose a file for data import. message: Importa da zip @@ -64,7 +80,10 @@ buttonReplaceAll: message: Tutti buttonReset: description: Button to reset to default values. - message: Reset + message: Inizializza +buttonResetSettings: + description: Button in settings page to reset all settings + message: Inizializza impostazioni buttonRestore: description: Button to restore a removed script. message: Ripristina @@ -73,83 +92,142 @@ buttonSave: message: Salva buttonSaveClose: description: Button to save modifications of a script and then close the editing page. - message: Salva & Chiudi + message: Salva e chiudi +buttonSaved: + description: Button text after saving. + message: Salvato buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. - message: Mostra lo stato dell'editor + message: Mostra stato editor buttonSupport: description: Button to open support page. - message: Supporto + message: Pagina di assistenza +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. message: Annulla - touched: false buttonUpdate: - description: Check a script for updates. - message: Controlla aggiornamenti + description: Button to update a script. + message: Aggiorna buttonUpdateAll: description: Check all scripts for updates. message: Controlla tutti gli aggiornamenti + touched: false buttonVacuum: description: Button to vacuum extension data. - message: Svuota database + message: Ripulisci database buttonVacuumed: description: Message shown when data is vacuumed. - message: Dati eliminati + message: Dati ripuliti buttonVacuuming: description: Message shown when data vacuum is in progress. - message: Eliminando dati... + message: Ripulisco dati… +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + L'aggiornamento automatico è disattivato per questo script! + Fai clic su OK per aggiornarlo comunque. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- - Modifiche non salvate! - Clicca OK per scartarle o annulla per rimanere. + Ci sono modifiche non salvate! + Fai clic su OK per scartarle o annulla per rimanere. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Annulla tutte le modifiche eseguite al database (importazioni, + aggiornamenti, modifiche e personalizzazioni) descBlacklist: - description: HTML Description for the global blacklist. - message: Gli URL in questa lista non saranno affetti dagli script. + description: Description for the global injection blacklist. + message: >- + Lista nera iniezione (gli script non verranno eseguiti nei siti + corrispondenti) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Lista nera reti (gli script non si connetteranno ai siti corrispondenti e ai + loro cookie) descCustomCSS: description: Description of custom CSS section. message: >- - CSS personalizzato per le pagine opzioni e installazione script. Se non sei - sicuro di cosa sia, per favore non modificarlo. + CSS personalizzato per la pagina delle opzioni e per la pagina + dell'installazione di script. Se non sai a cosa serve, non modificare questo + valore. descEditorOptions: description: Description of editor options JSON section. message: >- - Opzioni personalizzate per CodeMirror e in oggetti JSON, come - {"indentUnit":2, "smartIndent":true} tuttavia, alcune - potrebbero non funzionare in Violentmonkey. Vedi la {"indentUnit":2, "smartIndent":true}; tuttavia, + tieni presente che alcune di esse potrebbero non funzionare in + Violentmonkey. Vedi l'lista intera. Per usare un thema - personalizzato CodeMirror, specificane il file name, come ad esempio - "theme": "3024-day"qui e incolla il CSS del temain "Stile - personalizzato" sotto. + rel="noopener noreferrer">elenco completo. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Le opzioni booleane possono essere attivate o disattivate facendo doppio + clic sul valore: vero = attivo, falso = + disattivato. Le opzioni numeriche che terminano con ritardo, + intervallo, ritmo e tempo sono in + millisecondi. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Opzioni non standard: autocompleteOnTyping è un ritardo in + millisecondi per visualizzare suggerimenti di autocompletamento dopo + l'inserimento (0 = disattiva), + killTrailingSpaceOnSave rimuove automaticamente gli spazi alla + fine di ogni riga al salvataggio, showTrailingSpace mostra + tutti gli spazi terminali come punti. +descScriptTemplate: + description: Description of script template section. + message: >- + Variabili supportate: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + con un formato compatibile con MomentJS, ad esempio <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: gruppo +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: nascondi +disabledScriptsSelector: + description: Label of the option. + message: 'Script disattivati:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: mostra editHelpDocumention: description: Label in the editor help tab for the documentation link. - message: 'Documentation on userscript metadata block and GM API:' + message: 'Documentazione sul blocco di metadati userscript e API GM:' editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. - message: 'Keyboard shortcuts:' + message: 'Scorciatoie da tastiera:' editHowToHint: description: The text of the how-to link in the editor header. - message: Use another editor? + message: Vuoi usare un altro editor? editLabelMeta: description: Metadata section in settings tab of script editor. - message: Metadata personalizzato + message: Metadati personalizzati editLabelSettings: description: Settings section in settings tab of script editor. message: Impostazioni script editLongLine: description: Shown in the editor in lines that were cut due to being too long - message: La linea è troppo lunga + message: Riga troppo lunga editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long message: >- - La linea è troppo lunga, per cui il testo è stato ridotto per prevenire - ritardi durante l'editing. + La riga è troppo lunga, perciò il testo è stato contenuto per prevenire + ritardi durante le modifiche. - Puoi impostarne il limite nelle impostazioni avanzate, per esempio: + Puoi personalizzare il limite nelle impostazioni avanzate, per esempio: "maxDisplayLength":20000 editNavCode: @@ -164,85 +242,128 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: Tutti + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: Mostra/modifica l'intero archivio dei valori dello script -editValueCancel: - description: Button to cancel modification of a script value. - message: Annulla -editValueSave: - description: Button to save modification of a script value. - message: Salva extDescription: - description: 'Description for this extension, will be displayed in web store' - message: Un gestore open source di userscript che supporta molti browser + description: Description for this extension, will be displayed in web store + message: Un gestore di userscript open source che supporta un sacco di browser extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' - message: Blacklisted in Violentmonkey's settings + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: Aggiunto alla lista nera nelle impostazioni di Violentmonkey failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) - message: |- - Violentmonkey cannot run userscripts in this page - (common examples: browser UI or an extension) + message: >- + Violentmonkey non può eseguire script utente in questa pagina + + (esempi comuni: interfaccia utente del browser, un'estensione, bloccato da + criteri di gruppo locali, un sito motore di ricerca in Opera) +failureReasonRestarted: + description: Shown in the popup. + message: >- + Violentmonkey è stato riavviato. Ricarica la pagina per eseguire gli + userscript. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Esistono diversi metodi per installare o tenere traccia delle modifiche in + un file locale: <1> Trascina il file in una pagina o in un popup di + Violentmonkey aperto, incluso questo. <2> Installa un server HTTP locale per + questo file e aprilo tramite http://localhost. <3> Abilita "Consenti accesso + a URL di file" nei dettagli di Violentmonkey nella pagina + chrome://extensions. Questo è pericoloso perché qualsiasi userscript può + leggere qualsiasi file locale. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: ordine alfabetico + message: alfabetico filterExecutionOrder: description: Label for option to sort scripts in execution order. - message: ordine di esecuzione + message: esecuzione filterLastUpdateOrder: description: Label for option to sort scripts by last update time. - message: last update time + message: ultimo aggiornamento +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. - message: All + message: Tutti + touched: false filterScopeCode: description: Option in dashboard's search scope filter. - message: Code + message: Codice + touched: false filterScopeName: description: Option in dashboard's search scope filter. - message: Name + message: Nome + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: dimensioni genericError: description: Label for generic error. message: Errore genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' - message: 'off' + description: To indicate something is turned off or disabled, similar to "no". + message: spento genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' - message: 'on' + description: To indicate something is turned on or enabled, similar to "yes". + message: acceso genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. - message: use global setting + message: usa impostazione globale +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: '' headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. - message: Recycle Bin + message: Cestino +helpForLocalFile: + description: Label of the checkbox. + message: >- + Mostra le istruzioni per gli script locali che vengono trascinati nella + finestra di Violentmonkey +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: per $1 script corrispondenti +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. - message: 'URL Input:' + message: 'URL input:' hintRecycleBin: description: Hint for recycle bin. message: Gli script rimossi sono elencati qui e verranno conservati per 7 giorni. hintUseDownloadURL: description: Shown as a place holder for @updateURL when it is not assigned - message: Utilizza @downloadURL + message: Usa @downloadURL hintVacuum: description: Hint for vacuuming data. - message: Rimuovi le ridondanze e prova a ricarica le risorse mancanti in cache. + message: Scarta le ridondanze e prova a ricaricare le risorse mancanti in cache. +install: + description: Label for button to install a script. + message: Installa installFrom: description: Label for button to install script from a userscript site. message: Installa da $1 installOptionClose: description: Option to close confirm window after installation. - message: Chiudo dopo l'installazione -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Traccia il file locale prima di chiudere questa finestra + message: Chiudi dopo l'installazione + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -256,114 +377,149 @@ labelAbout: touched: false labelAdvanced: description: Label for button to show advanced settings. - message: Impostazioni avanzate + message: Avanzate labelAllowUpdate: description: Option to allow checking updates for a script. - message: Permetti aggiornamenti + message: Consenti aggiornamento labelAuthor: description: Label of author shown in the details of a script. message: 'Autore: ' labelAutoReloadCurrentTab: description: Option to reload current tab after a script is switched on or off from menu. - message: Ricarica la scheda corrente dopo aver spento o acceso uno script dal menu + message: >- + Ricarica la scheda attuale dopo aver attivato/disattivato uno script dal + menu labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. - message: '' + message: >- + Gli script correnti continueranno a funzionare fino a quando non + ricaricherai la scheda + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: 'Check for script updates every $1 day(s), use 0 to disable' + message: >- + Controlla gli aggiornamenti degli script ogni $1 giorni, imposta 0 per + disattivare il controllo +labelBackup: + description: Label of the import/export section in settings. + message: Backup + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Backup e manutenzione labelBadge: description: Label for option to show number on badge. - message: Mostra sull'icona + message: 'Badge:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Colori badge:' labelBadgeNone: description: Option to display nothing on badge. - message: Nulla + message: nessuno labelBadgeTotal: description: Option to display total number of running scripts on badge. - message: Il numero di script totali in esecuzione + message: numero di script in esecuzione labelBadgeUnique: description: Option to display number of unique running scripts on badge. - message: Il numero di script unici in esecuzione + message: numero di script unici in esecuzione labelBlacklist: description: Label for global blacklist settings in security section. - message: Blacklist + message: Lista nera labelContributors: description: Label for link to contributors. - message: Contributori + message: Collaboratori labelCurrentLang: description: Label of current language. - message: 'Linguaggio corrente: ' + message: 'Lingua attuale: ' labelCustomCSS: description: Label for custom CSS section. - message: Stile Personalizzato + message: Stile personalizzato labelDataExport: description: Section title of data export. message: Esportazione Dati + touched: false labelDataImport: description: Section title of data import. message: Importazione Dati + touched: false labelDonate: description: Label of link to donate page. message: Dona + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. - message: 'URL Download:' + message: 'URL download:' labelEditValue: description: Label shown in the panel to edit a script value. - message: Modifica valore per lo scipt + message: Modifica valore script labelEditValueAll: description: Label shown in the panel to edit the entire script value storage. message: Modifica archivio script labelEditor: description: Label for Editor settings message: Editor +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: solo script abilitati labelExclude: description: Label of @exclude rules. - message: '@exclude rules' + message: Regole @exclude labelExcludeMatch: description: Label of @exclude-match rules. - message: '@exclude-match rules' + message: Regole @exclude-match labelExportScriptData: description: Option to export script data along with scripts. - message: Esporta i dati degli script + message: Esporta dati script labelExposeStatus: description: Option in advanced settings. - message: 'Esporre la versione installata sui siti catalogo di userscript: $1' + message: 'Esponi la versione installata sui siti catalogo di script utente: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Modalità alternativa $1 in Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Una modalità alternativa di iniezione per gli script da + Firefox 59, più veloce di quella predefinita. Così come "Modalità a pagina + sincrona" aumenta anche il consumo di memoria, quindi potresti volerla + disattivare se i tuoi script funzionano bene senza questa opzione. labelFeedback: description: Label of link to feedback page. - message: Feedback -labelFilterSort: - description: Label for sort filter. - message: Ordina per $1 + message: Segnala problemi o invia un tuo parere labelGeneral: description: Label for general settings. - message: Generale + message: Generali labelHelpTranslate: description: Label for link to localization guide in about tab - message: Aiuta con la traduzione + message: Aiuta a tradurre labelHomepage: description: Label for home page in about tab. - message: Homepage + message: Pagina principale labelHomepageURL: description: Label of script @homepageURL in custom meta data. - message: 'URL Homepage:' + message: 'URL pagina principale:' +labelIconURL: + description: Label for the input. + message: 'URL icona:' labelImportScriptData: description: Option to import script data along with scripts. - message: Importa i dati dello script + message: Importa dati script labelImportSettings: description: Label for option to import settings from zip file. message: Importa impostazioni labelInclude: description: Label of @include rules. - message: '@include rules' + message: Regole @include labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Default injection mode: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Modalità di iniezione:' labelInstall: description: Shown in the title of the confirm page while trying to install a script. - message: Installando script + message: Installazione dello script labelKeepOriginal: description: Option to keep the original match or ignore rules. message: Mantieni originale @@ -372,17 +528,19 @@ labelLastUpdatedAt: message: 'Ultimo aggiornamento: $1' labelLineNumber: description: Label for line number jumper. - message: 'Linea numero: ' + message: 'Riga numero: ' labelMatch: description: Label of @match rules. - message: '@match rules' + message: Regole @match labelName: description: Label of script name. message: 'Nome:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Esegui in frame:' labelNoName: description: Text as the name of a script when no @name is assigned. message: Senza nome - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: Nessuno script trovato. @@ -391,37 +549,39 @@ labelNotifyThisUpdated: A per-script option in editor to enable notification when this script is updated. The text follows "Allow update" checkbox option so it's like a continuation of the phrase. - message: ', then notify:' + message: ', poi notifica:' labelNotifyUpdates: description: Option to show notification when script is updated. message: Notifica aggiornamenti script labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. - message: ignore per-script notification ("settings" tab in editor) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Sort scripts in popup by $1 + message: ignora notifica per singoli script (scheda "impostazioni" dell'editor) labelPrivacyPolicy: description: Label of link to privacy policy - message: Politica Sulla Riservatezza + message: Politica sulla privacy +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Reinstallazione script labelRelated: description: Label of related links. message: 'Link correlati: ' labelRemovedAt: description: Label for the time when the script is removed. - message: Removed at $1 + message: Rimosso alle $1 labelReplace: description: Label for replace input in search box. message: 'Sostituisci con: ' labelRunAt: description: Label of script @run-at properties in custom meta data. - message: 'Esegui-In: ' + message: 'Esegui in: ' labelRunAtDefault: description: Shown when custom @run-at is not assigned. - message: (Default) + message: (Predefinito) labelScriptTemplate: description: Label for custom script template. - message: Custom Script Template + message: Modello script personalizzato labelSearch: description: Label for search input in search box. message: 'Cerca: ' @@ -431,18 +591,27 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Impostazioni +labelShowOrder: + description: Label for option in dashboard -> script list + message: Mostra posizione per ordine di esecuzione +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. - message: Sincronizzazione + message: Sincronizza labelSyncAnonymous: description: Label for using anonymous account. - message: Use anonymous account + message: Usa profilo anonimo labelSyncAuthorize: description: Label for button to authorize a service. message: Autorizza labelSyncAuthorizing: description: Label for button when authorization is in progress. - message: Autorizzando + message: Autorizzazione in corso +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: Nessuno @@ -458,106 +627,154 @@ labelSyncRevoke: message: Revoca labelSyncScriptStatus: description: Label for option to sync script status. - message: Sincronizza lo stato degli script + message: Sincronizza stato degli script labelSyncServerUrl: description: Label for input to hold server URL. - message: 'Server URL: ' + message: 'URL server: ' labelSyncService: description: Label for sync service select. message: Sincronizza con labelSyncUsername: description: Label for input to hold username. - message: 'Username: ' + message: 'Nome utente: ' +labelTags: + description: Label for custom tags. + message: 'Etichette (separate da uno spazio):' +labelTheme: + description: Label for the visual theme option. + message: 'Tema:' labelTranslator: description: Label of translator. message: 'Traduttore: ' touched: false labelUpdateURL: description: Label of script @updateURL in custom meta data. - message: 'URL Aggiornamento:' + message: 'URL aggiornamento:' labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: Colonna singola labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. - message: '' + message: Vista tabella +labelWidth: + description: Width. + message: 'Larghezza:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Modalità $1 sincrona +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Abilita solo se hai uno script che deve essere eseguito prima che la pagina + inizi a caricarsi e al momento viene eseguito troppo tardi. Proprio come la + modalità di iniezione istantanea in Tampermonkey, questa opzione usa il + deprecato XHR sincrono, quindi in Chrome/Chromium vedrai degli avvisi nella + console per sviluppatori, anche se puoi tranquillamente ignorarli dato che + gli effetti negativi sono trascurabili in questo caso. Puoi nascondere per + sempre gli avvisi cliccando con il tasto destro su uno di essi. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (eccetto la modalità in incognito e siti con i cookie disattivati) lastSync: description: Label for last sync timestamp. - message: Ultima sincronizzazione il $1 + message: Ultima sincronizzazione alle $1 learnBlacklist: description: Refers to a link to introduce blacklist patterns. - message: Impara di più sui pattern blacklist. + message: Scopri di più sugli schemi della lista nera. learnInjectionMode: description: Refers to a link to introduce injection modes. - message: Learn more about injection modes. + message: Scopri di più sulle modalità di iniezione. menuCommands: description: Menu item to list script commands. message: Comandi script touched: false menuDashboard: description: Label for menu item to open dashboard. - message: Apri Dashboard + message: Apri pannello di controllo menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: '' + message: Escludi... menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. - message: '' + message: >- + La scheda corrente verrà ricaricata automaticamente se hai attivato questa + opzione nelle impostazioni generali. + + Per applicare le modifiche a tutte le altre schede, ricaricale manualmente. + + Usa la scheda "Impostazioni" dell'editor per una maggiore flessibilità. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Feedback menuFindScripts: description: Menu item to find scripts for a site. - message: Cerca script per questo sito + message: Trova script per questo sito menuInjectionFailed: description: Injection error. message: Impossibile iniettare alcuni script. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: Riprovare in modalità "auto" +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Script disattivati menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. - message: Sub-frames only scripts + message: Script solo per sub-frame menuMatchedScripts: description: Label for menu listing matched scripts. message: Script corrispondenti menuNewScript: description: Menu item to create a new script. - message: Create a new script + message: Crea un nuovo script menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' - message: Script disabilitati + description: Menu item showing the status of Violentmonkey, when disabled. + message: Script disattivati menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' - message: Script abilitati + description: Menu item showing the status of Violentmonkey, when enabled. + message: Script attivati msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. - message: Controllo degli aggiornamenti... + message: Controllo aggiornamenti in corso... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Fai clic per aprire la documentazione di MomentJS. Token consentiti: $1. Usa + [parentesi quadre] per proteggere il testo letterale. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of the script. - message: Errore durante il recupero della risorsa! + message: Errore nel recupero della risorsa! msgErrorFetchingScript: description: Message shown when Violentmonkey fails fetching a new version of the script. - message: Errore durante lo scaricamento dello script! + message: Errore nel recupero dello script! msgErrorFetchingUpdateInfo: description: Message shown when Violentmonkey fails fetching version data of the script. - message: Problemi nello scaricamento delle informazioni sull'aggiornamento. + message: Recupero informazioni di aggiornamento fallito. msgErrorLoadingData: description: >- Message shown on confirm page when the script to be installed cannot be loaded. - message: Errore caricamento dati script. + message: Errore nel caricamento dei dati dello script. msgErrorLoadingDependency: description: Message shown when not all requirements are loaded successfully. - message: Errore caricamento dipendenze. + message: Errore nel caricamento delle dipendenze. msgImported: description: >- Message shown after import. There is an argument referring to the count of scripts imported. - message: $1 elemento/i importati. + message: $1 elementi importati. msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito @@ -583,17 +800,29 @@ msgLoading: touched: false msgLoadingData: description: Message shown on confirm page when the script to be installed is loading. - message: Caricamento dati script... + message: Caricamento dati script in corso... msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. - message: Caricamento dipendenze... ($1/$2) + message: Caricamento dipendenze in corso... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Mancano risorse necessarie. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. message: >- - Conflitto del namespace dello script! Per favore modifica @name e - @namespace. + Uno script come questo è già installato. + + Usa un @name e @namespace diverso qui o modifica l'altro script con gli + stessi valori. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Uno script con lo stesso @name e @namespace è già installato. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -601,40 +830,65 @@ msgNewVersion: message: Nuova versione trovata. msgNoUpdate: description: Message shown when there is no new version of a script. - message: Aggiornamento non trovato. + message: Nessun aggiornamento trovato. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Errore nell'aggiornamento degli script. Fai clic per aprirli. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Per favore salva o installa nuovamente questi script:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (il codice è lo stesso) msgSavedBlacklist: description: Message shown when blacklist are saved. - message: Blacklist aggiornata. + message: Lista nera aggiornata. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Stile personalizzato aggiornato. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. - message: Editor options are updated. + message: Opzioni editor aggiornate. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. - message: Custom script template is updated. + message: Modello script personalizzato aggiornato. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Lo script [$1] è aggiornato!' + message: Lo script [$1] è aggiornato! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff - message: '' + message: Mostra/nascondi msgSyncError: description: Message shown when sync failed. - message: Errore sincronizzazione! + message: Errore di sincronizzazione! msgSyncInit: description: Message shown when sync service is initializing. - message: Inizializzando... + message: Inizializzazione in corso... msgSyncInitError: description: Message shown when sync fails in initialization. - message: Errore inizializzazione! + message: Errore di inizializzazione! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Non ancora autorizzato. msgSyncReady: description: Message shown when sync will start soon. - message: La sincronizzazione partirà presto... + message: La sincronizzazione inizierà a breve... + touched: false msgSyncing: description: Message shown when sync is in progress. - message: Sincronizzazione in progresso... + message: Sincronizzazione in corso... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Errore di sintassi? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: '' msgUpdated: description: Message shown when a script is updated/reinstalled. message: Script aggiornato. @@ -646,58 +900,211 @@ noValues: message: Nessun valore salvato optionEditorWindow: description: Label for the option in settings - message: Apri editor da popup ad una nuova finestra + message: Apri editor da finestra a comparsa in una nuova finestra optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support onBoundsChanged message: >- - La posizione della finestra dell'editor sarà memorizzata solo su - ridimensionamento o salvataggio + La posizione della finestra dell'editor sarà ricordata solo quando viene + ridimensionata o salvata optionEditorWindowSimple: description: Label for the editor window type message: Nascondi omnibox +optionPopup: + description: Label of the popup menu section in settings. + message: Menu a comparsa e icona optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. - message: Enabled first + message: Mostra quelli attivati per primi +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Raggruppa script disattivati + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Raggruppa per stadio @run-at optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: Hide disabled + message: Nascondi script disattivati + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Mostra script disattivati + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. - message: Visualizza script abilitati per primi + message: Mostra per primi gli script attivati +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Tema interfaccia utente:' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: automatico +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: scuro +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: chiaro +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Aggiorna +popupSettings: + description: Item inside the popup's "⋮" menu + message: Impostazioni finestre a comparsa +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Clic tasto destro: impostazioni finestre a comparsa' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Modalità solo lettura +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Questo script è in modalità di sola lettura perché sono attivi gli + aggiornamenti automatici. Per poterlo modificare devi disattivare gli + aggiornamenti o consentire le modifiche. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Consenti modifiche +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (il prossimo aggiornamento sovrascriverà le tue modifiche) +reinstall: + description: Button to reinstall a script + message: Reinstalla +reloadTab: + description: Label of action to reload the tab + message: Ricarica scheda +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Se attivato, la scheda attiva sarà ricaricata quando vengono rilevate delle + modifiche e quando lo script corrisponde all'URL della scheda. +removeAllScripts: + description: Button to remove all scripts + message: Rimuovi tutti gli script + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: >- + Vuoi spostare tutti gli script nel cestino? + + Dopo un po' di tempo verrà svuotato automaticamente. Volendo, puoi aprirlo e + svuotarlo manualmente. + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search - message: Maiuscole/minuscole + message: Discrimina maiuscole searchUseRegex: description: Option to perform a regular expression search - message: Espressione regolare + message: Usa espressione regolare sideMenuAbout: description: 'Side menu: About' - message: A riguardo + message: Informazioni sideMenuInstalled: description: 'Side menu: Installed scripts' message: Script installati sideMenuSettings: description: 'Side menu: Settings' message: Impostazioni -titleScriptUpdated: - description: Notification title for script updates. - message: Aggiornamento +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Ricarica la pagina senza gli script +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Hai momentaneamente disattivato gli script per questa pagina. Ricaricando la + scheda o navigando tra i link, gli script verranno riattivati. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Ordine:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Smetti di rilevare modifiche +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Colore predefinito +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Colore quando il sito non è iniettabile (è nella lista nera o non è + supportato) titleSearchHint: description: Hover title for search icon in dashboard. message: |- - * key adds the text to autocomplete history - * RegExp syntax is supported: /re/ and /re/flags + * il tasto aggiunge il testo alla cronologia di autocompletamento + * La sintassi RegExp è supportata: /re/ e /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * il tastoInvio aggiunge il testo alla cronologia di + autocompletamento + + * tutte le condizioni non fanno diffierenze tra maiuscole e minuscole + + * le condizioni separate da uno spazio possono essere combinate + + * puoi cercare i metadati: "Awesome Script" "Description" + + * puoi cercare le etichette: #tag1 #tag2 + + * puoi cercare il nome dello script: name:"awesome name" + + * puoi cercare il codice dello script: code:"awesome code" + + * puoi escludere dei risultati: !#tag2 !name:"unwanted" + + * puoi usare espressioni regolari: /\w+?/, + /\w+?/gi, name:/\w+?/, name+re:"\w+? with + space" +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: Rileva modifiche esterne +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: >- + Lascia questa pagina aperta per poter rilevare le modifiche in un file + salvato localmente +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Aggiorna script ($1) +updateScript: + description: Button to update one script. + message: Aggiorna +updateScriptsAll: + description: Command/button to update all scripts. + message: Aggiorna tutti gli script +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Aggiorna gli script della scheda attuale valueLabelKey: description: Label for key of a script value. message: Chiave (stringa) valueLabelValue: description: Label for value of a script value. - message: Valore (stringa JSON) + message: Valore (formato JSON) valueLabelValueAll: description: Label for input of entire script value storage. - message: Tutti i valori (serializzato come JSON) + message: Tutti i valori (formato JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' visitWebsite: description: Label for link to open Violentmonkey website. - message: Visit Website + message: Visita sito web + touched: false diff --git a/src/_locales/ja/messages.yml b/src/_locales/ja/messages.yml index f56fbbd0de..8a8db37657 100644 --- a/src/_locales/ja/messages.yml +++ b/src/_locales/ja/messages.yml @@ -1,21 +1,37 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: 適用 buttonCancel: description: Cancel button on dialog. message: キャンセル +buttonCheckForUpdates: + description: Button to check a script for updates. + message: 更新を確認 + touched: false buttonClose: description: Button to close window. message: 閉じる buttonConfirmInstallation: description: Button to confirm installation of a script. message: インストール + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: 再インストール + touched: false buttonDisable: description: Button to disable a script. message: 無効化 +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false buttonEdit: description: Button to edit a script. message: 編集 buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: '' + message: スクリプト名を右クリック、 [Ctrl] キーを押しながらクリック、ホイールでクリックすることもできます。 buttonEmptyRecycleBin: description: Button to empty the recycle bin. message: 今すぐゴミ箱を空にする @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: リセット +buttonResetSettings: + description: Button in settings page to reset all settings + message: 設定を初期化 buttonRestore: description: Button to restore a removed script. message: 復元 @@ -72,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: 保存して閉じる +buttonSaved: + description: Button text after saving. + message: 保存済み buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: エディタの設定を表示 buttonSupport: description: Button to open support page. message: サポートページ +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. message: 元に戻す - touched: false buttonUpdate: - description: Check a script for updates. - message: 更新を確認 + description: Button to update a script. + message: 更新 buttonUpdateAll: description: Check all scripts for updates. message: すべての更新を確認 + touched: false buttonVacuum: description: Button to vacuum extension data. message: データベースの掃除 @@ -97,46 +125,90 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: データを掃除中… +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + このスクリプトの自動更新は無効中です! + それでも更新するなら、OKを押します。 confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- 変更内容が保存されていません! - 「OK」をクリックすると変更を破棄します。 (「キャンセル」でこのページに留まります。) + OKで変更を破棄、キャンセルで編集を続けます。 +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: データベースへのすべての変更を元に戻します (インポート、更新、編集、カスタマイズ) descBlacklist: - description: HTML Description for the global blacklist. - message: リストのURLに一致するページでは、スクリプトを実行しません。 + description: Description for the global injection blacklist. + message: 挿入のブラックリスト (一致するサイトでスクリプトを実行しない) +descBlacklistNet: + description: Description for the global network blacklist. + message: ネットワークのブラックリスト (スクリプトは一致するサイトとそのクッキーに接続しない) descCustomCSS: description: Description of custom CSS section. - message: オプションページ、およびスクリプトのインストールページ用のカスタムCSSです。この項目が何であるか把握していない場合は、編集しないようお願いします。 + message: オプションページ、またスクリプトのインストールページ用のカスタムCSSです。これが何か分からないなら編集はお控えください。 descEditorOptions: description: Description of editor options JSON section. message: >- - CodeMirrorやアドオンのカスタムオプションとして、JSONオブジェクトを {"indentUnit":2, - "smartIndent":true} の様に指定します。 いくつかのオプションは Violentmonkey - では動作しない可能性があります。 完全なリストは {"indentUnit":2, + "smartIndent":true} のように指定します。 一部の設定はViolentmonkeyでは動作しないことがあります。こちら を参照してください。 + rel="noopener noreferrer">完全な解説をご覧ください。 +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + 真偽値のオプションは値をダブルクリックして切り替えできます: true = 有効、false = + 無効。数値のオプションの単位はミリ秒です:Delay, Interval, + Rate, Time +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + 標準でないオプション: autocompleteOnTyping は、入力後に自動補完の候補を表示するミリ秒単位の遅延時間 + (0 = 無効)、killTrailingSpaceOnSave + は、保存時に各行の末尾の空白を自動削除、showTrailingSpace は、 末尾の空白をドットとして表示します。 +descScriptTemplate: + description: Description of script template section. + message: >- + 使用可能な変数: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> はMomentJS互換、例 + <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: グループ +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: 非表示 +disabledScriptsSelector: + description: Label of the option. + message: '無効化中のスクリプト:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: 表示 editHelpDocumention: description: Label in the editor help tab for the documentation link. - message: 'ユーザースクリプトのメタデータブロックと GM API に関するドキュメント:' + message: 'ユーザースクリプトのメタデータ部分と GM API の説明文書:' editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. message: キーボード ショートカット editHowToHint: description: The text of the how-to link in the editor header. - message: 別のエディタを使用しますか? + message: 好きなエディタを使うには? editLabelMeta: description: Metadata section in settings tab of script editor. - message: カスタムメタデータ + message: メタデータの設定 editLabelSettings: description: Settings section in settings tab of script editor. - message: スクリプト別の設定 + message: このスクリプト用の設定 editLongLine: description: Shown in the editor in lines that were cut due to being too long - message: '' + message: 行が長すぎます editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long - message: '' + message: |- + 行が長すぎるので、編集時の遅延防止のため折りたたまれました。 + 高度な設定にて制限を調整できます。指定例: + "maxDisplayLength": 20000 editNavCode: description: Label of code tab in script editor. message: コード @@ -148,32 +220,37 @@ editNavValues: message: 値 editValueAll: description: Button to show/edit the entire script value storage. - message: '' + message: すべて + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: キャンセル -editValueSave: - description: Button to save modification of a script value. - message: 保存 + message: スクリプトの値の保存データを表示・編集 extDescription: - description: 'Description for this extension, will be displayed in web store' - message: 多くのブラウザをサポートする、オープンソースの UserScript マネージャー + description: Description for this extension, will be displayed in web store + message: 幅広いブラウザに対応のオープンソースのユーザースクリプト管理ツール extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' - message: 設定によりブロックリストに登録されています + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: ブロックリストに登録済み failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) message: |- - Violentmonkeyはこのページでユーザースクリプトを実行しません - (一般的な例: ブラウザーUI や 拡張機能のページ) + このページでユーザースクリプトを実行できません + (一般例: ブラウザーのUI、拡張機能、ポリシーにてブロック済み、Operaの検索サイト) +failureReasonRestarted: + description: Shown in the popup. + message: Violentmonkey が再起動しました。タブを再読込みし、ユーザースクリプトを実行してください。 +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + パソコン内のファイルをインストールし追跡する方法が数種類あります:<1> 開いている Violentmonkey + のページやポップアップにファイルをドラッグ&ドロップ。ここでも可能。 <2> ファイル用のローカルのHTTPサーバーをインストールし + http://localhost 経由でファイルを開く。 <3> chrome://extensions ページを開き、Violentmonkey + の「詳細」から「ファイルのURLへのアクセスを許可」を有効化します。しかしこれは危険です。ユーザースクリプトがパソコン内のファイルを読み込む可能性があります。 filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. message: アルファベット順 @@ -183,56 +260,85 @@ filterExecutionOrder: filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: 最終更新日順 +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: すべて + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: コード + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: 名前 + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: サイズ genericError: description: Label for generic error. - message: '' + message: エラー genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: オフ genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: オン genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: グローバル設定を使用 +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: '「#」の規則は、最初にサイトを開いた時や、タブの再読込み時にのみ動作: $1' headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: ゴミ箱 +helpForLocalFile: + description: Label of the checkbox. + message: パソコン内のスクリプトをドラッグ&ドロップするための説明を表示 +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: $1 個の一致したスクリプトに対して +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. - message: URLを入力してください。 + message: 'URLを入力:' hintRecycleBin: description: Hint for recycle bin. - message: 削除されたスクリプトはここに一覧表示され、7日間保持されます。 + message: 削除したスクリプトはここに7日間表示されます。 hintUseDownloadURL: description: Shown as a place holder for @updateURL when it is not assigned message: '@downloadURL を使用' hintVacuum: description: Hint for vacuuming data. message: 冗長なデータを破棄し、キャッシュから欠落しているリソースの再読み込みを試みます。 +install: + description: Label for button to install a script. + message: インストール installFrom: description: Label for button to install script from a userscript site. message: $1 からインストール installOptionClose: description: Option to close confirm window after installation. message: インストール後にこのページを閉じる -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: このページを閉じるまでローカルファイルの変更を監視する + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before window is closed. - message: Firefox 68 以降では、ソースファイルタブを開いたままにしておく必要があります。 + message: Firefox 68 以降では、入力元ファイルのタブは開いたままにしてください。 labelAbout: description: Label shown on top of the about page. message: Violentmonkey について @@ -253,13 +359,24 @@ labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. - message: '' + message: 現在のスクリプトは、タブを再ロードするまで実行され続けます。 + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: スクリプトの更新を $1 日ごとに確認 (0 を指定すると無効) + message: スクリプト更新を $1 日ごとに確認 (0 で無効) +labelBackup: + description: Label of the import/export section in settings. + message: バックアップ + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: バックアップと管理 labelBadge: description: Label for option to show number on badge. - message: 'バッジの表示: ' + message: 'バッジ:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'バッジの色:' labelBadgeNone: description: Option to display nothing on badge. message: 何も表示しない @@ -268,7 +385,7 @@ labelBadgeTotal: message: 実行中のスクリプト数 labelBadgeUnique: description: Option to display number of unique running scripts on badge. - message: 一意な実行中のスクリプト数 + message: 個々の実行中のスクリプト数 labelBlacklist: description: Label for global blacklist settings in security section. message: ブロックリスト @@ -280,16 +397,19 @@ labelCurrentLang: message: '現在の言語: ' labelCustomCSS: description: Label for custom CSS section. - message: カスタムスタイル + message: スタイルの指定 labelDataExport: description: Section title of data export. message: エクスポート + touched: false labelDataImport: description: Section title of data import. message: インポート + touched: false labelDonate: description: Label of link to donate page. message: 寄付 + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: '更新を取得するURL:' @@ -298,10 +418,13 @@ labelEditValue: message: スクリプトの値を編集中 labelEditValueAll: description: Label shown in the panel to edit the entire script value storage. - message: '' + message: スクリプト保存データの編集 labelEditor: description: Label for Editor settings message: エディタ +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: 有効化したスクリプトのみ labelExclude: description: Label of @exclude rules. message: 除外するページのURL (@exclude) @@ -313,13 +436,18 @@ labelExportScriptData: message: スクリプトが保存したデータを含める labelExposeStatus: description: Option in advanced settings. - message: '' + message: 'ユーザスクリプト集積サイトにインストールしたバージョンを知らせる: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Firefox用の別の $1 モード +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + スクリプトのFireFox 59 + 以降での別の挿入方法で、標準の方法より高速です。同期ページモードと同じくメモリ消費量が増加するので、このオプションを使わずにスクリプトが正常に動作するなら無効にします。 labelFeedback: description: Label of link to feedback page. - message: フィードバック -labelFilterSort: - description: Label for sort filter. - message: $1 で並べ替え + message: 意見や報告 labelGeneral: description: Label for general settings. message: 基本設定 @@ -332,18 +460,23 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'ホームページのURL:' +labelIconURL: + description: Label for the input. + message: 'アイコンのURL:' labelImportScriptData: description: Option to import script data along with scripts. - message: '' + message: スクリプトデータのインポート labelImportSettings: description: Label for option to import settings from zip file. - message: Violentmonkey の設定もインポートする + message: Violentmonkey の設定もインポート labelInclude: description: Label of @include rules. message: 実行するページのURL (@include) labelInjectionMode: - description: Label for default option to inject scripts. - message: '既定のインジェクションモード: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: '挿入先: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: スクリプトのインストール @@ -362,13 +495,15 @@ labelMatch: labelName: description: Label of script name. message: 'スクリプト名:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'フレーム内での実行:' labelNoName: description: Text as the name of a script when no @name is assigned. message: 名前なし - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. - message: スクリプトは見つかりませんでした。 + message: スクリプトなし。 labelNotifyThisUpdated: description: >- A per-script option in editor to enable notification when this script is @@ -380,19 +515,21 @@ labelNotifyUpdates: message: スクリプトの更新を通知する labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. - message: スクリプトごとの通知を無視する (エディタの "設定" タブ内) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: ポップアップ内のスクリプトを $1 で並べ替え + message: スクリプトごとの通知を無視 (エディタの「設定」タブ内) labelPrivacyPolicy: description: Label of link to privacy policy - message: プライバシーポリシー + message: 個人情報保護方針 +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: スクリプトの再インストール labelRelated: description: Label of related links. message: '関連リンク: ' labelRemovedAt: description: Label for the time when the script is removed. - message: $1 に削除されました + message: $1 に削除 labelReplace: description: Label for replace input in search box. message: '置換後の文字列: ' @@ -404,7 +541,7 @@ labelRunAtDefault: message: (変更しない) labelScriptTemplate: description: Label for custom script template. - message: カスタムスクリプト (テンプレート) + message: スクリプトのテンプレート labelSearch: description: Label for search input in search box. message: '検索する文字列: ' @@ -414,6 +551,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: 設定 +labelShowOrder: + description: Label for option in dashboard -> script list + message: 実行順を表示 +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: 同期 @@ -426,6 +569,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: 確認中 +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: なし @@ -451,6 +597,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: 'ユーザ名: ' +labelTags: + description: Label for custom tags. + message: タグ (空白区切り) +labelTheme: + description: Label for the visual theme option. + message: 'テーマ:' labelTranslator: description: Label of translator. message: '翻訳者: ' @@ -462,19 +614,34 @@ labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: 1列表示 labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. - message: '' + message: 表形式 +labelWidth: + description: Width. + message: '幅:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: $1同期モード +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + スクリプトがページの読み込み前に実行される必要があり、現在その実行が遅い場合のみ有効化します。Tampermonkeyの挿入方法「即時」と同じく、これも非推奨のXHRの同期を使うため、Chrome/Chromiumでは開発者ツールのコンソールに警告が表示されます。警告は右クリックで非表示にできます。 +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (シークレットモードやクッキー無効中のサイトを除く) lastSync: description: Label for last sync timestamp. message: $1 に同期 learnBlacklist: description: Refers to a link to introduce blacklist patterns. - message: パターンの詳細 + message: 記法の詳細 learnInjectionMode: description: Refers to a link to introduce injection modes. - message: インジェクションモードについての詳細 + message: 挿入方法の詳細 menuCommands: description: Menu item to list script commands. message: ユーザスクリプトコマンド @@ -486,45 +653,61 @@ menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: '' + message: 除外… menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. - message: '' + message: |2- + 一般設定でこのオプションを有効にすると、現在のタブが自動再ロードされます。 + 他のすべてのタブに変更を適用するには、手動で再ロードしてください。 + 柔軟性を高めるには、エディタの「設定」タブを使用します。 +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: 意見や報告 menuFindScripts: description: Menu item to find scripts for a site. - message: 表示しているサイト用のスクリプトを探す + message: このサイト用のスクリプトを探す menuInjectionFailed: description: Injection error. - message: '' + message: 一部のスクリプトを挿入できませんでした。 menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' - message: '' + description: Injection error fix, shown in case the default mode is "page". + message: 「自動」モードで再試行してください +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: このサイト用の無効なスクリプト menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. - message: Sub-frames only scripts + message: フレーム用スクリプト menuMatchedScripts: description: Label for menu listing matched scripts. - message: このページで実行されるスクリプト + message: このサイト用のスクリプト menuNewScript: description: Menu item to create a new script. - message: 新しいスクリプトを作成 + message: スクリプトを新規作成 menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' - message: スクリプトは無効 + description: Menu item showing the status of Violentmonkey, when disabled. + message: スクリプト無効 menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' - message: スクリプトは有効 + description: Menu item showing the status of Violentmonkey, when enabled. + message: スクリプト有効 msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: 更新を確認中… +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: 'クリックで MomentJS の説明を開きます。可能な記法: $1. [各括弧] 内の文字列はそのままになります。' msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of the script. - message: リソースの取得中にエラーが発生しました + message: リソース取得中にエラー発生! msgErrorFetchingScript: description: Message shown when Violentmonkey fails fetching a new version of the script. - message: スクリプトの取得中にエラーが発生しました! + message: スクリプト取得中にエラー発生! msgErrorFetchingUpdateInfo: description: Message shown when Violentmonkey fails fetching version data of the script. message: 更新情報の取得に失敗しました。 @@ -532,10 +715,10 @@ msgErrorLoadingData: description: >- Message shown on confirm page when the script to be installed cannot be loaded. - message: スクリプトの取得に失敗しました。 + message: スクリプトの読み込みに失敗しました。 msgErrorLoadingDependency: description: Message shown when not all requirements are loaded successfully. - message: 依存ライブラリの取得中にエラーが発生しました。 + message: 依存ライブラリの取得中にエラー発生。 msgImported: description: >- Message shown after import. There is an argument referring to the count of @@ -545,7 +728,7 @@ msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito window. - message: シークレットモードで行った変更は、メインプロファイルにも適用されます。 + message: シークレットモードでの変更もメインプロファイルに適用されます。 msgInstalled: description: Message shown when a script is installed. message: スクリプトがインストールされました。 @@ -568,37 +751,64 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: 依存ライブラリを取得中… ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: 必要なリソースなし。 msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. - message: スクリプトの名前空間が衝突しています! @name か @namespace を変更してください。 + message: |- + スクリプトの名前空間が衝突しています! + @name か @namespace を変更してください。 +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: '@name と @namespace が同じスクリプトがインストール済みです。' msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @downloadURL is provided. - message: 新しいバージョンが見つかりました。 + message: 新しいバージョンあり。 msgNoUpdate: description: Message shown when there is no new version of a script. - message: 更新は見つかりませんでした。 + message: 更新なし。 +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: スクリプト更新時のエラー。クリックでそれを開きます。 +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: '次のスクリプトを再び保存または再インストールしてください:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (変更なし) msgSavedBlacklist: description: Message shown when blacklist are saved. message: ブロックリストを更新しました。 + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: カスタムスタイルが更新されました。 + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: エディタの設定が更新されました。 + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: カスタムスクリプト (テンプレート) が更新されました。 + touched: false msgScriptUpdated: description: Notification message for script updates. message: スクリプト「$1」が更新されました! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff - message: '' + message: 表示/非表示 msgSyncError: description: Message shown when sync failed. message: 同期エラー! @@ -608,12 +818,24 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: 初期化エラー! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: まだ認証なし。 msgSyncReady: description: Message shown when sync will start soon. message: 同期を開始中… + touched: false msgSyncing: description: Message shown when sync is in progress. message: 同期中… +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: 構文エラー? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + ページの読込み時に、スクリプトがなかったか、URLが一致しませんでした。この現象はフェイスブックやインスタグラムのような、偽装ナビゲーションを用いた単一ページ構成のアプリ風サイトで発生します。タブを再読込みし、スクリプトを実行できます。スクリプトを修正する際は、@match + をサイト全体に使用し、MutationObserver か window.navigation API から変更を検出してください。 msgUpdated: description: Message shown when a script is updated/reinstalled. message: スクリプトが更新されました。 @@ -622,33 +844,102 @@ msgUpdating: message: 更新中… noValues: description: Label shown when there is no value for current script. - message: 値は保存されていません + message: 保存された値ありません optionEditorWindow: description: Label for the option in settings - message: '' + message: ポップアップから新規ウィンドウでエディターを開く optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support onBoundsChanged - message: '' + message: エディタのウィンドウ位置は、サイズ変更または保存時にのみ記憶されます optionEditorWindowSimple: description: Label for the editor window type - message: '' + message: オムニバーを表示しない +optionPopup: + description: Label of the popup menu section in settings. + message: ポップアップメニューとアイコン optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. - message: 有効なものを先頭に表示 + message: 有効なものを先に表示 +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: 無効中のものをグループ化 + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: '@run-at の指定値ごとにグループ化' optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: 無効なものを非表示 + message: 無効中のものを非表示 + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: 無効中のものも表示 + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. - message: 有効なスクリプトを先頭に表示 + message: 有効なスクリプトを先に表示 +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'UIのテーマ:' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: 自動 +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: ダーク +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: ライト +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: 更新 +popupSettings: + description: Item inside the popup's "⋮" menu + message: 設定をポップアップ +popupSettingsHint: + description: Hint for the config button in the popup + message: '右クリック: 設定をポップアップ' +readonly: + description: Text in the editor's header for non-editable scripts. + message: 読取専用 +readonlyNote: + description: Warning when trying to type in the editor. + message: 自動更新されたスクリプトは読み取り専用です。更新を無効にするか、編集を許可するまでです。 +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: 編集を許可 +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (次の更新時に上書きされます) +reinstall: + description: Button to reinstall a script + message: 再インストール +reloadTab: + description: Label of action to reload the tab + message: タブを再読込み +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: このスクリプトがタブのURLに一致すると、変更が検出されたアクティブなタブを再読込します。 +removeAllScripts: + description: Button to remove all scripts + message: すべてのスクリプトを削除 + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: |- + すべてのスクリプトを「ごみ箱」に移動しますか? + 一定期間後に自動で空になります。またいつでも空にできます。 + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search - message: 大文字と小文字を区別する + message: 大文字と小文字を区別 searchUseRegex: description: Option to perform a regular expression search - message: 正規表現を使用する + message: 正規表現を使用 sideMenuAbout: description: 'Side menu: About' message: Violentmonkey について @@ -658,23 +949,92 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: 設定 -titleScriptUpdated: - description: Notification title for script updates. - message: 更新 +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: ユーザースクリプトなしで再読込 +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: ユーザースクリプトを無効化中。再び実行するには再読込または移動します。 +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '並び替え:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: 追跡を中止 +titleBadgeColor: + description: Tooltip for option to set badge color. + message: 通常のバッジの色 +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: 挿入不可能なサイトでのバッジの色 (ブラックリストに登録済み、未対応) titleSearchHint: description: Hover title for search icon in dashboard. message: |- - * キーでオートコンプリートの履歴にテキストを追加します。 - * サポートしている正規表現の構文: /パターン/ 及び /パターン/フラグ + * キーで自動補完の履歴に文字列を追加 + * 使用できる正規表現の構文: /条件/ や /条件/フラグ + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Enter キーは自動補完の履歴に文字列を追加 + + * すべての条件は大文字小文字を区別しない + + * 空白区切りで条件を併用できます + + * メタデータで検索: "Awesome Script" "Description" + + * タグで検索: #tag1 #tag2 + + * スクリプト名で検索: name:"awesome name" + + * スクリプトのコードを検索: code:"awesome code" + + * 検索除外: !#tag2 !name:"unwanted" + + * 正規表現: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? with space" +toggleInjection: + description: Label for a keyboard shortcut. + message: ユーザースクリプト挿入の切替 +trackEdits: + description: Button in a script installation dialog. + message: 外部での編集を追跡 +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: このページを開いておき、パソコン内ファイルの編集を追跡 +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: スクリプトを更新 ($1) +updateScript: + description: Button to update one script. + message: 更新 +updateScriptsAll: + description: Command/button to update all scripts. + message: すべてのスクリプトを更新 +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: 現在のタブのスクリプトを更新 valueLabelKey: description: Label for key of a script value. message: キー (文字列) valueLabelValue: description: Label for value of a script value. - message: 値 (JSONとしてシリアル化された) + message: 値 (JSONとしてシリアル化) valueLabelValueAll: description: Label for input of entire script value storage. + message: すべての値 (JSONとしてシリアル化) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: ウェブサイトを開く + touched: false diff --git a/src/_locales/ko/messages.yml b/src/_locales/ko/messages.yml index d488a091a8..5e05f7dea0 100644 --- a/src/_locales/ko/messages.yml +++ b/src/_locales/ko/messages.yml @@ -1,21 +1,37 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: 적용 buttonCancel: description: Cancel button on dialog. message: 취소 +buttonCheckForUpdates: + description: Button to check a script for updates. + message: 업데이트 확인 + touched: false buttonClose: description: Button to close window. message: 닫기 buttonConfirmInstallation: description: Button to confirm installation of a script. message: 설치 확인 + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: 재설치 확인 + touched: false buttonDisable: description: Button to disable a script. message: 비활성화 +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false buttonEdit: description: Button to edit a script. message: 편집 buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: '' + message: 스크립트 이름을 마우스 오른쪽 버튼 클릭, Ctrl 클릭, 휠 클릭할 수도 있습니다. buttonEmptyRecycleBin: description: Button to empty the recycle bin. message: 지금 휴지통 비우기! @@ -24,7 +40,7 @@ buttonEnable: message: 활성화 buttonExportData: description: Button to open the data export dialog. - message: zip파일로 내보내기 + message: ZIP 파일로 내보내기 buttonFilter: description: Button to show filters menu. message: 필터 @@ -34,7 +50,7 @@ buttonHome: message: 홈페이지 buttonImportData: description: Button to choose a file for data import. - message: zip에서 불러오기 + message: ZIP 파일에서 불러오기 buttonInstallFromURL: description: Button to ask for URL of a user script. message: URL에서 설치 @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: 초기화 +buttonResetSettings: + description: Button in settings page to reset all settings + message: 설정 초기화 buttonRestore: description: Button to restore a removed script. message: 복구 @@ -72,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: 저장하고 닫기 +buttonSaved: + description: Button text after saving. + message: 저장 완료 buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: 편집기 상태 보기 buttonSupport: description: Button to open support page. message: 지원 페이지 +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. - message: 되돌리기 - touched: false + message: 실행 취소 buttonUpdate: - description: Check a script for updates. - message: 업데이트 확인 + description: Button to update a script. + message: 업데이트 buttonUpdateAll: description: Check all scripts for updates. message: 모두 업데이트 확인 + touched: false buttonVacuum: description: Button to vacuum extension data. message: 데이터베이스 청소 @@ -96,15 +124,28 @@ buttonVacuumed: message: 데이터 청소됨 buttonVacuuming: description: Message shown when data vacuum is in progress. - message: 데이터를 청소하는 중... + message: 데이터 청소 중... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + 이 스크립트의 자동 업데이트가 꺼져 있습니다! + 업데이트를 진행하려면 '확인'을 누르세요. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- - 변경사항이 저자오디지 않았습니다! - 무시하려면 확인버튼을, 계속 머무르려면 취소 버튼을 눌러주세요. + 변경사항이 저장되지 않았습니다! + 무시하려면 확인 버튼을, 계속 머무르려면 취소 버튼을 눌러주세요. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: 데이터베이스의 모든 변경 사항 되돌리기 (가져오기, 업데이트, 수정, 사용자 지정) descBlacklist: - description: HTML Description for the global blacklist. + description: Description for the global injection blacklist. message: 이 목록과 일치하는 URL에는 스크립트가 삽입되지 않습니다. +descBlacklistNet: + description: Description for the global network blacklist. + message: '' descCustomCSS: description: Description of custom CSS section. message: 옵션 페이지와 스크립트 설치 페이지를 위한 커스텀 CSS입니다. 만약 이게 뭔지 모르신다면 수정하지 마세요. @@ -115,6 +156,36 @@ descEditorOptions: JSON 오브젝트로 할 수 있으며 몇몇 설정은 Violentmonkey 에서 작동하지 않을 수 있습니다. 전체 목록을 확인하세요. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + 값을 더블 클릭하여 설정을 켜고 끌 수 있습니다.true = 활성화, false = + 비활성화. 지연, 주기, 속도, 시간 등 + 수로 입력하는 설정의 단위는 밀리초입니다. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + 비표준 설정: autocompleteOnTyping으로 입력 후 자동 완성 힌트를 표시할 때까지의 지연 + 시간(밀리초)을 설정할 수 있고 (0 = 비활성화), + killTrailingSpaceOnSave는 저장할 때 후행 공백을 자동으로 제거하고, + showTrailingSpace는 후행 공백을 점으로 표시합니다. +descScriptTemplate: + description: Description of script template section. + message: >- + 지원하는 변수: MomentJS와 호환되는 <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + 형식. 예: <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: '' +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: '' +disabledScriptsSelector: + description: Label of the option. + message: '' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: '' editHelpDocumention: description: Label in the editor help tab for the documentation link. message: '유저스크립트의 메타데이터와 GM API 문서:' @@ -132,10 +203,13 @@ editLabelSettings: message: 스크립트 설정 editLongLine: description: Shown in the editor in lines that were cut due to being too long - message: '' + message: 줄이 너무 깁니다 editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long - message: '' + message: |- + 줄이 너무 길어서 편집 중 지연을 피하기 위해 텍스트가 접혀 있습니다. + 고급 설정에서 제한을 조정할 수 있습니다. 예시: + "maxDisplayLength": 20000 editNavCode: description: Label of code tab in script editor. message: 코드 @@ -148,23 +222,18 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: '' + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: 취소 -editValueSave: - description: Button to save modification of a script value. - message: 저장 + message: 전체 스크립트 값 저장소 표시/편집 extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: 유저스크립트를 실행합니다. extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: Violentmonkey 블랙리스트 목록에 추가되어 있습니다 failureReasonNoninjectable: description: >- @@ -172,7 +241,17 @@ failureReasonNoninjectable: failureBlacklistedUrl) message: |- Violentmonkey가 이 페이지의 유저스크립트를 실행하지 못했습니다 - (대표적인 예: 브라우저 UI 또는 확장 기능) + (대표적인 원인 예시: 브라우저 UI, 확장 기능, 정책, Opera 내 검색엔진 사이트) +failureReasonRestarted: + description: Shown in the popup. + message: Violentmonkey가 재시작되었습니다. 유저스크립트를 실행하려면 탭을 새로고침하세요. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + 로컬 파일의 변경사항을 적용하거나 추적하는 방법이 몇 가지 있습니다. <1> 파일을 이 페이지처럼 열려 있는 Violentmonkey + 페이지나 팝업으로 끌어다 놓습니다. <2> 이 파일의 로컬 HTTP 서버를 설치한 뒤 http://localhost 주소로 파일을 + 엽니다. <3> chrome://extensions 페이지의 확장 세부정보에서 "파일 URL에 대한 액세스 허용"을 켭니다. 이 방식은 + 모든 유저스크립트에서 모든 파일에 접근할 수 있게 되므로 위험합니다. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. message: 가나다 순 @@ -182,30 +261,58 @@ filterExecutionOrder: filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: 최근 업데이트 순 +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: 전체 + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: 코드 + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: 이름 + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: 크기 순 genericError: description: Label for generic error. - message: '' + message: 오류 genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: 끄기 genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: 켜기 genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: 전역 설정 사용 +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: '"#" 패턴은 사이트를 처음 열거나 탭을 새로 고침할 때만 작동합니다 : $1' headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: 휴지통 +helpForLocalFile: + description: Label of the checkbox. + message: 끌어다 놓은 로컬 스크립트에 대한 지침 표시 +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: 일치하는 스크립트 $1개 +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'URL 입력:' @@ -218,15 +325,16 @@ hintUseDownloadURL: hintVacuum: description: Hint for vacuuming data. message: 불필요한 중복 데이터를 버리고 캐시에 없는 리소스를 다시 불러오도록 시도합니다. +install: + description: Label for button to install a script. + message: 설치 installFrom: description: Label for button to install script from a userscript site. message: $1에서 설치 installOptionClose: description: Option to close confirm window after installation. message: 설치 후 닫기 -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: 이 창이 닫히기 전에 로컬 파일 추적 + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -252,13 +360,24 @@ labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. - message: '' + message: 탭을 새로고침할 때까지 현재 스크립트가 계속 실행됩니다. + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: 매 $1 일마다 업데이트합니다. (0 = 비활성화) + message: $1일마다 업데이트를 확인합니다. (0 = 비활성화) +labelBackup: + description: Label of the import/export section in settings. + message: 백업 + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: 백업 및 유지보수 labelBadge: description: Label for option to show number on badge. - message: 배지에 보일 숫자 + message: '배지 표시: ' +labelBadgeColors: + description: Label for option group to set badge colors. + message: '배지 색상: ' labelBadgeNone: description: Option to display nothing on badge. message: 없음 @@ -283,12 +402,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: 데이터 내보내기 + touched: false labelDataImport: description: Section title of data import. message: 데이터 불러오기 + touched: false labelDonate: description: Label of link to donate page. message: 기부 + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: '다운로드 URL:' @@ -297,10 +419,13 @@ labelEditValue: message: 스크립트 변수 수정 labelEditValueAll: description: Label shown in the panel to edit the entire script value storage. - message: 스크립트 스토리지 수정 + message: 스크립트 저장소 수정 labelEditor: description: Label for Editor settings message: 편집기 +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: 활성화된 스크립트만 labelExclude: description: Label of @exclude rules. message: '@exclude 규칙' @@ -313,12 +438,17 @@ labelExportScriptData: labelExposeStatus: description: Option in advanced settings. message: '유저스크립트 카탈로그 사이트에 설치된 버전 표시: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Firefox에서 대체 $1 모드 +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Firefox 59 이후 스크립트를 기본 모드보다 빠르게 인젝션하는 방식이 도입되었습니다. "페이지 동기화 + 모드"처럼 메모리 사용량이 증가하므로, 이 옵션 없이도 스크립트가 올바르게 동작한다면 끄실 수 있습니다. labelFeedback: description: Label of link to feedback page. message: 피드백 -labelFilterSort: - description: Label for sort filter. - message: $1에 따라 정렬 labelGeneral: description: Label for general settings. message: 일반 @@ -331,9 +461,12 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: '홈페이지 URL:' +labelIconURL: + description: Label for the input. + message: '' labelImportScriptData: description: Option to import script data along with scripts. - message: '' + message: 스크립트 데이터 가져오기 labelImportSettings: description: Label for option to import settings from zip file. message: 불러오기 설정 @@ -341,8 +474,10 @@ labelInclude: description: Label of @include rules. message: '@include 규칙' labelInjectionMode: - description: Label for default option to inject scripts. - message: '기본 인젝션 모드: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: '인젝션 모드: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: 스크립트 설치 @@ -361,10 +496,12 @@ labelMatch: labelName: description: Label of script name. message: '이름:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: '프레임에서 실행:' labelNoName: description: Text as the name of a script when no @name is assigned. message: 이름 없음 - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: 스크립트를 찾을 수 없습니다. @@ -380,12 +517,14 @@ labelNotifyUpdates: labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: 각 스크립트의 알림 설정을 무시하고 전역 설정 사용 -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: 팝업에서 $1 순서로 스크립트 나열하기 labelPrivacyPolicy: description: Label of link to privacy policy message: 개인정보 보호정책 +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: 스크립트 재설치 중 labelRelated: description: Label of related links. message: '관련된 링크: ' @@ -409,10 +548,16 @@ labelSearch: message: '찾을 내용: ' labelSearchScript: description: Placeholder for script search box. - message: 스크립트를 검색해보세요... + message: 스크립트 검색... labelSettings: description: Label shown on the top of settings page message: 설정 +labelShowOrder: + description: Label for option in dashboard -> script list + message: 실행 순서 표시 +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: 동기화 @@ -424,7 +569,10 @@ labelSyncAuthorize: message: 인증 labelSyncAuthorizing: description: Label for button when authorization is in progress. - message: 인증중 + message: 인증 중 +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: 없음 @@ -450,6 +598,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: '사용자: ' +labelTags: + description: Label for custom tags. + message: '태그 (공백으로 구분):' +labelTheme: + description: Label for the visual theme option. + message: '테마: ' labelTranslator: description: Label of translator. message: '번역자: ' @@ -461,10 +615,28 @@ labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: 1열 형식 labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. + message: 표 형식 +labelWidth: + description: Width. message: '' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: $1 동기화 모드 +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + 페이지 로드를 시작하기 전에 실행해야 하는 스크립트가 있고 현재 너무 늦게 실행되고 있는 경우에만 활성화합니다. Tampermonkey의 + Instant injection 모드처럼, 이 옵션은 더 이상 사용되지 않는 동기식 XHR을 사용하므로 Chrome/Chromium에서 + devtools 콘솔에 경고가 표시됩니다, 이 경우 부작용은 무시해도 됩니다. 경고를 마우스 오른쪽 버튼으로 클릭하여 영구적으로 숨길 수 + 있습니다. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (시크릿 모드 및 쿠키를 끈 사이트 제외) lastSync: description: Label for last sync timestamp. message: $1에 마지막으로 동기화됨 @@ -485,37 +657,53 @@ menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: '' + message: 제외... menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. - message: '' + message: |- + 일반 설정에서 이 옵션을 활성화한 경우 현재 탭이 자동으로 새로고침됩니다. + 다른 모든 탭에 변경 사항을 적용하려면 수동으로 새로고침하세요. + 더 많은 유연성을 위해 편집기의 '설정' 탭을 사용하세요. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: 피드백 menuFindScripts: description: Menu item to find scripts for a site. - message: 이 사이트를 위한 스크립트 찾기 + message: 이 사이트의 스크립트 찾기 menuInjectionFailed: description: Injection error. message: 일부 스크립트를 삽입할 수 없습니다. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: '"자동" 모드로 재시도' +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: 일치하는 비활성화된 스크립트 menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. - message: 하위 프레임 스크립트들 + message: 하위 프레임 전용 스크립트 menuMatchedScripts: description: Label for menu listing matched scripts. - message: 일치하는 스크립트들 + message: 일치하는 스크립트 menuNewScript: description: Menu item to create a new script. message: 새 스크립트 만들기 menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' - message: 스크립트들 비활성화됨 + description: Menu item showing the status of Violentmonkey, when disabled. + message: 스크립트 비활성화됨 menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' - message: 스크립트들 활성화됨 + description: Menu item showing the status of Violentmonkey, when enabled. + message: 스크립트 활성화됨 msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: 업데이트 확인중... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: 'MomentJS 문서를 열려면 클릭합니다. 허용된 토큰 : $1. 리터럴 텍스트를 보호하려면 [대괄호]를 사용하세요.' msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -544,7 +732,7 @@ msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito window. - message: '' + message: 시크릿 모드에서 변경한 사항은 기본 프로필에도 적용됩니다. msgInstalled: description: Message shown when a script is installed. message: 스크립트 설치됨. @@ -567,11 +755,21 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: 의존성을 불러오는 중... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: 요구되는 리소스가 손실됨. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. message: 스크립트의 네임스페이스가 충돌했습니다! @name과 @namespace를 수정해주세요. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: '@name과 @namespace가 동일한 스크립트가 이미 설치되었습니다.' msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -580,74 +778,169 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: 업데이트 없음. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: 스크립트 업데이트 오류. 클릭해서 열기. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 스크립트를 재설치하시거나 다시 저장하세요. +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: ' (변경 없음)' msgSavedBlacklist: description: Message shown when blacklist are saved. message: 블랙리스트 업데이트됨. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: 커스텀 스타일 업데이트됨. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: 편집기 설정이 업데이트됨. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: 스크립트 템플릿이 업데이트됨. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: '스크립트 [$1]이(가) 업데이트됨!' + message: 스크립트 [$1]이(가) 업데이트됨! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff - message: '' + message: 표시/숨김 msgSyncError: description: Message shown when sync failed. message: 동기화 오류! msgSyncInit: description: Message shown when sync service is initializing. - message: 초기화중... + message: 초기화 중... msgSyncInitError: description: Message shown when sync fails in initialization. message: 초기화 오류! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: 아직 승인되지 않았습니다. msgSyncReady: description: Message shown when sync will start soon. message: 동기화가 곧 시작합니다.... + touched: false msgSyncing: description: Message shown when sync is in progress. - message: 동기화 진행중... + message: 동기화 진행 중... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: 구문 오류? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + 페이지가 로드될 때 스크립트가 존재하지 않거나 URL과 일치하지 않는데, 이는 페이크 내비게이션을 사용하는 페이스북이나 인스타그램과 같은 + 단일 페이지 애플리케이션 사이트에서 발생합니다. 탭을 새로고침하여 스크립트를 실행할 수 있습니다. 스크립트를 수정하려면 전체 사이트에 + @match를 사용한 다음 MutationObserver 또는 window.navigation API를 사용하여 변경 사항을 감지합니다. msgUpdated: description: Message shown when a script is updated/reinstalled. message: 스크립트 업데이트됨. msgUpdating: description: Message shown when a new version of script is being fetched. - message: 업데이트중... + message: 업데이트 중... noValues: description: Label shown when there is no value for current script. message: 저장된 값이 없습니다. optionEditorWindow: description: Label for the option in settings - message: '' + message: 새 창의 팝업에서 편집기 열기 optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support onBoundsChanged - message: '' + message: 편집기 창 위치는 크기를 조정하거나 저장할 때만 기억됩니다. optionEditorWindowSimple: description: Label for the editor window type - message: '' + message: omnibox 숨기기 +optionPopup: + description: Label of the popup menu section in settings. + message: 팝업 메뉴 및 아이콘 optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: 활성화된 것 먼저 +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: 비활성화된 스크립트 묶기 + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: '@run-at stage로 묶기' optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: 비활성화된 것 숨기기 + message: 비활성화된 스크립트 숨김 + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: 비활성화된 스크립트 표시 + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: 활성화된 스크립트 먼저 보여주기 +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'UI 테마: ' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: 자동 +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: 다크 +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: 라이트 +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: 업데이트 +popupSettings: + description: Item inside the popup's "⋮" menu + message: '' +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: 읽기 전용 +readonlyNote: + description: Warning when trying to type in the editor. + message: 자동 업데이트된 스크립트는 업데이트를 끄거나 편집을 허용하기 전까지 읽기 전용 상태입니다. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: 편집 허용 +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (다음 업데이트에서 편집 사항을 덮어씀) +reinstall: + description: Button to reinstall a script + message: 재설치 +reloadTab: + description: Label of action to reload the tab + message: 탭 새로고침 +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: 켜진 경우, 활성화된 탭의 URL에 해당하는 스크립트가 바뀌면 탭을 새로고침합니다. +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search - message: 대소문자 확인하기 + message: 대소문자 확인 searchUseRegex: description: Option to perform a regular expression search - message: 정규표현식 사용하기 + message: 정규표현식 사용 sideMenuAbout: description: 'Side menu: About' message: 정보 @@ -657,14 +950,76 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: 설정 -titleScriptUpdated: - description: Notification title for script updates. - message: 업데이트 +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: 유저스크립트를 끄고 페이지 새로고침 +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: 이 페이지에서 유저스크립트를 비활성화했습니다. 스크립트를 실행하려면 탭을 새로고침하거나 탐색하세요. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: 추적 중단 +titleBadgeColor: + description: Tooltip for option to set badge color. + message: 일반 배지 색상 +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: 주입할 수 없는 사이트의 배지 색상(블랙리스트 또는 지원되지 않음) titleSearchHint: description: Hover title for search icon in dashboard. message: |- * 키로 자동완성 내역에 추가할 수 있습니다. * 정규표현식을 지원합니다: /re/ 또는 /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Enter 키를 누르면 텍스트가 자동 완성 내역에 추가됩니다 + + * 모든 조건은 대소문자를 구분하지 않습니다 + + * 조건은 공백으로 구분해 여러 개 입력할 수 있습니다 + + * 메타데이터 검색: "Awesome Script" "Description" + + * 태그 검색: #tag1 #tag2 + + * 스크립트 이름 검색: name:"awesome name" + + * 스크립트 코드 검색: code:"awesome code" + + * 검색에서 제외: !#tag2 !name:"unwanted" + + * 정규표현식: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? with space" +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: 외부 편집 추적 +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: 로컬 파일의 편집을 추적하려면 이 페이지를 계속 열어두세요 +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: 스크립트 업데이트 ($1) +updateScript: + description: Button to update one script. + message: 업데이트 +updateScriptsAll: + description: Command/button to update all scripts. + message: '' +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: '' valueLabelKey: description: Label for key of a script value. message: 키 (문자열) @@ -674,6 +1029,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: 모든 값 (JSON 직렬화) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: 웹 사이트 방문 + touched: false diff --git a/src/_locales/lv/messages.yml b/src/_locales/lv/messages.yml new file mode 100644 index 0000000000..842f2bd9c7 --- /dev/null +++ b/src/_locales/lv/messages.yml @@ -0,0 +1,985 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: '' +buttonCancel: + description: Cancel button on dialog. + message: Atcelt +buttonCheckForUpdates: + description: Button to check a script for updates. + message: '' + touched: false +buttonClose: + description: Button to close window. + message: Aizvērt +buttonConfirmInstallation: + description: Button to confirm installation of a script. + message: '' + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: '' + touched: false +buttonDisable: + description: Button to disable a script. + message: Atspējot +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false +buttonEdit: + description: Button to edit a script. + message: Rediģēt +buttonEditClickHint: + description: Tooltip for the Edit button in popup. + message: '' +buttonEmptyRecycleBin: + description: Button to empty the recycle bin. + message: '' +buttonEnable: + description: Button to enable a script. + message: Iespējot +buttonExportData: + description: Button to open the data export dialog. + message: Eksportēt kā zip arhīvu +buttonFilter: + description: Button to show filters menu. + message: '' + touched: false +buttonHome: + description: Button to open homepage. + message: Mājas lapa +buttonImportData: + description: Button to choose a file for data import. + message: Importēt zip arhīvu +buttonInstallFromURL: + description: Button to ask for URL of a user script. + message: Uzstādīt no URL +buttonInstallOptions: + description: Button to show options of installation confirm page. + message: '' + touched: false +buttonNew: + description: Button to create a new script. + message: Jauns +buttonOK: + description: OK button on dialog. + message: Labi +buttonRecycleBin: + description: Button to list scripts in recycle bin. + message: '' +buttonRemove: + description: Button to remove a script. + message: '' +buttonReplace: + description: Button to replace the current match. + message: '' +buttonReplaceAll: + description: Button to replace all matches. + message: '' +buttonReset: + description: Button to reset to default values. + message: '' +buttonResetSettings: + description: Button in settings page to reset all settings + message: '' +buttonRestore: + description: Button to restore a removed script. + message: Atjaunot +buttonSave: + description: Button to save modifications of a script. + message: Saglabāt +buttonSaveClose: + description: Button to save modifications of a script and then close the editing page. + message: Saglabāt & Aizvērt +buttonSaved: + description: Button text after saving. + message: Saglabāts +buttonShowEditorState: + description: Button to show the list of currently used CodeMirror options. + message: '' +buttonSupport: + description: Button to open support page. + message: Atbalsta lapa +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' +buttonUndo: + description: Button to undo removement of a script. + message: Atsaukt +buttonUpdate: + description: Button to update a script. + message: Atjaunināt +buttonUpdateAll: + description: Check all scripts for updates. + message: '' + touched: false +buttonVacuum: + description: Button to vacuum extension data. + message: '' +buttonVacuumed: + description: Message shown when data is vacuumed. + message: '' +buttonVacuuming: + description: Message shown when data vacuum is in progress. + message: '' +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: '' +confirmNotSaved: + description: Confirm message shown when there are unsaved script modifications. + message: '' +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: '' +descBlacklist: + description: Description for the global injection blacklist. + message: '' +descBlacklistNet: + description: Description for the global network blacklist. + message: '' +descCustomCSS: + description: Description of custom CSS section. + message: '' +descEditorOptions: + description: Description of editor options JSON section. + message: '' +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: '' +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: '' +descScriptTemplate: + description: Description of script template section. + message: '' +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: '' +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: '' +disabledScriptsSelector: + description: Label of the option. + message: 'Atspējotie skripti:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: '' +editHelpDocumention: + description: Label in the editor help tab for the documentation link. + message: '' +editHelpKeyboard: + description: Label in the editor help tab for the keyboard shortcuts. + message: 'Īsinājumtaustiņi:' +editHowToHint: + description: The text of the how-to link in the editor header. + message: '' +editLabelMeta: + description: Metadata section in settings tab of script editor. + message: '' +editLabelSettings: + description: Settings section in settings tab of script editor. + message: Skripta iestatījumi +editLongLine: + description: Shown in the editor in lines that were cut due to being too long + message: Rinda ir pārāk gara +editLongLineTooltip: + description: Tooltip shown in the editor in lines that were cut due to being too long + message: '' +editNavCode: + description: Label of code tab in script editor. + message: '' +editNavSettings: + description: Label of settings tab in script editor. + message: Iestatījumi +editNavValues: + description: Label of values tab in script editor. + message: '' +editValueAll: + description: Button to show/edit the entire script value storage. + message: '' + touched: false +editValueAllHint: + description: Tooltip for the 'all' button. + message: '' +extDescription: + description: Description for this extension, will be displayed in web store + message: '' +extName: + description: Name of this extension. + message: Violentmonkey +failureReasonBlacklisted: + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: '' +failureReasonNoninjectable: + description: >- + Shown for URLs that cannot be processed (same places as + failureBlacklistedUrl) + message: '' +failureReasonRestarted: + description: Shown in the popup. + message: '' +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: '' +filterAlphabeticalOrder: + description: Label for option to sort scripts in alphabetical order. + message: alfabētiski +filterExecutionOrder: + description: Label for option to sort scripts in execution order. + message: '' +filterLastUpdateOrder: + description: Label for option to sort scripts by last update time. + message: '' +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' +filterScopeAll: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterScopeCode: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterScopeName: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: izmēra +genericError: + description: Label for generic error. + message: Kļūda +genericOff: + description: To indicate something is turned off or disabled, similar to "no". + message: izslēgts +genericOn: + description: To indicate something is turned on or enabled, similar to "yes". + message: ieslēgts +genericUseGlobal: + description: To indicate some per-script option will use its analog in global settings. + message: '' +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: '' +headerRecycleBin: + description: Text shown in the header when scripts in recycle bin are listed. + message: '' +helpForLocalFile: + description: Label of the checkbox. + message: '' +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: '' +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' +hintInputURL: + description: Hint for a prompt box to input URL of a user script. + message: '' +hintRecycleBin: + description: Hint for recycle bin. + message: '' +hintUseDownloadURL: + description: Shown as a place holder for @updateURL when it is not assigned + message: '' +hintVacuum: + description: Hint for vacuuming data. + message: '' +install: + description: Label for button to install a script. + message: Uzstādīt +installFrom: + description: Label for button to install script from a userscript site. + message: Uzstādīt no $1 +installOptionClose: + description: Option to close confirm window after installation. + message: '' + touched: false +installOptionTrackTooltip: + description: >- + Tooltip in Firefox 68+ for the option to track the loading local file before + window is closed. + message: '' +labelAbout: + description: Label shown on top of the about page. + message: '' + touched: false +labelAdvanced: + description: Label for button to show advanced settings. + message: '' +labelAllowUpdate: + description: Option to allow checking updates for a script. + message: Atļaut atjaunināt +labelAuthor: + description: Label of author shown in the details of a script. + message: 'Izstrādāja: ' +labelAutoReloadCurrentTab: + description: Option to reload current tab after a script is switched on or off from menu. + message: '' +labelAutoReloadCurrentTabDisabled: + description: >- + Tooltip in menu after Violentmonkey is switched off if auto-reload is + disabled. + message: '' + touched: false +labelAutoUpdate: + description: Option to allow automatically checking scripts for updates + message: '' +labelBackup: + description: Label of the import/export section in settings. + message: '' + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Dublēšana un apkope +labelBadge: + description: Label for option to show number on badge. + message: '' +labelBadgeColors: + description: Label for option group to set badge colors. + message: '' +labelBadgeNone: + description: Option to display nothing on badge. + message: '' +labelBadgeTotal: + description: Option to display total number of running scripts on badge. + message: '' +labelBadgeUnique: + description: Option to display number of unique running scripts on badge. + message: '' +labelBlacklist: + description: Label for global blacklist settings in security section. + message: '' +labelContributors: + description: Label for link to contributors. + message: '' +labelCurrentLang: + description: Label of current language. + message: '' +labelCustomCSS: + description: Label for custom CSS section. + message: '' +labelDataExport: + description: Section title of data export. + message: '' + touched: false +labelDataImport: + description: Section title of data import. + message: '' + touched: false +labelDonate: + description: Label of link to donate page. + message: '' + touched: false +labelDownloadURL: + description: Label of script @downloadURL in custom meta data. + message: 'Lejupielādes URL:' +labelEditValue: + description: Label shown in the panel to edit a script value. + message: '' +labelEditValueAll: + description: Label shown in the panel to edit the entire script value storage. + message: '' +labelEditor: + description: Label for Editor settings + message: '' +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: tikai iespējotos skriptus +labelExclude: + description: Label of @exclude rules. + message: '' +labelExcludeMatch: + description: Label of @exclude-match rules. + message: '' +labelExportScriptData: + description: Option to export script data along with scripts. + message: Eksportēt skripta datus +labelExposeStatus: + description: Option in advanced settings. + message: '' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: '' +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: '' +labelFeedback: + description: Label of link to feedback page. + message: '' +labelGeneral: + description: Label for general settings. + message: '' +labelHelpTranslate: + description: Label for link to localization guide in about tab + message: Palīdziet iztulkot +labelHomepage: + description: Label for home page in about tab. + message: Mājas lapa +labelHomepageURL: + description: Label of script @homepageURL in custom meta data. + message: '' +labelIconURL: + description: Label for the input. + message: 'Ikonas URL:' +labelImportScriptData: + description: Option to import script data along with scripts. + message: Ievietot skripta datus +labelImportSettings: + description: Label for option to import settings from zip file. + message: Importēt iestatījumus +labelInclude: + description: Label of @include rules. + message: '' +labelInjectionMode: + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: '' +labelInstall: + description: Shown in the title of the confirm page while trying to install a script. + message: '' +labelKeepOriginal: + description: Option to keep the original match or ignore rules. + message: '' +labelLastUpdatedAt: + description: Label shown on last updated time. + message: '' +labelLineNumber: + description: Label for line number jumper. + message: 'Rindas Nr.: ' +labelMatch: + description: Label of @match rules. + message: '' +labelName: + description: Label of script name. + message: 'Nosaukums:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: '' +labelNoName: + description: Text as the name of a script when no @name is assigned. + message: '' +labelNoSearchScripts: + description: Message shown when no script is found in search results. + message: '' +labelNotifyThisUpdated: + description: >- + A per-script option in editor to enable notification when this script is + updated. The text follows "Allow update" checkbox option so it's like a + continuation of the phrase. + message: '' +labelNotifyUpdates: + description: Option to show notification when script is updated. + message: Paziņot par skriptu atjauninājumiem +labelNotifyUpdatesGlobal: + description: Option to prioritize global notification option over script's setting. + message: '' +labelPrivacyPolicy: + description: Label of link to privacy policy + message: Konfidencialitātes politika +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: '' +labelRelated: + description: Label of related links. + message: '' +labelRemovedAt: + description: Label for the time when the script is removed. + message: '' +labelReplace: + description: Label for replace input in search box. + message: '' +labelRunAt: + description: Label of script @run-at properties in custom meta data. + message: '' +labelRunAtDefault: + description: Shown when custom @run-at is not assigned. + message: '' +labelScriptTemplate: + description: Label for custom script template. + message: '' +labelSearch: + description: Label for search input in search box. + message: '' +labelSearchScript: + description: Placeholder for script search box. + message: '' +labelSettings: + description: Label shown on the top of settings page + message: Iestatījumi +labelShowOrder: + description: Label for option in dashboard -> script list + message: '' +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' +labelSync: + description: Label for sync options. + message: '' +labelSyncAnonymous: + description: Label for using anonymous account. + message: Izmantot anonīmu kontu +labelSyncAuthorize: + description: Label for button to authorize a service. + message: '' +labelSyncAuthorizing: + description: Label for button when authorization is in progress. + message: '' +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' +labelSyncDisabled: + description: Label for option to disable sync service. + message: '' +labelSyncPassword: + description: Label for input to hold password. + message: 'Parole: ' +labelSyncReauthorize: + description: Option to reauthorize sync service when expired. + message: '' + touched: false +labelSyncRevoke: + description: Label for button to revoke authorization for a service. + message: '' +labelSyncScriptStatus: + description: Label for option to sync script status. + message: '' +labelSyncServerUrl: + description: Label for input to hold server URL. + message: '' +labelSyncService: + description: Label for sync service select. + message: '' +labelSyncUsername: + description: Label for input to hold username. + message: 'Lietotājvārds: ' +labelTags: + description: Label for custom tags. + message: 'Birkas (atdalīt ar atstarpi):' +labelTheme: + description: Label for the visual theme option. + message: 'Motīvs: ' +labelTranslator: + description: Label of translator. + message: '' + touched: false +labelUpdateURL: + description: Label of script @updateURL in custom meta data. + message: '' +labelViewSingleColumn: + description: >- + Label for option in dashboard script list to show the scripts in single + column. + message: Viena kolonna +labelViewTable: + description: Label for option in dashboard script list to show the scripts as a table. + message: Tabulas skats +labelWidth: + description: Width. + message: 'Platums:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: '' +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: '' +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: '' +lastSync: + description: Label for last sync timestamp. + message: '' +learnBlacklist: + description: Refers to a link to introduce blacklist patterns. + message: '' +learnInjectionMode: + description: Refers to a link to introduce injection modes. + message: '' +menuCommands: + description: Menu item to list script commands. + message: '' + touched: false +menuDashboard: + description: Label for menu item to open dashboard. + message: '' +menuExclude: + description: >- + Shown in popup menu after clicking script's "..." dropdown so try to keep + the label short. + message: '' +menuExcludeHint: + description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. + message: '' +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: '' +menuFindScripts: + description: Menu item to find scripts for a site. + message: '' +menuInjectionFailed: + description: Injection error. + message: '' +menuInjectionFailedFix: + description: Injection error fix, shown in case the default mode is "page". + message: '' +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: '' +menuMatchedFrameScripts: + description: Label for menu listing matching scripts in sub-frames. + message: '' +menuMatchedScripts: + description: Label for menu listing matched scripts. + message: '' +menuNewScript: + description: Menu item to create a new script. + message: Izveidot jaunu skriptu +menuScriptDisabled: + description: Menu item showing the status of Violentmonkey, when disabled. + message: Skripti atspējoti +menuScriptEnabled: + description: Menu item showing the status of Violentmonkey, when enabled. + message: Skripti iespējoti +msgCheckingForUpdate: + description: Message shown when a script is being checked for updates by version numbers. + message: '' +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: '' +msgErrorFetchingResource: + description: >- + Message shown when Violentmonkey fails fetching a resource/require/icon of + the script. + message: '' +msgErrorFetchingScript: + description: Message shown when Violentmonkey fails fetching a new version of the script. + message: '' +msgErrorFetchingUpdateInfo: + description: Message shown when Violentmonkey fails fetching version data of the script. + message: '' +msgErrorLoadingData: + description: >- + Message shown on confirm page when the script to be installed cannot be + loaded. + message: '' +msgErrorLoadingDependency: + description: Message shown when not all requirements are loaded successfully. + message: '' +msgImported: + description: >- + Message shown after import. There is an argument referring to the count of + scripts imported. + message: '' +msgIncognitoChanges: + description: >- + Message shown in popup and installation tab when opened in an incognito + window. + message: '' +msgInstalled: + description: Message shown when a script is installed. + message: Skripts uzstādīts. +msgInvalidScript: + description: Message shown when script is invalid. + message: '' +msgLoadedData: + description: >- + Message shown in the confirm page when a javascript file to be installed is + loaded. + message: '' + touched: false +msgLoading: + description: Message shown in the options page before script list is loaded. + message: '' + touched: false +msgLoadingData: + description: Message shown on confirm page when the script to be installed is loading. + message: '' +msgLoadingDependency: + description: Message shown on confirm page when the requirements are being downloaded. + message: '' +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: '' +msgNamespaceConflict: + description: >- + Message shown when namespace of the new script conflicts with an existent + one. + message: '' +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: '' +msgNewVersion: + description: >- + Message shown when a new version of script is found by @updateURL, but no + @downloadURL is provided. + message: Pieejama jauna versija. +msgNoUpdate: + description: Message shown when there is no new version of a script. + message: '' +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: '' +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: '' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: '' +msgSavedBlacklist: + description: Message shown when blacklist are saved. + message: '' + touched: false +msgSavedCustomCSS: + description: Message shown when custom CSS is saved. + message: '' + touched: false +msgSavedEditorOptions: + description: Message shown when editor options are saved. + message: '' + touched: false +msgSavedScriptTemplate: + description: Message shown when custom script template is saved. + message: '' + touched: false +msgScriptUpdated: + description: Notification message for script updates. + message: '' +msgShowHide: + description: Tooltip or text shown on a toggle that shows/hides stuff + message: '' +msgSyncError: + description: Message shown when sync failed. + message: '' +msgSyncInit: + description: Message shown when sync service is initializing. + message: '' +msgSyncInitError: + description: Message shown when sync fails in initialization. + message: '' +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: '' +msgSyncReady: + description: Message shown when sync will start soon. + message: '' + touched: false +msgSyncing: + description: Message shown when sync is in progress. + message: '' +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: '' +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: '' +msgUpdated: + description: Message shown when a script is updated/reinstalled. + message: Skripts atjaunināts. +msgUpdating: + description: Message shown when a new version of script is being fetched. + message: '' +noValues: + description: Label shown when there is no value for current script. + message: '' +optionEditorWindow: + description: Label for the option in settings + message: '' +optionEditorWindowHint: + description: >- + Tooltip for optionEditorWindow in case the browser doesn't support + onBoundsChanged + message: '' +optionEditorWindowSimple: + description: Label for the editor window type + message: '' +optionPopup: + description: Label of the popup menu section in settings. + message: '' +optionPopupEnabledFirst: + description: Option to show enabled scripts first in popup. + message: '' +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: '' + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: '' +optionPopupHideDisabled: + description: Option to hide disabled scripts in popup. + message: '' + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: '' + touched: false +optionShowEnabledFirst: + description: Option to show enabled scripts first in alphabetical order. + message: Vispirms rādīt iespējotos skriptus +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: '' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: '' +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Atjaunināt +popupSettings: + description: Item inside the popup's "⋮" menu + message: Uznirstošo logu iestatījumi +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: '' +readonlyNote: + description: Warning when trying to type in the editor. + message: '' +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: '' +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: '' +reinstall: + description: Button to reinstall a script + message: '' +reloadTab: + description: Label of action to reload the tab + message: Pārlādēt cilni +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: '' +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false +searchCaseSensitive: + description: Option to perform a case-sensitive search + message: Reģistrjutīgs +searchUseRegex: + description: Option to perform a regular expression search + message: Lietot regulāro izteiksmi +sideMenuAbout: + description: 'Side menu: About' + message: Par +sideMenuInstalled: + description: 'Side menu: Installed scripts' + message: Uzstādītie skripti +sideMenuSettings: + description: 'Side menu: Settings' + message: Iestatījumi +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Pārlādēt lapu bez lietotājskriptiem +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Jūs atspējojāt lietotājskriptus šai lapai. Lai tos izmantotu, atkārtoti + ielādējiet vai pārvietojieties pa cilni. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Kārtot pēc:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: '' +titleBadgeColor: + description: Tooltip for option to set badge color. + message: '' +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: '' +titleSearchHint: + description: Hover title for search icon in dashboard. + message: '' + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: '' +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: '' +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: '' +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: '' +updateScript: + description: Button to update one script. + message: Atjaunināt +updateScriptsAll: + description: Command/button to update all scripts. + message: '' +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: '' +valueLabelKey: + description: Label for key of a script value. + message: '' +valueLabelValue: + description: Label for value of a script value. + message: '' +valueLabelValueAll: + description: Label for input of entire script value storage. + message: '' +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' +visitWebsite: + description: Label for link to open Violentmonkey website. + message: '' + touched: false diff --git a/src/_locales/nl/messages.yml b/src/_locales/nl/messages.yml index dbddb18b5c..fec961b92f 100644 --- a/src/_locales/nl/messages.yml +++ b/src/_locales/nl/messages.yml @@ -1,21 +1,37 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Toepassen buttonCancel: description: Cancel button on dialog. message: Annuleren +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Controleren op updates + touched: false buttonClose: description: Button to close window. message: Sluiten buttonConfirmInstallation: description: Button to confirm installation of a script. message: Installatie bevestigen + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Herinstallatie bevestigen + touched: false buttonDisable: description: Button to disable a script. message: Uitschakelen +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: 'Thema''s downloaden:' + touched: false buttonEdit: description: Button to edit a script. message: Bewerken buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: 'Je kunt tevens rechts-, middel- of Ctrl-klikken op de scriptnaam.' + message: Je kunt tevens rechts-, middel- of Ctrl-klikken op de scriptnaam. buttonEmptyRecycleBin: description: Button to empty the recycle bin. message: Leeg de prullenbak! @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Standaardwaarden +buttonResetSettings: + description: Button in settings page to reset all settings + message: Standaardwaarden buttonRestore: description: Button to restore a removed script. message: Herstellen @@ -72,39 +91,65 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Opslaan en sluiten +buttonSaved: + description: Button text after saving. + message: Opgeslagen buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Bewerkstatus tonen buttonSupport: description: Button to open support page. message: Ondersteuningspagina +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: Eenmalig lokaal ophalen +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: Eenmalig versturen naar externe bron buttonUndo: description: Button to undo removement of a script. - message: '' - touched: false + message: Ongedaan maken buttonUpdate: - description: Check a script for updates. - message: Controleren op updates + description: Button to update a script. + message: Bijwerken buttonUpdateAll: description: Check all scripts for updates. message: Alle scripts controleren op updates + touched: false buttonVacuum: description: Button to vacuum extension data. - message: Databank opschonen + message: Gegevens opschonen buttonVacuumed: description: Message shown when data is vacuumed. - message: De databank is opgeschoond + message: Gegevens zijn opgeschoond buttonVacuuming: description: Message shown when data vacuum is in progress. - message: Bezig met opschonen... + message: Gegevens aan het opschonen... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Automatisch bijwerken van dit script is uitgeschakeld! + Klik op oké om tóch bij te werken. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- De aanpassingen zijn niet opgeslagen! Klik op oké om ze te verwerpen of annuleren om door te gaan. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Herstel alle wijzigingen aan de databank (importeren, bijwerken, bewerken en + aanpassen) descBlacklist: - description: HTML Description for the global blacklist. - message: Scripts worden niet uitgevoerd op url's die op deze lijst staan. + description: Description for the global injection blacklist. + message: Zwarte injectielijst (scripts worden niet op deze sites uitgevoerd) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Zwarte netwerklijst (scripts maken geen verbinding met deze sites en hun + cookies) descCustomCSS: description: Description of custom CSS section. message: >- @@ -115,19 +160,46 @@ descEditorOptions: message: >- Aangepaste instellingen voor CodeMirror en uitbreidingen in json-objecten, zoals {"indentUnit":2, "smartIndent":true}. Let op: niet alle - werken gegarandeerd in Violentmonkey - bekijk de volledige lijst. Je kunt een aangepast thema - gebruiken door de bestandsnaam ervan in te voeren. Voorbeeld: "theme": - "3024-day". De inhoud van het css-bestand kun je hieronder - plakken bij 'Aangepaste stijl'. + rel="noopener noreferrer">volledige lijst. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Boolean opties kunnen worden omgeschakeld door te dubbelklikken op de + waarde: true = ingeschakeld, false = + uitgeschakeld. Numerieke opties die eindigen op Delay, + Interval, Rate, Time zijn in + milliseconden. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Niet-standaard opties: autocompleteOnTyping is een vertraging + in milliseconden om autocomplete hints weer te geven na het typen van + (0 = uitschakelen), killTrailingSpaceOnSave + verwijdert automatisch spaties in elke regel bij het opslaan, + showTrailingSpace toont de achterliggende spaties als punten. +descScriptTemplate: + description: Description of script template section. + message: >- + Ondersteunde variabelen: <{{name}}>, <{{url}}>, <{{date}}>, + <{{date:format}}> met een met MomentJS compatibel formaat, bijvoorbeeld + <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: groep +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: verbergen +disabledScriptsSelector: + description: Label of the option. + message: 'Uitgeschakelde scripts:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: tonen editHelpDocumention: description: Label in the editor help tab for the documentation link. - message: >- - Documentatie over gebruikersscript-metagegevensblokken en GM - API: + message: 'Documentatie over gebruikersscript metagegevensblok en GM API:' editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. message: 'Sneltoetsen:' @@ -164,64 +236,109 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: Alles + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: Alle scriptwaarden tonen/aanpassen -editValueCancel: - description: Button to cancel modification of a script value. - message: Annuleren -editValueSave: - description: Button to save modification of a script value. - message: Opslaan extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: Een open source gebruikersscriptbeheerder voor diverse webbrowsers extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: Staat op Violentmonkey's zwarte lijst failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) - message: |- + message: >- Violentmonkey kan geen gebruikersscripts uitvoeren op deze pagina - (bijv. interne of extensiepagina's) + + (bijv. interne of extensiepagina's, of vanwege een blokkadebeleid of + bepaalde Opera-zoekmachine) +failureReasonRestarted: + description: Shown in the popup. + message: >- + Violentmonkey is opnieuw opgestart. Laad het tabblad opnieuw om + gebruikersscripts uit te voeren. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Er zijn verschillende manieren op bewerkingen in een lokaal bestand op te + nemen of bij te houden: <1> Sleep het bestand naar een geopende + Violentmonkey-pagina of -pop-up, zoals deze. <2> Installeer een lokale + http-server voor dit bestand en open via http://localhost. <3> Schakel + toegang tot bestands-url's in voor Violentmonkey op chrome://extensions. Let + op: dit wordt afgeraden, omdat elk gebruikersscript lokale bestanden kan + uitlezen. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: alfabetische volgorde + message: alfabetisch filterExecutionOrder: description: Label for option to sort scripts in execution order. message: uitvoervolgorde filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: onlangs bijgewerkt +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: recentst bezoek +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: De recentste keer dat je een site waarop het script werkt bezocht hebt. filterScopeAll: description: Option in dashboard's search scope filter. message: Alle + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Code + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: Naam + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: omvang genericError: description: Label for generic error. message: Fout genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: uit genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: aan genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: algemene instelling volgen +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + "#"-patronen werken alleen bij het openen van de site of bij het opnieuw + laden van het tabblad: $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Prullenbak +helpForLocalFile: + description: Label of the checkbox. + message: Instructies tonen voor slepen-en-neerzetten van lokale scripts +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: voor $1 overeenkomende scripts +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: |- + Een gesandboxed script kan deze globale eigenschappen niet wijzigen: $1. + Voeg `@grant none` toe om de sandbox uit te schakelen of maak gebruik van + `unsafeWindow` in plaats van `window`. hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Invoer-url:' @@ -234,15 +351,16 @@ hintUseDownloadURL: hintVacuum: description: Hint for vacuuming data. message: Wis de overbodige gegevens en probeer de ontbrekende bronnen te herladen. +install: + description: Label for button to install a script. + message: Installeren installFrom: description: Label for button to install script from a userscript site. message: Installeren van $1 installOptionClose: description: Option to close confirm window after installation. message: Sluiten na installatie -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Lokaal bestand bijhouden na sluiten van venster + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -269,12 +387,23 @@ labelAutoReloadCurrentTabDisabled: Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: De huidige scripts worden uitgevoerd totdat je het tabblad herlaadt + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates message: Elke $1 dag(en) controleren op updates (0 = uitschakelen) +labelBackup: + description: Label of the import/export section in settings. + message: Back-ups + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Reservekopie en onderhoud labelBadge: description: Label for option to show number on badge. - message: 'Tonen op embleem: ' + message: 'Embleem:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Embleemkleuren:' labelBadgeNone: description: Option to display nothing on badge. message: niks @@ -299,12 +428,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Gegevens exporteren + touched: false labelDataImport: description: Section title of data import. message: Gegevens importeren + touched: false labelDonate: description: Label of link to donate page. message: Doneren + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'Download-url:' @@ -317,6 +449,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: Bewerker +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: Alleen ingeschakelde scripts labelExclude: description: Label of @exclude rules. message: '@exclude-regels' @@ -329,12 +464,19 @@ labelExportScriptData: labelExposeStatus: description: Option in advanced settings. message: 'Geïnstalleerde versies tonen aan gebruikersscriptsites: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Alternatieve $1-modus in Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Een alternatieve injectiemethode voor scripts voor Firefox + 59 en nieuwer. Deze methode is sneller en vergelijkbaar met ‘Synchrone + paginamodus’. Hierdoor is het geheugengebruik hoger, dus schakel deze optie + uit als scripts ook zonder deze optie werken. labelFeedback: description: Label of link to feedback page. message: Feedback -labelFilterSort: - description: Label for sort filter. - message: Sorteren op $1 labelGeneral: description: Label for general settings. message: Algemeen @@ -347,6 +489,9 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'Website-url:' +labelIconURL: + description: Label for the input. + message: 'Pictogram-url:' labelImportScriptData: description: Option to import script data along with scripts. message: Scriptgegevens importeren @@ -357,8 +502,10 @@ labelInclude: description: Label of @include rules. message: '@include-regels' labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Standaard injectiemodus: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Injectiemodus:' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: Scriptinstallatie @@ -377,10 +524,12 @@ labelMatch: labelName: description: Label of script name. message: 'Naam:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Uitvoeren in frames:' labelNoName: description: Text as the name of a script when no @name is assigned. message: Naamloos - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: Er zijn geen scripts gevonden. @@ -396,12 +545,14 @@ labelNotifyUpdates: labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: per-scriptmeldingen negeren (tabblad 'instellingen' in de bewerker) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Scripts in pop-up sorteren op $1 labelPrivacyPolicy: description: Label of link to privacy policy message: Privacybeleid +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Bezig met herinstalleren… labelRelated: description: Label of related links. message: 'Gerelateerde links: ' @@ -429,6 +580,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Instellingen +labelShowOrder: + description: Label for option in dashboard -> script list + message: Uitvoervolgorde tonen +labelShowVisited: + description: Label for option in dashboard -> script list + message: Recentste bezoektijd tonen labelSync: description: Label for sync options. message: Synchroniseren @@ -441,6 +598,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Bezig met goedkeuren +labelSyncAutomatically: + description: Label for option to sync automatically. + message: Automatisch synchroniseren labelSyncDisabled: description: Label for option to disable sync service. message: Niets @@ -466,6 +626,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: 'Gebruikersnaam: ' +labelTags: + description: Label for custom tags. + message: 'Labels (spatiegescheiden):' +labelTheme: + description: Label for the visual theme option. + message: 'Thema:' labelTranslator: description: Label of translator. message: '' @@ -481,6 +647,26 @@ labelViewSingleColumn: labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. message: Tabelweergave +labelWidth: + description: Width. + message: 'Breedte:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Synchrone $1modus +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Schakel deze optie alleen in als je een script hebt dat dient te worden + uitgevoerd vóórdat de pagina wordt geladen en het momenteel niet wordt + uitgevoerd. Dit is vergelijkbaar met de injectiemodus van Tampermonkey en + maakt gebruik van de verouderde synchrone XHR. Hierdoor kan het gebeuren dat + je Chrome-/Chromium-console waarschuwingen toont - deze kun je echter + negeren. Rechtsklik op een waarschuwing om deze nooit meer te tonen. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (m.u.v. incognitomodus en websites met uitgeschakelde cookies) lastSync: description: Label for last sync timestamp. message: Gesynchroniseerd op $1 @@ -511,6 +697,11 @@ menuExcludeHint: Herlaad de andere tabbladen handmatig om de wijzigingen toe te passen. Op het tabblad 'Instellingen' van de bewerker heb je meer flexibiliteit. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Feedback menuFindScripts: description: Menu item to find scripts for a site. message: Scripts voor deze site zoeken @@ -518,8 +709,13 @@ menuInjectionFailed: description: Injection error. message: Enkele scripts kunnen niet worden geïnjecteerd. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: Opnieuw proberen in 'automatische' modus +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Uitgeschakelde compatibele scripts menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. message: Alleen scripts met onderliggende frames @@ -530,14 +726,19 @@ menuNewScript: description: Menu item to create a new script. message: Script schrijven menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Scripts zijn uitgeschakeld menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Scripts zijn ingeschakeld msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Bezig met controleren op updates... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Klik om de MomentJS-documentatie te lezen. Toegestane sleutels: $1. Gebruik + [vierkante haakjes] om letterlijke tekst te beschermen. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -561,7 +762,7 @@ msgImported: description: >- Message shown after import. There is an argument referring to the count of scripts imported. - message: $1 item(s) geïmporteerd. + message: $1 item(s) zijn geïmporteerd. msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito @@ -569,10 +770,10 @@ msgIncognitoChanges: message: Aanpassingen in incognitomodus worden tevens toegepast op het hoofdprofiel. msgInstalled: description: Message shown when a script is installed. - message: Het script is geïnstalleerd. + message: Script geïnstalleerd. msgInvalidScript: description: Message shown when script is invalid. - message: Het script is ongeldig! + message: Ongeldig script! msgLoadedData: description: >- Message shown in the confirm page when a javascript file to be installed is @@ -589,34 +790,61 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Bezig met laden van afhankelijkheden... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Er ontbreken vereiste bronnen. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. - message: Er zijn script-namespaceconflicten! Pas @name and @namespace aan. + message: |- + Er is al een vergelijkbaar script geïnstalleerd. + Kies een andere @name en @namespace of bewerk het andere script. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Er is al een script met dezelfde @name en @namespace geïnstalleerd. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @downloadURL is provided. - message: Er is een nieuwe versie beschikbaar. + message: Nieuwe versie gevonden. msgNoUpdate: description: Message shown when there is no new version of a script. - message: Er is geen nieuwe versie beschikbaar. + message: Geen update gevonden. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: De scripts kunnen niet worden bijgewerkt. Klik om te openen. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Sla de volgende scripts opnieuw op of herinstalleer ze:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (code is hetzelfde) msgSavedBlacklist: description: Message shown when blacklist are saved. message: De zwarte lijst is bijgewerkt. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: De aangepaste stijl is bijgewerkt. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: De bewerkerinstellingen zijn opgeslagen. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: Het aangepaste scriptsjabloon is bijgewerkt. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: '[$1] is bijgewerkt!' + message: Script [$1] is bijgewerkt! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: Tonen/Verbergen @@ -629,12 +857,28 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Initialisatiefout! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Nog niet geautoriseerd. msgSyncReady: description: Message shown when sync will start soon. message: De synchronisatie begint spoedig... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Bezig met synchroniseren... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Syntaxisfout? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + Het script bestond niet of kwam niet overeen met de URL toen de pagina werd + geladen, wat gebeurt bij Single-Page Application-sites zoals FB of Instagram + die gebruikmaken van nepnavigatie. Je kan het tabblad opnieuw laden om het + script uit te voeren. Gebruik @match voor de hele site om het script te + repareren en detecteer vervolgens wijzigingen met MutationObserver of + window.navigation API. msgUpdated: description: Message shown when a script is updated/reinstalled. message: Het script is bijgewerkt. @@ -657,15 +901,90 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: Omnibalk verbergen +optionPopup: + description: Label of the popup menu section in settings. + message: Pop-upmenu en pictogram optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: Ingeschakelde bovenaan +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Uitgeschakelde scripts groeperen + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Groeperen op @run-at-niveau optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: Uitgeschakelde verbergen + message: Uitgeschakelde scripts verbergen + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Uitgeschakelde scripts tonen + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Ingeschakelde scripts bovenaan +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'UI Thema:' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: automatisch +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: donker +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: licht +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Bijwerken +popupSettings: + description: Item inside the popup's "⋮" menu + message: Pop-upinstellingen +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Rechtsklikken: pop-upinstellingen' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Alleen-lezen +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Het automatisch bijgewerkte script is alleen-lezen, tenzij je updates + uitschakelt of bewerken toestaat. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Bewerken toestaan +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (de volgende update overschrijft ze) +reinstall: + description: Button to reinstall a script + message: Opnieuw installeren +reloadTab: + description: Label of action to reload the tab + message: Tabblad herladen +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Schakel in om het actieve tabblad te herladen als er wijzigingen zijn + aangetroffen en het script overeenkomt met de url. +removeAllScripts: + description: Button to remove all scripts + message: Alle scripts verwijderen + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: >- + Weet je zeker dat je alle scripts in de prullenbak wilt zetten? + + Na verloop van tijd wordt de prullenbak geleegd. Ook kun je handmatig de + prullenbak legen. + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Hoofdlettergevoelig @@ -681,14 +1000,80 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Instellingen -titleScriptUpdated: - description: Notification title for script updates. - message: Bijwerken +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Tabblad herladen zonder gebruikersscripts +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Je hebt gebruikersscripts uitgeschakeld op deze pagina. Schakel ze in door + te herladen of een link aan te klikken. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: Sorteervolgorde +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Bijhouden stoppen +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Normale embleemkleur +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + De kleur die wordt gebruikt als de code niet kan worden toegevoegd aan de + site (zwarte lijst of niet-ondersteund) titleSearchHint: description: Hover title for search icon in dashboard. message: |- * Druk op om de tekst toe te voegen aan de auto-aanvulgeschiedenis * Ondersteunt reguliere uitdrukkingen: /re/ en /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Entertoets om tekst toe te voegen aan automatisch aanvullen + + * Alle omstandigheden zijn hoofdletterongevoelig + + * Spatiegescheiden omstandigheden kunnen worden gecombineerd + + * Zoeken op metagegevens: "Geweldig script" "Beschrijving" + + * Zoeken op labels: #label1 #label2 + + * Zoeken op scriptnaam: name:"geweldige naam" + + * Zoeken op scriptcode: code:"geweldige code" + + * Negatief zoeken: !#label2 !name:"ongewenst" + + * Reguliere uitdrukkingen: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? met spatie" +toggleInjection: + description: Label for a keyboard shortcut. + message: Gebruikersscriptinjectie aan/uit +trackEdits: + description: Button in a script installation dialog. + message: Externe bewerkingen bijhouden +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: Houd deze pagina geopend om bewerkingen in het lokale bestand bij te houden +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Scrips bijwerken ($1) +updateScript: + description: Button to update one script. + message: Bijwerken +updateScriptsAll: + description: Command/button to update all scripts. + message: Alle scripts bijwerken +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Scripts van huidig tabblad bijwerken valueLabelKey: description: Label for key of a script value. message: Sleutel (tekenreeks) @@ -698,6 +1083,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: Alle waarden (in json-opmaak) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: Violentmonkey-website + touched: false diff --git a/src/_locales/no/messages.yml b/src/_locales/no/messages.yml new file mode 100644 index 0000000000..e1373b4bca --- /dev/null +++ b/src/_locales/no/messages.yml @@ -0,0 +1,983 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: '' +buttonCancel: + description: Cancel button on dialog. + message: '' +buttonCheckForUpdates: + description: Button to check a script for updates. + message: '' + touched: false +buttonClose: + description: Button to close window. + message: '' +buttonConfirmInstallation: + description: Button to confirm installation of a script. + message: '' + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: '' + touched: false +buttonDisable: + description: Button to disable a script. + message: '' +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false +buttonEdit: + description: Button to edit a script. + message: '' +buttonEditClickHint: + description: Tooltip for the Edit button in popup. + message: '' +buttonEmptyRecycleBin: + description: Button to empty the recycle bin. + message: '' +buttonEnable: + description: Button to enable a script. + message: '' +buttonExportData: + description: Button to open the data export dialog. + message: '' +buttonFilter: + description: Button to show filters menu. + message: '' + touched: false +buttonHome: + description: Button to open homepage. + message: '' +buttonImportData: + description: Button to choose a file for data import. + message: '' +buttonInstallFromURL: + description: Button to ask for URL of a user script. + message: '' +buttonInstallOptions: + description: Button to show options of installation confirm page. + message: '' + touched: false +buttonNew: + description: Button to create a new script. + message: '' +buttonOK: + description: OK button on dialog. + message: '' +buttonRecycleBin: + description: Button to list scripts in recycle bin. + message: '' +buttonRemove: + description: Button to remove a script. + message: '' +buttonReplace: + description: Button to replace the current match. + message: '' +buttonReplaceAll: + description: Button to replace all matches. + message: '' +buttonReset: + description: Button to reset to default values. + message: '' +buttonResetSettings: + description: Button in settings page to reset all settings + message: '' +buttonRestore: + description: Button to restore a removed script. + message: '' +buttonSave: + description: Button to save modifications of a script. + message: '' +buttonSaveClose: + description: Button to save modifications of a script and then close the editing page. + message: '' +buttonSaved: + description: Button text after saving. + message: '' +buttonShowEditorState: + description: Button to show the list of currently used CodeMirror options. + message: '' +buttonSupport: + description: Button to open support page. + message: '' +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' +buttonUndo: + description: Button to undo removement of a script. + message: '' +buttonUpdate: + description: Button to update a script. + message: '' +buttonUpdateAll: + description: Check all scripts for updates. + message: '' + touched: false +buttonVacuum: + description: Button to vacuum extension data. + message: '' +buttonVacuumed: + description: Message shown when data is vacuumed. + message: '' +buttonVacuuming: + description: Message shown when data vacuum is in progress. + message: '' +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: '' +confirmNotSaved: + description: Confirm message shown when there are unsaved script modifications. + message: '' +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: '' +descBlacklist: + description: Description for the global injection blacklist. + message: '' +descBlacklistNet: + description: Description for the global network blacklist. + message: '' +descCustomCSS: + description: Description of custom CSS section. + message: '' +descEditorOptions: + description: Description of editor options JSON section. + message: '' +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: '' +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: '' +descScriptTemplate: + description: Description of script template section. + message: '' +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: '' +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: '' +disabledScriptsSelector: + description: Label of the option. + message: '' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: '' +editHelpDocumention: + description: Label in the editor help tab for the documentation link. + message: '' +editHelpKeyboard: + description: Label in the editor help tab for the keyboard shortcuts. + message: '' +editHowToHint: + description: The text of the how-to link in the editor header. + message: '' +editLabelMeta: + description: Metadata section in settings tab of script editor. + message: '' +editLabelSettings: + description: Settings section in settings tab of script editor. + message: '' +editLongLine: + description: Shown in the editor in lines that were cut due to being too long + message: '' +editLongLineTooltip: + description: Tooltip shown in the editor in lines that were cut due to being too long + message: '' +editNavCode: + description: Label of code tab in script editor. + message: '' +editNavSettings: + description: Label of settings tab in script editor. + message: '' +editNavValues: + description: Label of values tab in script editor. + message: '' +editValueAll: + description: Button to show/edit the entire script value storage. + message: '' + touched: false +editValueAllHint: + description: Tooltip for the 'all' button. + message: '' +extDescription: + description: Description for this extension, will be displayed in web store + message: '' +extName: + description: Name of this extension. + message: '' +failureReasonBlacklisted: + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: '' +failureReasonNoninjectable: + description: >- + Shown for URLs that cannot be processed (same places as + failureBlacklistedUrl) + message: '' +failureReasonRestarted: + description: Shown in the popup. + message: '' +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: '' +filterAlphabeticalOrder: + description: Label for option to sort scripts in alphabetical order. + message: '' +filterExecutionOrder: + description: Label for option to sort scripts in execution order. + message: '' +filterLastUpdateOrder: + description: Label for option to sort scripts by last update time. + message: '' +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' +filterScopeAll: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterScopeCode: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterScopeName: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: '' +genericError: + description: Label for generic error. + message: '' +genericOff: + description: To indicate something is turned off or disabled, similar to "no". + message: '' +genericOn: + description: To indicate something is turned on or enabled, similar to "yes". + message: '' +genericUseGlobal: + description: To indicate some per-script option will use its analog in global settings. + message: '' +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: '' +headerRecycleBin: + description: Text shown in the header when scripts in recycle bin are listed. + message: '' +helpForLocalFile: + description: Label of the checkbox. + message: '' +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: '' +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' +hintInputURL: + description: Hint for a prompt box to input URL of a user script. + message: '' +hintRecycleBin: + description: Hint for recycle bin. + message: '' +hintUseDownloadURL: + description: Shown as a place holder for @updateURL when it is not assigned + message: '' +hintVacuum: + description: Hint for vacuuming data. + message: '' +install: + description: Label for button to install a script. + message: '' +installFrom: + description: Label for button to install script from a userscript site. + message: '' +installOptionClose: + description: Option to close confirm window after installation. + message: '' + touched: false +installOptionTrackTooltip: + description: >- + Tooltip in Firefox 68+ for the option to track the loading local file before + window is closed. + message: '' +labelAbout: + description: Label shown on top of the about page. + message: '' + touched: false +labelAdvanced: + description: Label for button to show advanced settings. + message: '' +labelAllowUpdate: + description: Option to allow checking updates for a script. + message: '' +labelAuthor: + description: Label of author shown in the details of a script. + message: '' +labelAutoReloadCurrentTab: + description: Option to reload current tab after a script is switched on or off from menu. + message: '' +labelAutoReloadCurrentTabDisabled: + description: >- + Tooltip in menu after Violentmonkey is switched off if auto-reload is + disabled. + message: '' + touched: false +labelAutoUpdate: + description: Option to allow automatically checking scripts for updates + message: '' +labelBackup: + description: Label of the import/export section in settings. + message: '' + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: '' +labelBadge: + description: Label for option to show number on badge. + message: '' +labelBadgeColors: + description: Label for option group to set badge colors. + message: '' +labelBadgeNone: + description: Option to display nothing on badge. + message: '' +labelBadgeTotal: + description: Option to display total number of running scripts on badge. + message: '' +labelBadgeUnique: + description: Option to display number of unique running scripts on badge. + message: '' +labelBlacklist: + description: Label for global blacklist settings in security section. + message: '' +labelContributors: + description: Label for link to contributors. + message: '' +labelCurrentLang: + description: Label of current language. + message: '' +labelCustomCSS: + description: Label for custom CSS section. + message: '' +labelDataExport: + description: Section title of data export. + message: '' + touched: false +labelDataImport: + description: Section title of data import. + message: '' + touched: false +labelDonate: + description: Label of link to donate page. + message: '' + touched: false +labelDownloadURL: + description: Label of script @downloadURL in custom meta data. + message: '' +labelEditValue: + description: Label shown in the panel to edit a script value. + message: '' +labelEditValueAll: + description: Label shown in the panel to edit the entire script value storage. + message: '' +labelEditor: + description: Label for Editor settings + message: '' +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: '' +labelExclude: + description: Label of @exclude rules. + message: '' +labelExcludeMatch: + description: Label of @exclude-match rules. + message: '' +labelExportScriptData: + description: Option to export script data along with scripts. + message: '' +labelExposeStatus: + description: Option in advanced settings. + message: '' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: '' +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: '' +labelFeedback: + description: Label of link to feedback page. + message: '' +labelGeneral: + description: Label for general settings. + message: '' +labelHelpTranslate: + description: Label for link to localization guide in about tab + message: '' +labelHomepage: + description: Label for home page in about tab. + message: '' +labelHomepageURL: + description: Label of script @homepageURL in custom meta data. + message: '' +labelIconURL: + description: Label for the input. + message: '' +labelImportScriptData: + description: Option to import script data along with scripts. + message: '' +labelImportSettings: + description: Label for option to import settings from zip file. + message: '' +labelInclude: + description: Label of @include rules. + message: '' +labelInjectionMode: + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: '' +labelInstall: + description: Shown in the title of the confirm page while trying to install a script. + message: '' +labelKeepOriginal: + description: Option to keep the original match or ignore rules. + message: '' +labelLastUpdatedAt: + description: Label shown on last updated time. + message: '' +labelLineNumber: + description: Label for line number jumper. + message: '' +labelMatch: + description: Label of @match rules. + message: '' +labelName: + description: Label of script name. + message: '' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: '' +labelNoName: + description: Text as the name of a script when no @name is assigned. + message: '' +labelNoSearchScripts: + description: Message shown when no script is found in search results. + message: '' +labelNotifyThisUpdated: + description: >- + A per-script option in editor to enable notification when this script is + updated. The text follows "Allow update" checkbox option so it's like a + continuation of the phrase. + message: '' +labelNotifyUpdates: + description: Option to show notification when script is updated. + message: '' +labelNotifyUpdatesGlobal: + description: Option to prioritize global notification option over script's setting. + message: '' +labelPrivacyPolicy: + description: Label of link to privacy policy + message: '' +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: '' +labelRelated: + description: Label of related links. + message: '' +labelRemovedAt: + description: Label for the time when the script is removed. + message: '' +labelReplace: + description: Label for replace input in search box. + message: '' +labelRunAt: + description: Label of script @run-at properties in custom meta data. + message: '' +labelRunAtDefault: + description: Shown when custom @run-at is not assigned. + message: '' +labelScriptTemplate: + description: Label for custom script template. + message: '' +labelSearch: + description: Label for search input in search box. + message: '' +labelSearchScript: + description: Placeholder for script search box. + message: '' +labelSettings: + description: Label shown on the top of settings page + message: '' +labelShowOrder: + description: Label for option in dashboard -> script list + message: '' +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' +labelSync: + description: Label for sync options. + message: '' +labelSyncAnonymous: + description: Label for using anonymous account. + message: '' +labelSyncAuthorize: + description: Label for button to authorize a service. + message: '' +labelSyncAuthorizing: + description: Label for button when authorization is in progress. + message: '' +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' +labelSyncDisabled: + description: Label for option to disable sync service. + message: '' +labelSyncPassword: + description: Label for input to hold password. + message: '' +labelSyncReauthorize: + description: Option to reauthorize sync service when expired. + message: '' + touched: false +labelSyncRevoke: + description: Label for button to revoke authorization for a service. + message: '' +labelSyncScriptStatus: + description: Label for option to sync script status. + message: '' +labelSyncServerUrl: + description: Label for input to hold server URL. + message: '' +labelSyncService: + description: Label for sync service select. + message: '' +labelSyncUsername: + description: Label for input to hold username. + message: '' +labelTags: + description: Label for custom tags. + message: '' +labelTheme: + description: Label for the visual theme option. + message: '' +labelTranslator: + description: Label of translator. + message: '' + touched: false +labelUpdateURL: + description: Label of script @updateURL in custom meta data. + message: '' +labelViewSingleColumn: + description: >- + Label for option in dashboard script list to show the scripts in single + column. + message: '' +labelViewTable: + description: Label for option in dashboard script list to show the scripts as a table. + message: '' +labelWidth: + description: Width. + message: '' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: '' +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: '' +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: '' +lastSync: + description: Label for last sync timestamp. + message: '' +learnBlacklist: + description: Refers to a link to introduce blacklist patterns. + message: '' +learnInjectionMode: + description: Refers to a link to introduce injection modes. + message: '' +menuCommands: + description: Menu item to list script commands. + message: '' + touched: false +menuDashboard: + description: Label for menu item to open dashboard. + message: '' +menuExclude: + description: >- + Shown in popup menu after clicking script's "..." dropdown so try to keep + the label short. + message: '' +menuExcludeHint: + description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. + message: '' +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: '' +menuFindScripts: + description: Menu item to find scripts for a site. + message: '' +menuInjectionFailed: + description: Injection error. + message: '' +menuInjectionFailedFix: + description: Injection error fix, shown in case the default mode is "page". + message: '' +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: '' +menuMatchedFrameScripts: + description: Label for menu listing matching scripts in sub-frames. + message: '' +menuMatchedScripts: + description: Label for menu listing matched scripts. + message: '' +menuNewScript: + description: Menu item to create a new script. + message: '' +menuScriptDisabled: + description: Menu item showing the status of Violentmonkey, when disabled. + message: '' +menuScriptEnabled: + description: Menu item showing the status of Violentmonkey, when enabled. + message: '' +msgCheckingForUpdate: + description: Message shown when a script is being checked for updates by version numbers. + message: '' +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: '' +msgErrorFetchingResource: + description: >- + Message shown when Violentmonkey fails fetching a resource/require/icon of + the script. + message: '' +msgErrorFetchingScript: + description: Message shown when Violentmonkey fails fetching a new version of the script. + message: '' +msgErrorFetchingUpdateInfo: + description: Message shown when Violentmonkey fails fetching version data of the script. + message: '' +msgErrorLoadingData: + description: >- + Message shown on confirm page when the script to be installed cannot be + loaded. + message: '' +msgErrorLoadingDependency: + description: Message shown when not all requirements are loaded successfully. + message: '' +msgImported: + description: >- + Message shown after import. There is an argument referring to the count of + scripts imported. + message: '' +msgIncognitoChanges: + description: >- + Message shown in popup and installation tab when opened in an incognito + window. + message: '' +msgInstalled: + description: Message shown when a script is installed. + message: '' +msgInvalidScript: + description: Message shown when script is invalid. + message: '' +msgLoadedData: + description: >- + Message shown in the confirm page when a javascript file to be installed is + loaded. + message: '' + touched: false +msgLoading: + description: Message shown in the options page before script list is loaded. + message: '' + touched: false +msgLoadingData: + description: Message shown on confirm page when the script to be installed is loading. + message: '' +msgLoadingDependency: + description: Message shown on confirm page when the requirements are being downloaded. + message: '' +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: '' +msgNamespaceConflict: + description: >- + Message shown when namespace of the new script conflicts with an existent + one. + message: '' +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: '' +msgNewVersion: + description: >- + Message shown when a new version of script is found by @updateURL, but no + @downloadURL is provided. + message: '' +msgNoUpdate: + description: Message shown when there is no new version of a script. + message: '' +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: '' +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: '' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: '' +msgSavedBlacklist: + description: Message shown when blacklist are saved. + message: '' + touched: false +msgSavedCustomCSS: + description: Message shown when custom CSS is saved. + message: '' + touched: false +msgSavedEditorOptions: + description: Message shown when editor options are saved. + message: '' + touched: false +msgSavedScriptTemplate: + description: Message shown when custom script template is saved. + message: '' + touched: false +msgScriptUpdated: + description: Notification message for script updates. + message: '' +msgShowHide: + description: Tooltip or text shown on a toggle that shows/hides stuff + message: '' +msgSyncError: + description: Message shown when sync failed. + message: '' +msgSyncInit: + description: Message shown when sync service is initializing. + message: '' +msgSyncInitError: + description: Message shown when sync fails in initialization. + message: '' +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: '' +msgSyncReady: + description: Message shown when sync will start soon. + message: '' + touched: false +msgSyncing: + description: Message shown when sync is in progress. + message: '' +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: '' +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: '' +msgUpdated: + description: Message shown when a script is updated/reinstalled. + message: '' +msgUpdating: + description: Message shown when a new version of script is being fetched. + message: '' +noValues: + description: Label shown when there is no value for current script. + message: '' +optionEditorWindow: + description: Label for the option in settings + message: '' +optionEditorWindowHint: + description: >- + Tooltip for optionEditorWindow in case the browser doesn't support + onBoundsChanged + message: '' +optionEditorWindowSimple: + description: Label for the editor window type + message: '' +optionPopup: + description: Label of the popup menu section in settings. + message: '' +optionPopupEnabledFirst: + description: Option to show enabled scripts first in popup. + message: '' +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: '' + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: '' +optionPopupHideDisabled: + description: Option to hide disabled scripts in popup. + message: '' + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: '' + touched: false +optionShowEnabledFirst: + description: Option to show enabled scripts first in alphabetical order. + message: '' +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: '' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: '' +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: '' +popupSettings: + description: Item inside the popup's "⋮" menu + message: '' +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: '' +readonlyNote: + description: Warning when trying to type in the editor. + message: '' +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: '' +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: '' +reinstall: + description: Button to reinstall a script + message: '' +reloadTab: + description: Label of action to reload the tab + message: '' +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: '' +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false +searchCaseSensitive: + description: Option to perform a case-sensitive search + message: '' +searchUseRegex: + description: Option to perform a regular expression search + message: '' +sideMenuAbout: + description: 'Side menu: About' + message: '' +sideMenuInstalled: + description: 'Side menu: Installed scripts' + message: '' +sideMenuSettings: + description: 'Side menu: Settings' + message: '' +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: '' +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: '' +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: '' +titleBadgeColor: + description: Tooltip for option to set badge color. + message: '' +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: '' +titleSearchHint: + description: Hover title for search icon in dashboard. + message: '' + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: '' +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: '' +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: '' +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: '' +updateScript: + description: Button to update one script. + message: '' +updateScriptsAll: + description: Command/button to update all scripts. + message: '' +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: '' +valueLabelKey: + description: Label for key of a script value. + message: '' +valueLabelValue: + description: Label for value of a script value. + message: '' +valueLabelValueAll: + description: Label for input of entire script value storage. + message: '' +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' +visitWebsite: + description: Label for link to open Violentmonkey website. + message: '' + touched: false diff --git a/src/_locales/pl/messages.yml b/src/_locales/pl/messages.yml index e3e980f050..d3cb885ab5 100644 --- a/src/_locales/pl/messages.yml +++ b/src/_locales/pl/messages.yml @@ -1,15 +1,31 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Zastosuj buttonCancel: description: Cancel button on dialog. message: Anuluj +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Sprawdź aktualizacje + touched: false buttonClose: description: Button to close window. message: Zamknij buttonConfirmInstallation: description: Button to confirm installation of a script. message: Potwierdź instalację + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Potwierdź ponowną instalację + touched: false buttonDisable: description: Button to disable a script. message: Wyłącz +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: Pobierz motywy + touched: false buttonEdit: description: Button to edit a script. message: Edytuj @@ -65,6 +81,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Zresetuj +buttonResetSettings: + description: Button in settings page to reset all settings + message: Resetuj ustawienia buttonRestore: description: Button to restore a removed script. message: Przywróć @@ -74,22 +93,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Zapisz i zamknij +buttonSaved: + description: Button text after saving. + message: Zapisano buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Pokaż określenia edytora buttonSupport: description: Button to open support page. message: Strona wsparcia technicznego +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. message: Cofnij - touched: false buttonUpdate: - description: Check a script for updates. - message: Sprawdź dostępność aktualizacji + description: Button to update a script. + message: Aktualizuj buttonUpdateAll: description: Check all scripts for updates. message: Sprawdź aktualizacje dla wszystkich skryptów + touched: false buttonVacuum: description: Button to vacuum extension data. message: Wyczyść bazę danych @@ -99,14 +127,31 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Czyszczenie danych... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Automatyczna aktualizacja jest wyłączona dla tego skryptu! + Kliknij OK, aby mimo to zaktualizować. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Zmiany nie zostały zapisane! Kliknij "OK", aby je porzucić lub "Anuluj", aby je zostawić. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Cofnij wszystkie zmiany wprowadzone w bazie danych (importowanie, + aktualizacja, edycja, dostosowywanie) descBlacklist: - description: HTML Description for the global blacklist. - message: 'Adresy URL, które są na tej liście, nie zostaną dołączone do skryptów.' + description: Description for the global injection blacklist. + message: Czarna lista wstrzykiwań (skrypty nie będą działać w pasujących witrynach) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Czarna lista sieci (skrypty nie będą łączyć się z pasującymi witrynami i ich + plikami cookie) descCustomCSS: description: Description of custom CSS section. message: >- @@ -115,16 +160,43 @@ descCustomCSS: descEditorOptions: description: Description of editor options JSON section. message: >- - Niestandardowe opcje CodeMirror i dodatków w obiekcie JSON, takie jak + Niestandardowe opcje dla CodeMirror i dodatków w obiekcie JSON, takie jak {"indentUnit":2, "smartIndent":true}, należy jednak pamiętać, że niektóre z nich mogą nie działać w Violentmonkey. Zobacz pełną listę. Aby użyć niestandardowego motywu - CodeMirror, podaj tutaj jego nazwę pliku, np. "theme": - "3024-day" i wklej poniżej CSS aktualnego motywu w polu - "Styl niestandardowy". + rel="noopener noreferrer">pełną listę. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Opcje logiczne można przełączać poprzez dwukrotne kliknięcie wartości: + true = włączone, false = wyłączone. Opcje + numeryczne kończące się na Delay, Interval, + Rate, Time podawane są w milisekundach. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Niestandardowe opcje: autocompleteOnTyping to opóźnienie w + milisekundach wyświetlania podpowiedzi autouzupełniania po wpisaniu + (0 = wyłączone), killTrailingSpaceOnSave + automatycznie usuwa końcowe spacje w każdym wierszu podczas zapisywania, + showTrailingSpace pokazuje końcowe spacje jako kropki. +descScriptTemplate: + description: Description of script template section. + message: >- + Obsługiwane zmienne: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> w + formacie zgodnym z MomentJS, np <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: grupuj +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: ukryj +disabledScriptsSelector: + description: Label of the option. + message: 'Wyłączone skrypty:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: pokaż editHelpDocumention: description: Label in the editor help tab for the documentation link. message: >- @@ -166,17 +238,12 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: Wszystko + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: Pokaż/edytuj całą pamięć wartości skryptu -editValueCancel: - description: Button to cancel modification of a script value. - message: Anuluj -editValueSave: - description: Button to save modification of a script value. - message: Zapisz extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: >- Menedżer skryptów użytkownika o otwartym kodzie źródłowym, który obsługuje wiele przeglądarek. @@ -184,48 +251,95 @@ extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: ' Zablokowano w czarnej liście w ustawieniach Violentmonkey''a' failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) - message: |- - Violentmonkey nie może uruchomić skryptów użytkownika na tej stronie - (możliwe przykłady: interfejs przeglądarki lub rozszerzenia) + message: >- + Violentmonkey nie może uruchamiać skryptów użytkownika na tej stronie + + (pospolite przykłady: interfejs przeglądarki, rozszerzenia, zablokowane + przez zasady, strona wyszukiwarki w Operze) +failureReasonRestarted: + description: Shown in the popup. + message: >- + Violentmonkey został ponownie uruchomiony. Załaduj ponownie kartę, aby + uruchomić skrypty użytkownika. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Istnieje kilka sposobów instalacji lub śledzenia zmian w pliku lokalnym: <1> + Przeciągnij i upuść plik na otwartą stronę lub okno Violentmonkey (także + tutaj). <2> Skonfiguruj lokalny serwer HTTP i otwórz plik przez + http://localhost. <3> Włącz "Zezwalaj na dostęp do adresów URL plików" w + szczegółach rozszerzenia Violentmonkey na stronie chrome://extensions. Jest + to ryzykowne, ponieważ każdy skrypt użytkownika będzie mógł odczytywać + lokalne pliki. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: kolejności alfabetycznej + message: alfabetycznie filterExecutionOrder: description: Label for option to sort scripts in execution order. - message: kolejności wykonywania + message: według kolejności wykonywania filterLastUpdateOrder: description: Label for option to sort scripts by last update time. - message: czasu ostatniej aktualizacji + message: według daty ostatniej aktualizacji +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: Wszędzie + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Kod + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: Nazwa + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: według rozmiaru genericError: description: Label for generic error. message: Błąd genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: wył genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: wł genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: użyj globalnych ustawień +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + "#" wzorce działają tylko przy pierwszym otwarciu witryny lub ponownym + załadowaniu karty: $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Kosz +helpForLocalFile: + description: Label of the checkbox. + message: Pokaż instrukcję dotyczącą przeciągania i upuszczania lokalnych skryptów +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: dla $1 pasujących skryptów +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Wprowadź adres URL:' @@ -240,15 +354,16 @@ hintVacuum: message: >- Usuń nadmiarowość i spróbuj przeładować brakujące zasoby w pamięci podręcznej. +install: + description: Label for button to install a script. + message: Zainstaluj installFrom: description: Label for button to install script from a userscript site. message: Zainstaluj z $1 installOptionClose: description: Option to close confirm window after installation. message: Zamknij po zakończeniu instalacji -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Śledź plik lokalny przed zamknięciem tego okna + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -277,12 +392,23 @@ labelAutoReloadCurrentTabDisabled: Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: Bieżące skrypty będą działać do momentu ponownego załadowania karty + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: 'Sprawdzaj aktualizacje skryptów co $1 dni, użyj 0 aby wyłączyć' + message: Sprawdzaj aktualizacje skryptów co $1 dni, użyj 0 aby wyłączyć +labelBackup: + description: Label of the import/export section in settings. + message: Kopia zapasowa + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Kopia zapasowa i konserwacja labelBadge: description: Label for option to show number on badge. - message: 'Wyświetl na plakietce: ' + message: 'Pokaż na plakietce:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Kolory plakietki:' labelBadgeNone: description: Option to display nothing on badge. message: nic @@ -307,12 +433,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Eksport danych + touched: false labelDataImport: description: Section title of data import. message: Import danych + touched: false labelDonate: description: Label of link to donate page. message: Dotacja + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'Adres URL pobierania:' @@ -325,6 +454,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: Edytor +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: tylko włączone skrypty labelExclude: description: Label of @exclude rules. message: Reguły @exclude @@ -339,12 +471,19 @@ labelExposeStatus: message: >- Pokazuj informację o obecnie zainstalowanej wersji skryptu na jego stronie listingu w katalogach: $1 +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Alternatywny tryb $1 w Firefoxie +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Alternatywna metoda wstrzykiwania skryptów od Firefoxa + wersji 59, szybsza niż tryb domyślny. Podobnie jak „Tryb strony + synchronicznej”, zwiększa on również zużycie pamięci, więc możesz go + wyłączyć, jeśli Twoje skrypty działają poprawnie bez tej opcji. labelFeedback: description: Label of link to feedback page. message: Informacje zwrotne -labelFilterSort: - description: Label for sort filter. - message: Sortuj wg $1 labelGeneral: description: Label for general settings. message: Ogólne @@ -357,6 +496,9 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'Adres URL strony głównej:' +labelIconURL: + description: Label for the input. + message: 'Adres URL ikony:' labelImportScriptData: description: Option to import script data along with scripts. message: Importuj dane skryptów @@ -367,8 +509,10 @@ labelInclude: description: Label of @include rules. message: Reguły @include labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Domyślny tryb wstrzykiwania: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Tryb wstrzykiwania: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: Instalacja skryptu @@ -387,10 +531,12 @@ labelMatch: labelName: description: Label of script name. message: 'Nazwa skryptu:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Uruchom w ramkach:' labelNoName: description: Text as the name of a script when no @name is assigned. message: Bez nazwy - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: Nie znaleziono skryptu. @@ -408,12 +554,14 @@ labelNotifyUpdatesGlobal: message: >- Ignoruj ​​powiadomienia dla poszczególnych skryptów (zakładka „ustawienia” w edytorze) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: 'Sortuj skrypty w wyskakującym okienku wg $1 ' labelPrivacyPolicy: description: Label of link to privacy policy message: Polityka prywatności +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Ponowna instalacja skryptu labelRelated: description: Label of related links. message: 'Powiązane linki: ' @@ -441,6 +589,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Ustawienia +labelShowOrder: + description: Label for option in dashboard -> script list + message: Pokaż kolejność wykonywania +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Synchronizacja @@ -453,6 +607,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Autoryzacja +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: Żaden @@ -478,6 +635,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: 'Nazwa użytkownika:' +labelTags: + description: Label for custom tags. + message: 'Tagi (oddzielone spacją):' +labelTheme: + description: Label for the visual theme option. + message: 'Motyw:' labelTranslator: description: Label of translator. message: 'Tłumacze: ' @@ -493,6 +656,27 @@ labelViewSingleColumn: labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. message: Widok tabeli +labelWidth: + description: Width. + message: 'Szerokość:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Tryb synchroniczny $1 +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Włącz tylko wtedy, gdy masz skrypt, który musi zostać uruchomiony przed + rozpoczęciem ładowania strony, a obecnie działa zbyt późno. Podobnie jak + tryb natychmiastowego wstrzykiwania w Tampermonkey, ta opcja używa + przestarzałego synchronicznego XHR, więc w Chrome/Chromium zobaczysz + ostrzeżenia w konsoli devtools, chociaż możesz je bezpiecznie zignorować, + ponieważ niekorzystne skutki są w tym przypadku znikome. Ostrzeżenia można + ukryć na dobre, klikając je prawym przyciskiem myszy. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (z wyjątkiem okna prywatnego i witryn z wyłączonymi ciasteczkami) lastSync: description: Label for last sync timestamp. message: Ostatnia synchronizacja o $1 @@ -523,6 +707,11 @@ menuExcludeHint: Aby zastosować zmiany we wszystkich innych kartach, odśwież je ręcznie. Aby uzyskać większą elastyczność, użyj zakładki „Ustawienia” w edytorze. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Opinie menuFindScripts: description: Menu item to find scripts for a site. message: Znajdź skrypty dla tej strony @@ -530,8 +719,13 @@ menuInjectionFailed: description: Injection error. message: Nie można wstrzyknąć niektórych skryptów. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: Spróbuj ponownie w trybie „auto” +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Dopasowane wyłączone skrypty menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. message: Skrypty tylko w ramkach @@ -542,14 +736,19 @@ menuNewScript: description: Menu item to create a new script. message: Utwórz nowy skrypt menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Skrypty wyłączone menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Skrypty włączone msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Sprawdzanie aktualizacji... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Kliknij, aby otworzyć dokumentację MomentJS. Dozwolone tokeny: $1. Użyj + [nawiasów kwadratowych], aby chronić tekst dosłowny. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -603,11 +802,23 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Wczytywanie zależności... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Brakuje wymaganych zasobów. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. - message: Konflikty przestrzeni nazw skryptu! Proszę zmodyfikować @name i @namespace. + message: |- + Taki skrypt jest już zainstalowany. + Użyj tutaj a inne @name i @namespace lub zamiast tego edytuj ten skrypt.. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Skrypt o tym samym @name i @namespace jest już zainstalowany. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -616,21 +827,36 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Nie znaleziono aktualizacji. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Błąd podczas aktualizowania skryptów. Kliknij, aby go otworzyć. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Zapisz ponownie lub przeinstaluj te skrypty:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (kod jest taki sam) msgSavedBlacklist: description: Message shown when blacklist are saved. message: Czarna lista zaktualizowana + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Styl niestandardowy jest aktualizowany. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Opcje edytora zostały zaktualizowane. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: Niestandardowy szablon skryptu jest aktualizowany. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Skrypt [$1] jest aktualizowany!' + message: Skrypt [$1] jest aktualizowany! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: Pokaż/ukryj @@ -643,12 +869,28 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Błąd inicjalizacji! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Jeszcze nie autoryzowano. msgSyncReady: description: Message shown when sync will start soon. message: Synchronizacja rozpocznie się wkrótce... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Trwa synchronizacja... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Błąd składni? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + Skrypt nie istniał lub nie pasował do bieżącego adresu URL w momencie + ładowania strony. Dzieje się tak na stronach typu Single-Page Application + (SPA), takich jak Facebook lub Instagram, gdzie nawigacja odbywa się bez + przeładowania strony. Odśwież kartę by uruchomić skrypt albo zastosuj @match + dla całej strony i wykrywaj zmiany za pomocą MutationObserver lub + window.navigation API. msgUpdated: description: Message shown when a script is updated/reinstalled. message: Skrypt zaktualizowany. @@ -671,15 +913,86 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: Ukryj omnibox +optionPopup: + description: Label of the popup menu section in settings. + message: Menu podręczne i ikona optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: Najpierw włączone +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Grupuj wyłączone skrypty + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Grupuj wg metadanych @run-at optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: Ukryj wyłączone + message: Ukryj wyłączone skrypty + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Pokaż wyłączone skrypty + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Pokazuj najpierw włączone skrypty +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Motyw interfejsu:' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: automatyczny +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: ciemny +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: jasny +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Aktualizacja +popupSettings: + description: Item inside the popup's "⋮" menu + message: Ustawienia wyskakującego okienka +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Prawy przycisk myszy: ustawienia wyskakującego okienka' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Tylko odczyt +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Automatycznie aktualizowany skrypt jest tylko do odczytu, chyba że wyłączysz + aktualizacje lub zezwolisz na edycję. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Zezwól na edycję +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (następna aktualizacja ją zastąpi) +reinstall: + description: Button to reinstall a script + message: Zainstaluj ponownie +reloadTab: + description: Label of action to reload the tab + message: Odśwież kartę +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Jeśli włączono, aktywna karta zostanie załadowana ponownie po wykryciu zmian + i ten skrypt będzie odpowiadał adresowi URL karty. +removeAllScripts: + description: Button to remove all scripts + message: Usuń wszystkie skrypty + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Rozróżnianie wielkości liter @@ -695,14 +1008,80 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Ustawienia -titleScriptUpdated: - description: Notification title for script updates. - message: Aktualizacja +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Odśwież stronę pomijając skrypty +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Wyłączyłeś skrypty użytkownika dla tej strony. Aby je uruchomić, załaduj + ponownie lub nawiguj w karcie. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Sortuj:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Przestań śledzić +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Kolor normalnej odznaki +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Kolor plakietki, kiedy witryna nie nadaje się do wstrzykiwania (jest na + czarnej liście lub nie jest obsługiwana) titleSearchHint: description: Hover title for search icon in dashboard. message: |- * Klawisz dodaje tekst do historii auto-uzupełnienia * Obsługiwana jest składnia RegExp: /re/ i /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Klawisz Enter dodaje tekst do historii autouzupełniania + + * We wszystkich warunkach wielkość liter nie jest uwzględniana + + * Warunki oddzielone spacjami można łączyć + + * Szukaj według metadanych: "Awesome Script" "Description" + + * Szukaj według tagów: #tag1 #tag2 + + * Szukaj według nazwy skryptu: name:"awesome name" + + * Szukaj według kodu skryptu: code:"awesome code" + + * Pomijanie wyszukiwania: !#tag2 !name:"unwanted" + + * Wyrażenia regularne: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? with space" +toggleInjection: + description: Label for a keyboard shortcut. + message: Przełącz tryb wstrzykiwania skryptu użytkownika +trackEdits: + description: Button in a script installation dialog. + message: Śledź zmiany zewnętrzne +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: Pozostaw tę stronę otwartą, aby śledzić zmiany w pliku lokalnym +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Zaktualizuj skrypty ($1) +updateScript: + description: Button to update one script. + message: Aktualizuj +updateScriptsAll: + description: Command/button to update all scripts. + message: Zaktualizuj wszystkie skrypty +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Zaktualizuj skrypty bieżącej karty valueLabelKey: description: Label for key of a script value. message: Klucz (string) @@ -712,6 +1091,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: Wszystkie wartości (serializowane jako JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: Odwiedź stronę internetową rozszerzenia + touched: false diff --git a/src/_locales/pt_BR/messages.yml b/src/_locales/pt_BR/messages.yml index dd38b12c11..1c3ac5e15c 100644 --- a/src/_locales/pt_BR/messages.yml +++ b/src/_locales/pt_BR/messages.yml @@ -1,21 +1,39 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Aplicar buttonCancel: description: Cancel button on dialog. message: Cancelar +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Verificar atualizações + touched: false buttonClose: description: Button to close window. message: Fechar buttonConfirmInstallation: description: Button to confirm installation of a script. message: Confirmar instalação + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Confirmar reinstalação + touched: false buttonDisable: description: Button to disable a script. message: Desativar +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: Baixar temas + touched: false buttonEdit: description: Button to edit a script. message: Editar buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: '' + message: >- + Você também pode clicar com o botão direito do mouse, Ctrl, roda do mouse, + no nome do script. buttonEmptyRecycleBin: description: Button to empty the recycle bin. message: Limpar a lixeira agora! @@ -24,20 +42,20 @@ buttonEnable: message: Ativar buttonExportData: description: Button to open the data export dialog. - message: Exportar para ZIP + message: Exportar em zip buttonFilter: description: Button to show filters menu. message: Filtros touched: false buttonHome: description: Button to open homepage. - message: Website + message: Página inicial buttonImportData: description: Button to choose a file for data import. - message: Importar a partir de ZIP + message: Importar a partir do zip buttonInstallFromURL: description: Button to ask for URL of a user script. - message: Instalar a partir de URL + message: Instalar a partir da URL buttonInstallOptions: description: Button to show options of installation confirm page. message: Opções @@ -63,6 +81,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Resetar +buttonResetSettings: + description: Button in settings page to reset all settings + message: Redefinir configurações buttonRestore: description: Button to restore a removed script. message: Restaurar @@ -71,23 +92,32 @@ buttonSave: message: Salvar buttonSaveClose: description: Button to save modifications of a script and then close the editing page. - message: Salvar e fechar + message: Salvar & Fechar +buttonSaved: + description: Button text after saving. + message: Salvo buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. - message: Estado do editor + message: Mostrar editor de estado buttonSupport: description: Button to open support page. message: Página de suporte +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. - message: Anular - touched: false + message: Desfazer buttonUpdate: - description: Check a script for updates. - message: Procurar atualizações + description: Button to update a script. + message: Atualizar buttonUpdateAll: description: Check all scripts for updates. - message: Procurar atualizações para todos scripts + message: Verificar atualizações para todos + touched: false buttonVacuum: description: Button to vacuum extension data. message: Limpar base de dados @@ -97,137 +127,233 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Otimizando os dados... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Atualização automática está desativada para este script! + Clique em OK para atualizá-lo mesmo assim. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- - As alterações não foram guardadas! - Clique em OK para rejeitá-las ou em Cancelar para ficar. + As modificações não foram salvas! + Clique em OK para descartá-los ou cancelar para ficar. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Reverter todas as alterações feitas no banco de dados (importação, + atualização, edição, personalização) descBlacklist: - description: HTML Description for the global blacklist. - message: Os URLs correspondidos por esta lista não serão injetados com scripts. + description: Description for the global injection blacklist. + message: URL nesta lista não será injetado por scripts. +descBlacklistNet: + description: Description for the global network blacklist. + message: '' descCustomCSS: description: Description of custom CSS section. message: >- - CSS personalizado para as páginas de opções e de instalação de scripts. Se - não tiver a certeza da utilidade disto, por favor, não altere. + Personalizar CSS para página de opções e página de instalação de script. Se + você não tem certeza do que é isso, por favor, não edite. descEditorOptions: description: Description of editor options JSON section. + message: >- + Opções personalizadas para CodeMirror e addons em objeto JSON como + {"indentUnit":2, "smartIndent":true} no entanto note que + alguns deles podem não funcionar no Violentmonkey. Consulte à lista completa. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: '' +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. message: '' +descScriptTemplate: + description: Description of script template section. + message: >- + Variáveis suportadas: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + com formato compatível com MomentJS, por exemplo <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: grupo +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: esconda +disabledScriptsSelector: + description: Label of the option. + message: 'Scripts Desativados:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: mostre editHelpDocumention: description: Label in the editor help tab for the documentation link. - message: '' + message: >- + Documentação sobre bloco de metadados de script do usuário e API + GM: editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. - message: 'Atalhos do teclado:' + message: 'Teclas de Atalhos:' editHowToHint: description: The text of the how-to link in the editor header. message: Usar outro editor? editLabelMeta: description: Metadata section in settings tab of script editor. - message: Metadados personalizados + message: Personalizar metadados editLabelSettings: description: Settings section in settings tab of script editor. - message: Definições do script + message: Configurações do script editLongLine: description: Shown in the editor in lines that were cut due to being too long - message: Linha muito longa + message: A linha é muito longa. editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long - message: '' + message: >- + Esta linha é muito longa, por isso seu texto foi encolhido para evitar + atrasos na edição. + + Você pode ajustar o limite em configurações avançadas, por exemplo: + + "maxDisplayLength": 20000 editNavCode: description: Label of code tab in script editor. message: Código editNavSettings: description: Label of settings tab in script editor. - message: Definições + message: Configurações editNavValues: description: Label of values tab in script editor. - message: Valores + message: Valor editValueAll: description: Button to show/edit the entire script value storage. message: Tudo + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: Cancelar -editValueSave: - description: Button to save modification of a script value. - message: Salvar + message: Mostrar/editar todo o armazenamento de valor do script extDescription: - description: 'Description for this extension, will be displayed in web store' - message: Fornece suporte a scripts de usuários para navegadores. + description: Description for this extension, will be displayed in web store + message: >- + Um gerenciador de script de usuário de código aberto que tem suporte à + muitos navegadores extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' - message: '' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: Configurações de Lista negra do Violentmonkey failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) - message: '' + message: >- + Violentmonkey não pode executar scripts de usuários nesta página + + (Como exemplos comuns á interface do navegador, uma extensão bloqueada via + políticas, motor de pesquisa de site no Opera) +failureReasonRestarted: + description: Shown in the popup. + message: Violentmonkey foi reiniciado. Por favor recarregue a aba para rodar scripts +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Existem vários métodos para instalar ou rastrear edições em um arquivo + local: <1> Arraste e solte o arquivo em uma página ou pop-up aberto do + Violentmonkey, incluindo este. <2> Instale um servidor HTTP local para este + arquivo e abra-o via http://localhost. <3> Habilite "Permitir acesso a URLs + de arquivos" nos detalhes do Violentmonkey na página chrome://extensions. + Isto é perigoso porque qualquer script de usuário pode ler qualquer arquivo + local. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: ordem alfabética + message: Ordem alfabética filterExecutionOrder: description: Label for option to sort scripts in execution order. - message: ordem de execução + message: execução filterLastUpdateOrder: description: Label for option to sort scripts by last update time. + message: Última atualização +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: Tudo + touched: false filterScopeCode: description: Option in dashboard's search scope filter. - message: '' + message: Código + touched: false filterScopeName: description: Option in dashboard's search scope filter. - message: '' + message: Nome + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: Tamanho genericError: description: Label for generic error. - message: '' + message: Erro genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' - message: Desativado + description: To indicate something is turned off or disabled, similar to "no". + message: Desligado genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' - message: Ativado + description: To indicate something is turned on or enabled, similar to "yes". + message: Ligado genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. + message: Usar configuração global +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). message: '' headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Lixeira +helpForLocalFile: + description: Label of the checkbox. + message: Mostrar as instruções para scripts locais arrastados e soltos +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: para $1 scripts correspondentes +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. - message: 'Coloque o URL:' + message: 'Inserir URL:' hintRecycleBin: description: Hint for recycle bin. - message: '' + message: Scripts removidos estão listados aqui e serão mantidos por 7 dias. hintUseDownloadURL: description: Shown as a place holder for @updateURL when it is not assigned - message: Utilizar @downloadURL + message: Usar @downloadURL hintVacuum: description: Hint for vacuuming data. - message: Eliminar as redundâncias e tentar recarregar os recursos em falta no cache. + message: Descartar a redundância e tente recarregar os recursos perdidos em cache. +install: + description: Label for button to install a script. + message: Instalar installFrom: description: Label for button to install script from a userscript site. message: Instalar a partir do $1 installOptionClose: description: Option to close confirm window after installation. - message: Fechar após instalação -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Monitorar o arquivo local até esta janela ser fechada + message: Fechar após a instalação + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before window is closed. - message: '' + message: >- + A aba de arquivos de origem deve ser mantida aberta no Firefox 68 e mais + novos. labelAbout: description: Label shown on top of the about page. message: Sobre o Violentmonkey @@ -243,156 +369,189 @@ labelAuthor: message: 'Autor: ' labelAutoReloadCurrentTab: description: Option to reload current tab after a script is switched on or off from menu. - message: >- - Recarregar o separador atual após ativar/desativar um script a partir do - menu + message: Recarregar a aba atual após ligar/desligar um script do menu labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. - message: '' + message: Os scripts atuais continuarão em execução até que você recarregue a aba + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: '' + message: >- + Verificar se há atualizações de script a cada $1 dia(s), use 0 para + desativar +labelBackup: + description: Label of the import/export section in settings. + message: Backup + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Backup e manutenção labelBadge: description: Label for option to show number on badge. - message: Exibir no Ícone + message: 'Exibição do emblema: ' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Cor do emblema: ' labelBadgeNone: description: Option to display nothing on badge. - message: nada + message: Nenhum labelBadgeTotal: description: Option to display total number of running scripts on badge. - message: número de scripts em execução + message: Número de scripts em execução labelBadgeUnique: description: Option to display number of unique running scripts on badge. - message: número de scripts únicos em execução + message: Número de scripts únicos em execução labelBlacklist: description: Label for global blacklist settings in security section. message: Lista negra labelContributors: description: Label for link to contributors. - message: Contribuidores + message: Colaboradores labelCurrentLang: description: Label of current language. message: 'Idioma atual: ' labelCustomCSS: description: Label for custom CSS section. - message: Estilo personalizado + message: Personalizar estilo labelDataExport: description: Section title of data export. message: Exportar dados + touched: false labelDataImport: description: Section title of data import. message: Importar dados + touched: false labelDonate: description: Label of link to donate page. - message: Fazer uma doação + message: Doar + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. - message: 'URL de download:' + message: 'URL para baixar:' labelEditValue: description: Label shown in the panel to edit a script value. - message: '' + message: Editando valor do script labelEditValueAll: description: Label shown in the panel to edit the entire script value storage. - message: '' + message: Editando armazenamento de script labelEditor: description: Label for Editor settings - message: '' + message: Editor +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: apenas scripts ativados labelExclude: description: Label of @exclude rules. - message: Regras @exclude + message: '@excluir regras' labelExcludeMatch: description: Label of @exclude-match rules. - message: Regras @exclude-match + message: '@exclude-match rules' labelExportScriptData: description: Option to export script data along with scripts. message: Exportar dados do script labelExposeStatus: description: Option in advanced settings. - message: '' + message: 'Expor a versão instalada em sites de catálogo de script do usuário: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Modo alternativo de $1 no Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Um método alternativo de injeção para scripts desde o + Firefox 59, mais rápido que o modo padrão. Da mesma forma que o "Modo de + página síncrona", também aumenta o consumo de memória, portanto, você pode + desativá-lo se seus scripts funcionarem corretamente sem essa opção. labelFeedback: description: Label of link to feedback page. - message: Fornecer comentários -labelFilterSort: - description: Label for sort filter. - message: Ordenar por $1 + message: Comentários labelGeneral: description: Label for general settings. - message: Gerais + message: Geral labelHelpTranslate: description: Label for link to localization guide in about tab - message: Ajude com a tradução + message: Ajuda com à tradução labelHomepage: description: Label for home page in about tab. - message: '' + message: Página inicial labelHomepageURL: description: Label of script @homepageURL in custom meta data. - message: 'URL do website:' + message: 'URL da página inicial:' +labelIconURL: + description: Label for the input. + message: 'URL do ícone:' labelImportScriptData: description: Option to import script data along with scripts. - message: '' + message: Importar dados do script labelImportSettings: description: Label for option to import settings from zip file. - message: Importar definições + message: Importar configurações labelInclude: description: Label of @include rules. - message: Regras @include + message: '@incluir regras' labelInjectionMode: - description: Label for default option to inject scripts. - message: '' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Modo de injeção: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. - message: Instalando o script + message: Instalando script labelKeepOriginal: description: Option to keep the original match or ignore rules. - message: Manter originais + message: Mantenha o original labelLastUpdatedAt: description: Label shown on last updated time. - message: 'Última atualização: $1' + message: Última atualização em $1 labelLineNumber: description: Label for line number jumper. - message: 'Linha n.º: ' + message: 'Sem linha.: ' labelMatch: description: Label of @match rules. - message: Regras @match + message: '@match rules' labelName: description: Label of script name. message: 'Nome:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Executar em quadros: ' labelNoName: description: Text as the name of a script when no @name is assigned. - message: Sem nome - touched: false + message: Sem Nome labelNoSearchScripts: description: Message shown when no script is found in search results. - message: Nenhum script encontrado. + message: Nenhum script é encontrado. labelNotifyThisUpdated: description: >- A per-script option in editor to enable notification when this script is updated. The text follows "Allow update" checkbox option so it's like a continuation of the phrase. - message: '' + message: ', em seguida, notificar:' labelNotifyUpdates: description: Option to show notification when script is updated. - message: Notificar sobre atualizações de scripts + message: Notificar as atualizações do script labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. - message: '' -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: '' + message: ignorar a notificação por script ("configurações" aba no editor) labelPrivacyPolicy: description: Label of link to privacy policy message: Política de Privacidade +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Reinstalando do script labelRelated: description: Label of related links. message: 'Links relacionados: ' labelRemovedAt: description: Label for the time when the script is removed. - message: '' + message: Removido em $1 labelReplace: description: Label for replace input in search box. - message: 'Substituir por: ' + message: 'Substitua por: ' labelRunAt: description: Label of script @run-at properties in custom meta data. message: 'Executar em: ' @@ -401,80 +560,116 @@ labelRunAtDefault: message: (Padrão) labelScriptTemplate: description: Label for custom script template. - message: '' + message: Modelo de script personalizado labelSearch: description: Label for search input in search box. - message: 'Localizar: ' + message: 'Pesquisar por: ' labelSearchScript: description: Placeholder for script search box. - message: Localizar scripts... + message: Pesquisar scripts... labelSettings: description: Label shown on the top of settings page message: Configurações +labelShowOrder: + description: Label for option in dashboard -> script list + message: Mostrar posições de ordem de execução +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Sincronizar labelSyncAnonymous: description: Label for using anonymous account. - message: '' + message: Usar conta anônima labelSyncAuthorize: description: Label for button to authorize a service. message: Autorizar labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Autorizando +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: Nenhum labelSyncPassword: description: Label for input to hold password. - message: 'Senha:' + message: 'Senha: ' labelSyncReauthorize: description: Option to reauthorize sync service when expired. - message: Reautorizar automaticamente quando expirar + message: Reautorizar automaticamente quando expirado touched: false labelSyncRevoke: description: Label for button to revoke authorization for a service. message: Revogar labelSyncScriptStatus: description: Label for option to sync script status. - message: Sincronizar estados dos scripts + message: Status de sincronização do script labelSyncServerUrl: description: Label for input to hold server URL. - message: '' + message: 'URL do servidor: ' labelSyncService: description: Label for sync service select. - message: Sincronizar em + message: Sincronizar com labelSyncUsername: description: Label for input to hold username. - message: Nome de usuário + message: 'Nome do usuário: ' +labelTags: + description: Label for custom tags. + message: 'Tags (separados por espaço):' +labelTheme: + description: Label for the visual theme option. + message: 'Tema: ' labelTranslator: description: Label of translator. - message: 'Tradução: ' + message: 'Tradutor: ' touched: false labelUpdateURL: description: Label of script @updateURL in custom meta data. - message: 'URL de atualização:' + message: 'Atualizar URL:' labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: Coluna única labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. - message: '' + message: Ver como tabela +labelWidth: + description: Width. + message: 'Largura:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Modo síncrono $1 +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Ativar somente se você tiver um script que precisa ser executado antes da + página começar o carregamento e atualmente está iniciando tarde demais. + Assim como a modo de injeção instantânea no Tampermonkey, esta opção está + usando o XHR síncromo preterido, assim no Chrome/Chromium você verá avisos + no console devtools, embora você pode ignorá-los com segurança, pois os + efeitos adversos são insignificantes neste caso. Você pode ocultar os avisos + para sempre clicando com o botão direito do mouse. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (exceto no modo anônimo e em sites com cookies desativados) lastSync: description: Label for last sync timestamp. - message: 'Última sincronização: $1' + message: Última sincronização em $1 learnBlacklist: description: Refers to a link to introduce blacklist patterns. - message: Saiba mais sobre os padrões para a lista negra. + message: Saiba mais sobre padrões de lista negra. learnInjectionMode: description: Refers to a link to introduce injection modes. - message: Saiba mais sobre os modos de injeção + message: Saiba mais sobre os modos de injeção. menuCommands: description: Menu item to list script commands. - message: Comandos do script + message: Comandos de script touched: false menuDashboard: description: Label for menu item to open dashboard. @@ -483,66 +678,89 @@ menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: '' + message: Excluir... menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. - message: '' + message: >- + A aba atual será recarregada automaticamente se você ativar essa opção em + configurações gerais. + + Para aplicar alterações em todas as outras abas, recarregue-as manualmente. + + Use a aba "Configurações" do editor para obter mais flexibilidade. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Comentário menuFindScripts: description: Menu item to find scripts for a site. - message: Procurar scripts para este site + message: Encontar scripts para este site menuInjectionFailed: description: Injection error. - message: Não pode injetar alguns scripts + message: Não podemos injetar alguns scripts. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: Tentar novamente no modo "automático" +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Scripts combinados desativados menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. - message: '' + message: Sub-frames apenas scripts menuMatchedScripts: description: Label for menu listing matched scripts. - message: Scripts correspondidos + message: Scripts combinados menuNewScript: description: Menu item to create a new script. - message: Criar novo script + message: Crie um novo script menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Scripts desativados menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Scripts ativados msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. - message: Procurando atualizações... + message: Verificando atualizações... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Clique para abrir a documentação do MomentJS. Tokens permitidos: $1. Use + [colchetes] para proteger texto literal. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of the script. - message: '' + message: Erro na busca de recurso! msgErrorFetchingScript: description: Message shown when Violentmonkey fails fetching a new version of the script. - message: Erro ao obter script! + message: Erro na busca de script! msgErrorFetchingUpdateInfo: description: Message shown when Violentmonkey fails fetching version data of the script. - message: Falha ao obter informações de atualização. + message: Falha na busca de informações de atualizações. msgErrorLoadingData: description: >- Message shown on confirm page when the script to be installed cannot be loaded. - message: Erro ao carregar os dados do script. + message: Erro no carregamento dos dados do script. msgErrorLoadingDependency: description: Message shown when not all requirements are loaded successfully. - message: Erro ao carregar dependências. + message: Erro no carregamento das dependências. msgImported: description: >- Message shown after import. There is an argument referring to the count of scripts imported. - message: $1 item(ns) importado(s). + message: $1 item(s) são importados. msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito window. - message: '' + message: >- + Alterações que você faz no modo de navegação anônima também se aplicam ao + seu perfil principal. msgInstalled: description: Message shown when a script is installed. message: Script instalado. @@ -557,21 +775,31 @@ msgLoadedData: touched: false msgLoading: description: Message shown in the options page before script list is loaded. - message: Carregando... + message: Carregando ... touched: false msgLoadingData: description: Message shown on confirm page when the script to be installed is loading. - message: A carregar dados do script... + message: Carregando dados do script... msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. - message: A carregar dependências... ($1/$2) + message: Carregando dependências... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Falta recursos necessários. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. - message: >- - Conflito de namespace entre scripts! Por favor, altere o @name e o - @namespace. + message: |- + Um script como este já está instalado ou ainda está na Lixeira. + Por favor, use um @name e @namespace diferente aqui ou edite esse script. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Um script com o mesmo @name e @namespace já está instalado. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -580,24 +808,39 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Nenhuma atualização encontrada. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Erro ao atualizar scripts. Clique para abri-los. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Por favor, salve novamente ou reinstale este(s) script(s):' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: ' (o código é o mesmo)' msgSavedBlacklist: description: Message shown when blacklist are saved. message: Lista negra atualizada. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. - message: Estilo personalizado atualizado. + message: Estilo personalizado foi atualizado. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. - message: Opções de editor estão atualizadas. + message: As opções do editor foram atualizadas. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. - message: '' + message: Modelo de script personalizado foi atualizado. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: '[$1] foi atualizado!' + message: Script [$1] foi atualizado! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff - message: '' + message: Mostrar/Ocultar msgSyncError: description: Message shown when sync failed. message: Erro de sincronização! @@ -607,12 +850,22 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Erro de inicialização! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Ainda não autorizado.. msgSyncReady: description: Message shown when sync will start soon. - message: A sincronização será iniciada em breve... + message: A sincronização começará em breve... + touched: false msgSyncing: description: Message shown when sync is in progress. - message: Sincronizando... + message: Sincronização em andamento... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Erro de sintaxe? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: '' msgUpdated: description: Message shown when a script is updated/reinstalled. message: Script atualizado. @@ -621,33 +874,104 @@ msgUpdating: message: Atualizando... noValues: description: Label shown when there is no value for current script. - message: '' + message: Nenhum valor é armazenado optionEditorWindow: description: Label for the option in settings - message: '' + message: Abrir editor em popup na nova janela optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support onBoundsChanged - message: '' + message: >- + A posição da janela do editor será lembrada apenas em redimensionando ou + salvando optionEditorWindowSimple: description: Label for the editor window type - message: '' + message: Ocultar omnibox +optionPopup: + description: Label of the popup menu section in settings. + message: Menu pop-up e ícone optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: Ativar primeiro +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Agrupar scripts desativados + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Agrupar por @run-at stage optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: Esconder desativado(s) + message: Ocultar scripts desativados + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Mostrar scripts desativados + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. - message: Mostrar scripts ativos primeiro + message: Mostrar scripts habilitados primeiro +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Tema de interface do usuário: ' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: Automático +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: Escuro +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: Claro +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Atualizar +popupSettings: + description: Item inside the popup's "⋮" menu + message: Configurações de Popup +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Somente leitura +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + O script atualizado automaticamente é somente leitura, a menos que você + desative as atualizações ou permita ter edição. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Permitir edições +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: '' +reinstall: + description: Button to reinstall a script + message: Reinstale +reloadTab: + description: Label of action to reload the tab + message: Recarregue a aba +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: '' +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search - message: '' + message: Respeitar maiúsculas/minúsculas searchUseRegex: description: Option to perform a regular expression search - message: '' + message: Usar Regex sideMenuAbout: description: 'Side menu: About' message: Sobre @@ -657,21 +981,74 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Configurações -titleScriptUpdated: - description: Notification title for script updates. - message: Atualização +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Recarregue a pagina sem os scripts +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: '' +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: '' +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Cor normal do emblema +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: Cor do emblema quando o site não é injetável (lista negra ou sem suporte) titleSearchHint: description: Hover title for search icon in dashboard. + message: |- + * chave adiciona ao histórico de texto do preenchimento automático + * A sintaxe RegExp suportada são: /re/ e /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: '' +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: '' +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: '' +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Atualize scripts ($1) +updateScript: + description: Button to update one script. + message: Atualizar +updateScriptsAll: + description: Command/button to update all scripts. + message: Atualize todos os scripts +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. message: '' valueLabelKey: description: Label for key of a script value. - message: '' + message: Chave (string) valueLabelValue: description: Label for value of a script value. - message: '' + message: Valor (serializado como JSON) valueLabelValueAll: description: Label for input of entire script value storage. + message: Todos os valores (serializados como JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' message: '' visitWebsite: description: Label for link to open Violentmonkey website. - message: Visitar Site + message: Visite o site + touched: false diff --git a/src/_locales/pt_PT/messages.yml b/src/_locales/pt_PT/messages.yml index b4f909c51b..fbbc0f73d5 100644 --- a/src/_locales/pt_PT/messages.yml +++ b/src/_locales/pt_PT/messages.yml @@ -1,15 +1,31 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: '' buttonCancel: description: Cancel button on dialog. message: Cancelar +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Procurar atualizações + touched: false buttonClose: description: Button to close window. message: Fechar buttonConfirmInstallation: description: Button to confirm installation of a script. message: Confirmar instalação + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Confirmar reinstalação + touched: false buttonDisable: description: Button to disable a script. message: Desativar +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: Transferir temas + touched: false buttonEdit: description: Button to edit a script. message: Editar @@ -18,7 +34,7 @@ buttonEditClickHint: message: '' buttonEmptyRecycleBin: description: Button to empty the recycle bin. - message: '' + message: Esvaziar lixo agora! buttonEnable: description: Button to enable a script. message: Ativar @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Redefinir +buttonResetSettings: + description: Button in settings page to reset all settings + message: '' buttonRestore: description: Button to restore a removed script. message: Restaurar @@ -72,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Guardar e fechar +buttonSaved: + description: Button text after saving. + message: '' buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Mostrar estado do editor buttonSupport: description: Button to open support page. message: Página de suporte +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. message: Anular - touched: false buttonUpdate: - description: Check a script for updates. - message: Procurar atualizações + description: Button to update a script. + message: Atualização buttonUpdateAll: description: Check all scripts for updates. message: Procurar atualizações para todos + touched: false buttonVacuum: description: Button to vacuum extension data. message: Otimizar base de dados @@ -97,14 +125,25 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: A otimizar dados... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: '' confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- As alterações não foram guardadas! Clique em OK para rejeitá-las ou em Cancelar para ficar. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: '' descBlacklist: - description: HTML Description for the global blacklist. + description: Description for the global injection blacklist. message: Os URLs correspondidos por esta lista não serão injetados com scripts. +descBlacklistNet: + description: Description for the global network blacklist. + message: '' descCustomCSS: description: Description of custom CSS section. message: >- @@ -117,15 +156,38 @@ descEditorOptions: como: {"indentUnit":2, "smartIndent":true}. No entanto, tenha em atenção que algumas poderão não funcionar com o Violentmonkey. Lista completa: https://codemirror.net/doc/manual.html#config +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: '' +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: '' +descScriptTemplate: + description: Description of script template section. + message: '' +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: '' +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: '' +disabledScriptsSelector: + description: Label of the option. + message: '' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: '' editHelpDocumention: description: Label in the editor help tab for the documentation link. - message: '' + message: >- + Documentação sobre o bloco de metadados do script de utilizador e a API + GM: editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. - message: '' + message: 'Atalhos do teclado:' editHowToHint: description: The text of the how-to link in the editor header. - message: '' + message: Utilizar outro editor? editLabelMeta: description: Metadata section in settings tab of script editor. message: Metadados personalizados @@ -134,7 +196,7 @@ editLabelSettings: message: Definições do script editLongLine: description: Shown in the editor in lines that were cut due to being too long - message: '' + message: Linha muito longa editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long message: '' @@ -149,24 +211,19 @@ editNavValues: message: Valores editValueAll: description: Button to show/edit the entire script value storage. - message: '' + message: Tudo + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: Cancelar -editValueSave: - description: Button to save modification of a script value. - message: Guardar + message: Exibir/editar todo o armazenamento de valores do script extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: Oferece suporte a scripts de utilizador para navegadores. extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: Na lista negra nas definições do Violentmonkey failureReasonNoninjectable: description: >- @@ -175,6 +232,12 @@ failureReasonNoninjectable: message: |- O Violentmonkey não pode executar scripts de utilizador nesta página (exemplos comuns: interface do navegador ou uma extensão) +failureReasonRestarted: + description: Shown in the popup. + message: '' +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: '' filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. message: ordem alfabética @@ -184,30 +247,58 @@ filterExecutionOrder: filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: data da última atualização +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. - message: '' + message: Tudo + touched: false filterScopeCode: description: Option in dashboard's search scope filter. - message: '' + message: Código + touched: false filterScopeName: description: Option in dashboard's search scope filter. + message: Nome + touched: false +filterSize: + description: Label for option to sort scripts by size. message: '' genericError: description: Label for generic error. - message: '' + message: Erro genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' - message: '' + description: To indicate something is turned off or disabled, similar to "no". + message: desligado genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' - message: '' + description: To indicate something is turned on or enabled, similar to "yes". + message: ligado genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. + message: Usar definições globais +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). message: '' headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Reciclagem +helpForLocalFile: + description: Label of the checkbox. + message: '' +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: '' +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Introduza o URL:' @@ -220,15 +311,16 @@ hintUseDownloadURL: hintVacuum: description: Hint for vacuuming data. message: Eliminar as redundâncias e tentar recarregar os recursos em falta na cache. +install: + description: Label for button to install a script. + message: '' installFrom: description: Label for button to install script from a userscript site. message: Instalar a partir do $1 installOptionClose: description: Option to close confirm window after installation. message: Fechar após instalação -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Monitorizar o ficheiro local até a janela ser fechada + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -256,13 +348,24 @@ labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. - message: '' + message: Os scripts atuais continuarão a executar até que tu recarregues o separador + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates message: '' +labelBackup: + description: Label of the import/export section in settings. + message: '' + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: '' labelBadge: description: Label for option to show number on badge. message: 'Mostrar no ícone: ' +labelBadgeColors: + description: Label for option group to set badge colors. + message: '' labelBadgeNone: description: Option to display nothing on badge. message: nada @@ -277,7 +380,7 @@ labelBlacklist: message: Lista negra labelContributors: description: Label for link to contributors. - message: '' + message: Contribuidores labelCurrentLang: description: Label of current language. message: 'Idioma atual: ' @@ -287,12 +390,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Exportar dados + touched: false labelDataImport: description: Section title of data import. message: Importar dados + touched: false labelDonate: description: Label of link to donate page. message: Faça um donativo + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'URL de transferência:' @@ -305,6 +411,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: Editor +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: '' labelExclude: description: Label of @exclude rules. message: Regras @exclude @@ -317,12 +426,15 @@ labelExportScriptData: labelExposeStatus: description: Option in advanced settings. message: '' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: '' +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: '' labelFeedback: description: Label of link to feedback page. message: Forneça comentários -labelFilterSort: - description: Label for sort filter. - message: Ordenar por $1 labelGeneral: description: Label for general settings. message: Gerais @@ -335,6 +447,9 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'URL do website:' +labelIconURL: + description: Label for the input. + message: '' labelImportScriptData: description: Option to import script data along with scripts. message: '' @@ -345,8 +460,10 @@ labelInclude: description: Label of @include rules. message: Regras @include labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Modo de injeção predefinido: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Modo de injeção: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: A instalar o script @@ -365,10 +482,12 @@ labelMatch: labelName: description: Label of script name. message: 'Nome:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: '' labelNoName: description: Text as the name of a script when no @name is assigned. message: Sem nome - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: Nenhum script encontrado. @@ -384,12 +503,14 @@ labelNotifyUpdates: labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: '' -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: '' labelPrivacyPolicy: description: Label of link to privacy policy - message: '' + message: Política de Privacidade +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: A reinstalar o script labelRelated: description: Label of related links. message: 'Ligações relacionadas: ' @@ -417,6 +538,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Definições +labelShowOrder: + description: Label for option in dashboard -> script list + message: Mostrar posições na ordem de execução +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Sincronizar @@ -429,6 +556,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: A autorizar +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: Nenhum @@ -454,6 +584,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: 'Nome de utilizador: ' +labelTags: + description: Label for custom tags. + message: '' +labelTheme: + description: Label for the visual theme option. + message: 'Tema: ' labelTranslator: description: Label of translator. message: 'Tradução: ' @@ -465,9 +601,23 @@ labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: Uma coluna labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. + message: Ver como tabela +labelWidth: + description: Width. + message: '' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: '' +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: '' +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. message: '' lastSync: description: Label for last sync timestamp. @@ -489,10 +639,15 @@ menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: '' + message: Excluir... menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. message: '' +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Forneça comentários menuFindScripts: description: Menu item to find scripts for a site. message: Procurar scripts para este site @@ -500,7 +655,12 @@ menuInjectionFailed: description: Injection error. message: '' menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". + message: '' +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. message: '' menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. @@ -512,14 +672,17 @@ menuNewScript: description: Menu item to create a new script. message: Criar um novo script menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Scripts desativados menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Scripts ativados msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: A procurar atualizações... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: '' msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -571,6 +734,11 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: A carregar dependências... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: '' msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent @@ -578,6 +746,11 @@ msgNamespaceConflict: message: >- Conflito de namespace entre scripts! Por favor, altere o @name e o @namespace. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: '' msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -586,24 +759,39 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Nenhuma atualização encontrada. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: '' +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: '' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: '' msgSavedBlacklist: description: Message shown when blacklist are saved. message: Lista negra atualizada. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Estilo personalizado atualizado. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Opções do editor atualizadas. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: O modelo de scripts personalizado foi atualizado. + touched: false msgScriptUpdated: description: Notification message for script updates. message: $1 foi atualizado! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff - message: '' + message: Mostrar/Ocultar msgSyncError: description: Message shown when sync failed. message: Erro de sincronização! @@ -613,12 +801,22 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Erro de inicialização! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Ainda não autorizado. msgSyncReady: description: Message shown when sync will start soon. message: A sincronização será iniciada em breve... + touched: false msgSyncing: description: Message shown when sync is in progress. message: A sincronizar... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: '' +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: '' msgUpdated: description: Message shown when a script is updated/reinstalled. message: Script atualizado. @@ -638,16 +836,83 @@ optionEditorWindowHint: message: '' optionEditorWindowSimple: description: Label for the editor window type + message: Ocultar omnibox +optionPopup: + description: Label of the popup menu section in settings. message: '' optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. + message: Ativado primeiro +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Agrupar scripts desativados + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. message: '' optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: '' + message: Ocultar scripts desativados + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Mostrar scripts desativados + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Mostrar scripts ativos primeiro +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: '' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: '' +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Atualização +popupSettings: + description: Item inside the popup's "⋮" menu + message: '' +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: '' +readonlyNote: + description: Warning when trying to type in the editor. + message: '' +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: '' +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: '' +reinstall: + description: Button to reinstall a script + message: '' +reloadTab: + description: Label of action to reload the tab + message: '' +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: '' +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Respeitar maiúsculas/minúsculas @@ -663,12 +928,56 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Definições -titleScriptUpdated: - description: Notification title for script updates. - message: Atualização +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: '' +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: '' +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: '' +titleBadgeColor: + description: Tooltip for option to set badge color. + message: '' +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: '' titleSearchHint: description: Hover title for search icon in dashboard. message: '' + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: '' +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: '' +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: '' +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: '' +updateScript: + description: Button to update one script. + message: Atualização +updateScriptsAll: + description: Command/button to update all scripts. + message: '' +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: '' valueLabelKey: description: Label for key of a script value. message: Chave (cadeia de carateres) @@ -677,7 +986,14 @@ valueLabelValue: message: Valor (serializado em JSON) valueLabelValueAll: description: Label for input of entire script value storage. + message: Todos os valores (serializados em JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: Visitar website + touched: false diff --git a/src/_locales/ro/messages.yml b/src/_locales/ro/messages.yml index 8f1ab0bdda..8d15573679 100644 --- a/src/_locales/ro/messages.yml +++ b/src/_locales/ro/messages.yml @@ -1,26 +1,40 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Aplică buttonCancel: description: Cancel button on dialog. message: Anulează +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Caută actualizări + touched: false buttonClose: description: Button to close window. message: Închide buttonConfirmInstallation: description: Button to confirm installation of a script. message: Confirmă instalarea + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Confirmă reinstalarea + touched: false buttonDisable: description: Button to disable a script. message: Dezactivează +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: Descarcă teme + touched: false buttonEdit: description: Button to edit a script. message: Editează buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: >- - Poți și să faci clic dreapta, Ctrl+clic, clic cu rotița pe numele - scriptului. + message: Poți și să dai clic dreapta, Ctrl+clic, clic cu rotița pe numele scriptului. buttonEmptyRecycleBin: description: Button to empty the recycle bin. - message: Golește coșul de gunoi acum! + message: Golește coșul de reciclare acum! buttonEnable: description: Button to enable a script. message: Activează @@ -49,7 +63,7 @@ buttonNew: message: Nou buttonOK: description: OK button on dialog. - message: Ok + message: OK buttonRecycleBin: description: Button to list scripts in recycle bin. message: Coș de reciclare @@ -65,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Resetează +buttonResetSettings: + description: Button in settings page to reset all settings + message: Resetează setările buttonRestore: description: Button to restore a removed script. message: Restaurează @@ -74,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Salvează și închide +buttonSaved: + description: Button text after saving. + message: Salvat buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Afișează starea editorului buttonSupport: description: Button to open support page. message: Pagină de asistență +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. - message: Revocă - touched: false + message: Anulează buttonUpdate: - description: Check a script for updates. - message: Caută actualizări + description: Button to update a script. + message: Actualizează buttonUpdateAll: description: Check all scripts for updates. message: Caută actualizări pentru toate + touched: false buttonVacuum: description: Button to vacuum extension data. message: Curăță baza de date @@ -99,14 +125,33 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Se curăță datele... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Actualizarea automată este dezactivată pentru acest script! + Dă clic pe OK pentru a-l actualiza oricum. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Modificările nu sunt salvate! - Clic pe Ok pentru a le ignora sau Anulează pentru a rămâne. + Dă clic pe OK pentru a le ignora sau Anulează pentru a rămâne. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Revocă toate modificările efectuate în baza de date (importare, actualizare, + editare, personalizare) descBlacklist: - description: HTML Description for the global blacklist. - message: Scripturile nu vor fi injectate în URL-urile corespondente acestei liste. + description: Description for the global injection blacklist. + message: >- + Lista neagră pentru injectare (scripturile nu vor rula în site-urile + coincidente) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Lista neagră pentru rețea (scripturile nu se vor conecta la site-urile + coincidente și la cookie-urile acestora) descCustomCSS: description: Description of custom CSS section. message: >- @@ -116,16 +161,45 @@ descCustomCSS: descEditorOptions: description: Description of editor options JSON section. message: >- - Opțiuni personalizate pentru CodeMirror și suplimente într-un obiect JSON - precum {"indentUnit":2, "smartIndent":true}. Cu toate acestea, - reține că unele dintre ele pot să nu funcționeze în Violentmonkey. Vezi {"indentUnit":2, "smartIndent":true} Cu toate acestea, reține + că unele dintre ele pot să nu funcționeze în Violentmonkey. Vezi lista completă. Pentru a folosi o temă - CodeMirror personalizată, specifică numele fișierului acesteia ca - "theme": "3024-day" aici și lipește CSS-ul actual al temei în - inputul „Stil personalizat” de mai jos. + rel="noopener noreferrer">lista completă. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Opțiunile de tip boolean pot fi comutate dând dublu clic pe valoare: + true = activat, false = dezactivat. Opțiunile + numerice care se termină cu Delay, Interval, + Rate, Time sunt exprimate în milisecunde. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Opțiuni nestandardizate: autocompleteOnTyping este o întârziere + în milisecunde pentru afișarea sugestiilor de completare automată după ce + tastezi (0 = dezactivat), killTrailingSpaceOnSave + elimină automat spațiile sfârșiturilor liniilor în fiecare linie atunci când + salvezi, showTrailingSpace afișează spațiile sfârșiturilor + liniilor sub formă de puncte. +descScriptTemplate: + description: Description of script template section. + message: >- + Variabile suportate: <{{{name}}>>, <{{{url}}>, <{{{date}}>, + <{{{date:format}}> cu un format compatibil cu MomentJS, de exemplu + <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: grupează-le +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: ascunde-le +disabledScriptsSelector: + description: Label of the option. + message: 'Scripturi dezactivate:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: afișează-le editHelpDocumention: description: Label in the editor help tab for the documentation link. message: >- @@ -145,12 +219,12 @@ editLabelSettings: message: Setări pentru script editLongLine: description: Shown in the editor in lines that were cut due to being too long - message: Rândul este prea lung + message: Linia este prea lungă editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long message: >- - Acest rând este prea lung, astfel că textul acestuia este restrâns pentru a - evita întârzierile la editare. + Această linie este prea lungă, astfel că textul acesteia este restrâns + pentru a evita întârzierile la editare. Poți ajusta limita în setările avansate, de exemplu: @@ -167,64 +241,106 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: Toate + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: Afișează/editează toate valorile scriptului pentru spațiul de stocare -editValueCancel: - description: Button to cancel modification of a script value. - message: Anulează -editValueSave: - description: Button to save modification of a script value. - message: Salvează extDescription: - description: 'Description for this extension, will be displayed in web store' - message: 'Un manager de userscripturi, open source, care suportă o grămadă de browsere' + description: Description for this extension, will be displayed in web store + message: Un manager de userscripturi, open source, care suportă o grămadă de browsere extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' - message: Trecut în lista neagră din setările Violentmonkey + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: Trecut în lista neagră în setările Violentmonkey failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) - message: |- + message: >- Violentmonkey nu poate rula userscripturi în această pagină - (exemple uzuale: interfața de utilizator a browserului sau o extensie) + + (exemple uzuale: interfața de utilizator a browserului, o extensie, blocat + via politici, un site al unui motor de căutare în Opera) +failureReasonRestarted: + description: Shown in the popup. + message: >- + Violentmonkey a fost repornit. Te rugăm să reîncarci fila pentru a rula + userscripturi. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Există mai multe metode de instalare sau de urmărire a editărilor într-un + fișier local: <1> Trage și plasează fișierul într-o pagină Violentmonkey + deschisă sau într-o fereastră pop-up, inclusiv aceasta. <2> Instalează un + server HTTP local pentru acest fișier și deschide-l prin http://localhost. + <3> Activează „Permite accesul la adresele URL de fișiere” în detaliile + pentru Violentmonkey din pagina chrome://extensions. Acest lucru este + periculos, deoarece orice userscript poate citi orice fișier local. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: ordinea alfabetică + message: alfabetică filterExecutionOrder: description: Label for option to sort scripts in execution order. - message: ordinea executării + message: executare filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: momentul ultimei actualizări +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: Toate + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Cod + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: Nume + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: dimensiune genericError: description: Label for generic error. message: Eroare genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: dezactivat genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: activat genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: folosește setarea globală +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + Tiparele „#” funcționează numai atunci când deschizi inițial site-ul sau + reîncarci fila: $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Coș de reciclare +helpForLocalFile: + description: Label of the checkbox. + message: Afișează instrucțiunile pentru scripturile locale trase și plasate +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: pentru $1 scripturi coincidente +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Introdu URL-ul:' @@ -239,15 +355,16 @@ hintVacuum: message: >- Înlătură datele redundante și încearcă să reîncarce resursele lipsă din cache. +install: + description: Label for button to install a script. + message: Instalează installFrom: description: Label for button to install script from a userscript site. message: Instalează de pe $1 installOptionClose: description: Option to close confirm window after installation. message: Închide după instalare -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Urmărește fișierul local înainte de închiderea acestei ferestre + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -276,14 +393,25 @@ labelAutoReloadCurrentTabDisabled: Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: Scripturile actuale vor continua să ruleze până când reîncarci fila + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates message: >- Caută actualizări pentru scripturi la fiecare $1 zi(le), folosește 0 pentru a dezactiva +labelBackup: + description: Label of the import/export section in settings. + message: Backup + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Backup și mentenanță labelBadge: description: Label for option to show number on badge. - message: 'Afișează pe insignă: ' + message: 'Insignă:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Culori pentru insignă:' labelBadgeNone: description: Option to display nothing on badge. message: nimic @@ -308,12 +436,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Exportare de date + touched: false labelDataImport: description: Section title of data import. message: Importare de date + touched: false labelDonate: description: Label of link to donate page. message: Donează + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'URL pentru descărcare:' @@ -326,6 +457,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: Editor +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: numai scripturile activate labelExclude: description: Label of @exclude rules. message: Reguli @exclude @@ -338,12 +472,20 @@ labelExportScriptData: labelExposeStatus: description: Option in advanced settings. message: 'Expune versiunea instalată pe site-urile de catalog ale userscripturilor: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Mod $1 alternativ în Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + O metodă alternativă de injectare pentru scripturile + începând cu Firefox 59, mai rapidă decât modul implicit. Similar cu „Mod + page sincron”, crește și consumul de memorie, astfel încât este posibil să + vrei să îl dezactivezi dacă scripturile tale funcționează corect fără + această opțiune. labelFeedback: description: Label of link to feedback page. message: Feedback -labelFilterSort: - description: Label for sort filter. - message: Sortează după $1 labelGeneral: description: Label for general settings. message: Generale @@ -356,18 +498,23 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'URL pentru pagina de start:' +labelIconURL: + description: Label for the input. + message: 'URL pentru pictogramă:' labelImportScriptData: description: Option to import script data along with scripts. message: Importă datele scripturilor labelImportSettings: description: Label for option to import settings from zip file. - message: Importă setări + message: Importă setările labelInclude: description: Label of @include rules. message: Reguli @include labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Modul de injectare implicit: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Modul de injectare: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: Instalarea scriptului @@ -379,17 +526,19 @@ labelLastUpdatedAt: message: Ultima actualizare în $1 labelLineNumber: description: Label for line number jumper. - message: 'Rândul nr.: ' + message: 'Linia nr.: ' labelMatch: description: Label of @match rules. message: Reguli @match labelName: description: Label of script name. message: 'Nume:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: Rulează în frame-uri labelNoName: description: Text as the name of a script when no @name is assigned. - message: Fără nume - touched: false + message: Niciun nume labelNoSearchScripts: description: Message shown when no script is found in search results. message: Nu a fost găsit niciun script. @@ -405,12 +554,14 @@ labelNotifyUpdates: labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: ignoră notificarea per script (fila „setări” în editor) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Sortează scripturile din popup după $1 labelPrivacyPolicy: description: Label of link to privacy policy message: Politică de confidențialitate +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Reinstalarea scriptului labelRelated: description: Label of related links. message: 'Linkuri relevante: ' @@ -438,6 +589,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Setări +labelShowOrder: + description: Label for option in dashboard -> script list + message: Afișează pozițiile ordinii executării +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Sincronizare @@ -450,6 +607,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Se autorizează +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: Niciunul @@ -475,6 +635,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: 'Nume de utilizator: ' +labelTags: + description: Label for custom tags. + message: 'Taguri (separate prin spații):' +labelTheme: + description: Label for the visual theme option. + message: 'Temă:' labelTranslator: description: Label of translator. message: 'Traducător: ' @@ -486,10 +652,31 @@ labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: O singură coloană labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. - message: '' + message: Vizualizare tip listă +labelWidth: + description: Width. + message: 'Lățime:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Mod $1 sincron +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Activează doar dacă ai un script care trebuie să ruleze înainte ca pagina să + înceapă să se încarce și în prezent rulează prea târziu. La fel ca și modul + de injectare instantanee din Tampermonkey, această opțiune folosește XHR + sincron, care este perimat, astfel încât în Chrome/Chromium vei vedea + avertismente în consola devtools, deși le poți ignora în siguranță, deoarece + efectele adverse sunt neglijabile în acest caz. Poți ascunde definitiv + avertismentele dând clic dreapta pe una. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (cu excepția incognito și a site-urilor cu cookie-uri dezactivate) lastSync: description: Label for last sync timestamp. message: Ultima sincronizare în $1 @@ -521,6 +708,11 @@ menuExcludeHint: manual. Folosește fila „Setări” a editorului pentru mai multă flexibilitate. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Feedback menuFindScripts: description: Menu item to find scripts for a site. message: Găsește scripturi pentru acest site @@ -528,11 +720,16 @@ menuInjectionFailed: description: Injection error. message: Nu s-au putut injecta unele scripturi. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: Reîncearcă în modul „auto” +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Scripturi dezactivate coincidente menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. - message: Numai scripturi în sub-frame-uri + message: Scripturi numai pentru sub-frame-uri menuMatchedScripts: description: Label for menu listing matched scripts. message: Scripturi coincidente @@ -540,14 +737,19 @@ menuNewScript: description: Menu item to create a new script. message: Creează un script nou menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Scripturi dezactivate menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Scripturi activate msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Se caută actualizări... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Dă clic pentru a deschide documentația MomentJS. Indicative permise: $1. + Folosește [paranteze pătrate] pentru a proteja textul literal. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -601,13 +803,25 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Se încarcă dependințele... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Lipsesc resursele necesare. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. message: >- - Conflicte în spațiul de nume al scriptului! Te rugăm să modifici @name și - @namespace. + Un script ca acesta este deja instalat. + + Te rugăm fie să folosești un alt @name și @namespace aici, fie să editezi + scriptul respectiv. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Un script cu același @name și @namespace este deja instalat. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -616,21 +830,36 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Nu au fost găsite actualizări. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Eroare la actualizarea scripturilor. Dă clic pentru a le deschide. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Te rugăm să resalvezi sau să reinstalezi acest(e) script(uri):' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (codul este la fel) msgSavedBlacklist: description: Message shown when blacklist are saved. message: Lista neagră este actualizată. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Stilul personalizat este actualizat. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Opțiunile editorului sunt actualizate. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: Șablonul personalizat pentru scripturi este actualizat. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Scriptul [$1] este actualizat!' + message: Scriptul [$1] este actualizat! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: Afișează/ascunde @@ -643,12 +872,28 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Eroare la inițializare! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Neautorizat încă. msgSyncReady: description: Message shown when sync will start soon. message: Sincronizarea va începe în curând... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Sincronizare în desfășurare... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Eroare de sintaxă? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + Scriptul nu a existat sau nu a coincis cu URL-ul la încărcarea paginii, ceea + ce se întâmplă în cazul site-urilor cu aplicație într-o singură pagină, cum + ar fi fb sau instagram, care folosesc navigarea falsă. Poți reîncărca fila + pentru a rula scriptul. Pentru a repara scriptul, folosește @match pentru + întregul site și apoi detectează modificările folosind MutationObserver sau + API-ul window.navigation. msgUpdated: description: Message shown when a script is updated/reinstalled. message: Script actualizat. @@ -671,15 +916,86 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: Ascunde caseta polivalentă +optionPopup: + description: Label of the popup menu section in settings. + message: Meniul și pictograma pentru popup optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: Mai întâi cele activate +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Grupează scripturile dezactivate + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Grupează după stadiul @run-at optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: Ascunde-le pe cele dezactivate + message: Ascunde scripturile dezactivate + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Afișează scripturile dezactivate + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Afișează mai întâi scripturile activate +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Temă pentru UI:' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: automată +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: întunecată +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: deschisă +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Actualizare +popupSettings: + description: Item inside the popup's "⋮" menu + message: Setări pentru popup +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Clic dreapta: setări pentru popup' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Numai pentru citire +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Scriptul actualizat automat este numai pentru citire, cu excepția cazului în + care dezactivezi actualizările sau permiți editarea. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Permite editările +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (următoarea actualizare le va suprascrie) +reinstall: + description: Button to reinstall a script + message: Reinstalează +reloadTab: + description: Label of action to reload the tab + message: Reîncarcă fila +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Dacă opțiunea este activată, fila activă va fi reîncărcată atunci când sunt + detectate modificări și acest script coincide cu URL-ul filei. +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Sensibil la scrierea cu litere mari și mici @@ -695,14 +1011,80 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Setări -titleScriptUpdated: - description: Notification title for script updates. - message: Actualizare +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Reîncarcă pagina fără userscripturi +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Ai dezactivat userscripturile pentru această pagină. Pentru a le rula, + reîncarcă sau navighează în filă. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Ordine de sortare:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Oprește urmărirea +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Culoare normală a insignei +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Culoarea insignei atunci când site-ul este neinjectabil (în lista neagră sau + nesuportat) titleSearchHint: description: Hover title for search icon in dashboard. message: |- * Tasta adaugă textul în istoricul completării automate * Sintaxa RegExp este suportată: /re/ și /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Tasta Enter adaugă textul în istoricul de completare automată + + * Toate condițiile nu fac distincție între majuscule și minuscule + + * Condițiile separate prin spațiu pot fi combinate + + * Caută după metadate: "Awesome Script" "Description" + + * Caută după taguri: #tag1 #tag2 + + * Caută după numele unui script: name:"awesome name" + + * Caută după codul unui script: code: "awesome code" + + * Caută excluzând anumite rezultate: !#tag2 !name:"unwanted" + + * Expresii regulate: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? with space" +toggleInjection: + description: Label for a keyboard shortcut. + message: Comută injectarea de userscripturi +trackEdits: + description: Button in a script installation dialog. + message: Urmărește editările externe +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: Ține această pagină deschisă pentru a-ți urmări editările în fișierul local +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Actualizează scripturile ($1) +updateScript: + description: Button to update one script. + message: Actualizează +updateScriptsAll: + description: Command/button to update all scripts. + message: Actualizează toate scripturile +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Actualizează scripturile filei actuale valueLabelKey: description: Label for key of a script value. message: Cheie (șir de caractere) @@ -712,6 +1094,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: Toate valorile (serializate ca JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: Vizitează site-ul web + touched: false diff --git a/src/_locales/ru/messages.yml b/src/_locales/ru/messages.yml index e1d0595c72..5f51e87243 100644 --- a/src/_locales/ru/messages.yml +++ b/src/_locales/ru/messages.yml @@ -1,21 +1,37 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Применить buttonCancel: description: Cancel button on dialog. message: Отмена +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Проверить обновления + touched: false buttonClose: description: Button to close window. message: Закрыть buttonConfirmInstallation: description: Button to confirm installation of a script. message: Установить + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Установить заново + touched: false buttonDisable: description: Button to disable a script. - message: Выключить + message: Отключить +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: Скачать темы + touched: false buttonEdit: description: Button to edit a script. - message: Редактировать + message: Править buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: 'Вы также можете щелкнуть ПКМ, Ctrl+ЛКМ или СКМ на имени скрипта.' + message: Вы также можете щёлкнуть ПКМ, Ctrl+ЛКМ или СКМ по имени скрипта. buttonEmptyRecycleBin: description: Button to empty the recycle bin. message: Очистить корзину! @@ -47,7 +63,7 @@ buttonNew: message: Создать buttonOK: description: OK button on dialog. - message: OK + message: ОК buttonRecycleBin: description: Button to list scripts in recycle bin. message: Корзина @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Сброс +buttonResetSettings: + description: Button in settings page to reset all settings + message: Сбросить настройки buttonRestore: description: Button to restore a removed script. message: Восстановить @@ -72,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Сохранить и закрыть +buttonSaved: + description: Button text after saving. + message: Сохранено buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Показать настройки редактора buttonSupport: description: Button to open support page. message: Страница поддержки +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: Забрать к локальному единожды +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: Отправить на внешнее единожды buttonUndo: description: Button to undo removement of a script. - message: Отменить - touched: false + message: Откатить buttonUpdate: - description: Check a script for updates. - message: Проверить обновления + description: Button to update a script. + message: Обновить buttonUpdateAll: description: Check all scripts for updates. message: Проверить обновления скриптов + touched: false buttonVacuum: description: Button to vacuum extension data. message: Сбросить кэш @@ -97,32 +125,77 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Очистка и сжатие кэша... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Автообновление отключено для этого скрипта! + Щёлкните ОК, чтобы обновить его в любом случае. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Изменения не сохранены! - Нажмите OK, чтобы выйти или Отмена, чтобы вернуться. + Нажмите ОК, чтобы выйти, или Отмена, чтобы вернуться. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Отменить все изменения, внесённые в базу данных (импорт, обновление, + редактирование, настройка) descBlacklist: - description: HTML Description for the global blacklist. - message: URL из этого списка не будут обрабатываться скриптами. + description: Description for the global injection blacklist. + message: Чёрный список инъекций (скрипты не будут запускаться на указанных сайтах) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Чёрный список сети (скрипты не будут подключаться к указанным сайтам и их + куки) descCustomCSS: description: Description of custom CSS section. message: >- - Пользовательский CSS для страницы настроек и страницы установки скриптов. - Если вы не знаете для чего это необходимо, не редактируйте это. + Пользовательский стиль CSS для страниц настроек и установки скриптов. Если + вы не знаете, зачем это нужно, пожалуйста, не редактируйте его. descEditorOptions: description: Description of editor options JSON section. message: >- - Ваши параметры для CodeMirror и аддонов в объекте JSON, например - {"indentUnit":2, "smartIndent":true}.Однако заметьте, некоторые - из них могут не работать в Violentmonkey. Смотрите {"indentUnit":2, "smartIndent":true}. Имейте в виду, что + некоторые опции могут не работать в Violentmonkey. См. полный список. Чтобы использовать - настраиваемую тему CodeMirror, укажите имя файла, например "theme": - "3024-day" и вставьте CSS актуальной темы темы в - поле "Пользовательский стиль" ниже. + rel="noopener noreferrer">полный список. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Логические параметры можно переключать двойным щелчком на значении: + true = включено, false = отключено. Числовые + параметры, заканчивающиеся на Delay, Interval, + Rate, Time, указываются в миллисекундах. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Нестандартные параметры: autocompleteOnTyping — задержка в + миллисекундах для отображения подсказок автозаполнения после ввода текста + (0 = отключить), killTrailingSpaceOnSave + автоматически удаляет пробелы в каждой строке при сохранении, + showTrailingSpace показывает пробелы в виде точек. +descScriptTemplate: + description: Description of script template section. + message: >- + Поддерживаемые переменные: <{{name}}>, <{{url}}>, <{{date}}>, + <{{date:format}}> с совместимым с MomentJS форматом, например, + <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: группировать +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: скрыть +disabledScriptsSelector: + description: Label of the option. + message: 'Отключённые скрипты:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: показать editHelpDocumention: description: Label in the editor help tab for the documentation link. message: 'Документация по блоку метаданных скриптов и GM API:' @@ -131,7 +204,7 @@ editHelpKeyboard: message: 'Сочетания клавиш:' editHowToHint: description: The text of the how-to link in the editor header. - message: Использовать другой редактор? + message: Как сменить редактор? editLabelMeta: description: Metadata section in settings tab of script editor. message: Пользовательские метаданные @@ -143,11 +216,13 @@ editLongLine: message: Слишком длинная строка editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long - message: |- - Эта строка слишком длинная и ее текст свернут, - чтобы избежать задержек при редактировании. - Вы можете настроить лимит в доп. настройках, - например:\"maxDisplayLength\": 20000 + message: >- + Слишком длинная строка, её текст свёрнут, чтобы избежать задержек при + правке. + + Вы можете настроить ограничение в доп. настройках, например: + + "maxDisplayLength": 20000 editNavCode: description: Label of code tab in script editor. message: Код @@ -160,33 +235,45 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: Все + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: Показать/отредактировать хранилище значений скрипта -editValueCancel: - description: Button to cancel modification of a script value. - message: Отмена -editValueSave: - description: Button to save modification of a script value. - message: Сохранить + message: Показать/править всё хранилище значений скрипта extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: >- - Violentmonkey - менеджер пользовательских скриптов для множества браузеров, - с открытым исходным кодом. + Менеджер пользовательских скриптов с открытым исходным кодом для различных + браузеров extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' - message: Черный список Violentmonkey + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: Чёрный список Violentmonkey failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) - message: |- + message: >- Violentmonkey не может запускать скрипты на этой странице - (например: UI браузера или расширения) + + (частые случаи: интерфейс браузера, расширение, заблокировано политикой, + сайт поисковой системы в Opera) +failureReasonRestarted: + description: Shown in the popup. + message: >- + Violentmonkey был перезапущен. Пожалуйста, перезагрузите вкладку, чтобы + запустить пользовательские скрипты. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Существует несколько способов установки или отслеживания правок в локальном + файле: <1>Перетащите файл на открытую страницу Violentmonkey или во + всплывающее окно, включая это. <2>Установите локальный HTTP-сервер для этого + файла и откройте его через http://localhost. <3>Включите «Разрешить + открывать локальные файлы по ссылкам» в сведениях Violentmonkey на странице + chrome://extensions. Это опасно, так как любой пользовательский скрипт может + прочитать любой локальный файл. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. message: алфавитному порядку @@ -196,51 +283,85 @@ filterExecutionOrder: filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: времени обновления +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: времени последнего посещения +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: Последний раз, когда вы посещали любой сайт, на который нацелен этот скрипт. filterScopeAll: description: Option in dashboard's search scope filter. message: Все + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Код + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: Имя + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: размеру genericError: description: Label for generic error. message: Ошибка genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' - message: выключено + description: To indicate something is turned off or disabled, similar to "no". + message: откл genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' - message: включено + description: To indicate something is turned on or enabled, similar to "yes". + message: вкл genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. - message: использовать глобальные настройки + message: использовать общую настройку +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + Шаблоны «#» работают только при первоначальном открытии сайта или при + перезагрузке вкладки: $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Корзина +helpForLocalFile: + description: Label of the checkbox. + message: Показать инструкцию по перетаскиванию локальных скриптов +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: применить к этим ($1) скриптам +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: |- + Скрипт в песочнице не может менять следующие глобальные свойства: $1. + Чтобы исправить скрипт, добавьте `@grant none` для отключения песочницы, + или используйте `unsafeWindow` вместо `window`. hintInputURL: description: Hint for a prompt box to input URL of a user script. - message: 'Введите URL:' + message: 'Введите URL-адрес:' hintRecycleBin: description: Hint for recycle bin. - message: Удаленные скрипты просуществуют еще 7 дней в этом списке. + message: Удалённые скрипты отображаются здесь и хранятся в течение 7 дней. hintUseDownloadURL: description: Shown as a place holder for @updateURL when it is not assigned message: Использовать @downloadURL hintVacuum: description: Hint for vacuuming data. message: Сбросить избыточный кэш и попробовать подгрузить недостающие ресурсы. +install: + description: Label for button to install a script. + message: Установить installFrom: description: Label for button to install script from a userscript site. message: Установить с $1 installOptionClose: description: Option to close confirm window after installation. message: Закрыть после установки. -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Отслеживать локальный файл до закрытия этого окна. + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -252,7 +373,7 @@ labelAbout: touched: false labelAdvanced: description: Label for button to show advanced settings. - message: Дополнительные настройки + message: Расширенные labelAllowUpdate: description: Option to allow checking updates for a script. message: Разрешить обновление @@ -261,30 +382,41 @@ labelAuthor: message: 'Автор: ' labelAutoReloadCurrentTab: description: Option to reload current tab after a script is switched on or off from menu. - message: Обновить текущую вкладку после включения или отключения скрипта из меню. + message: Обновить текущую вкладку при включении или отключении скрипта из меню labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: Текущие скрипты будут работать до перезагрузки вкладки. + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: 'Проверять наличие обновлений скриптов каждые $1 дней, 0 для отключения' + message: Проверять обновления скриптов каждые $1 дн., 0 для отключения +labelBackup: + description: Label of the import/export section in settings. + message: Резервная копия + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Резервное копирование и обслуживание labelBadge: description: Label for option to show number on badge. - message: 'Показывать на значке: ' + message: 'Счётчик на значке: ' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Цвета значка: ' labelBadgeNone: description: Option to display nothing on badge. - message: ничего + message: нет labelBadgeTotal: description: Option to display total number of running scripts on badge. - message: кол-во запущенных скриптов + message: запущенные скрипты labelBadgeUnique: description: Option to display number of unique running scripts on badge. - message: кол-во уникальных запущенных скриптов + message: уникальные запущенные скрипты labelBlacklist: description: Label for global blacklist settings in security section. - message: Черный список + message: Чёрный список labelContributors: description: Label for link to contributors. message: Участники @@ -297,24 +429,30 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Экспорт данных + touched: false labelDataImport: description: Section title of data import. message: Импорт данных + touched: false labelDonate: description: Label of link to donate page. message: Поддержать разработку + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. - message: 'URL загрузки:' + message: 'URL-адрес загрузки:' labelEditValue: description: Label shown in the panel to edit a script value. - message: Редактировать скрипт + message: Правка значения скрипта labelEditValueAll: description: Label shown in the panel to edit the entire script value storage. - message: Редактирование хранилища скриптов + message: Правка хранилища скрипта labelEditor: description: Label for Editor settings message: Редактор +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: только включённые скрипты labelExclude: description: Label of @exclude rules. message: '@exclude правила исключения' @@ -323,62 +461,76 @@ labelExcludeMatch: message: '@exclude-match правила исключения соответствий' labelExportScriptData: description: Option to export script data along with scripts. - message: Экспорт данных скриптов + message: Экспортировать данные скриптов labelExposeStatus: description: Option in advanced settings. - message: 'Публиковать установленную версию в каталоге userscripts: $1' + message: 'Показывать версию установленного скрипта на сайтах каталогов скриптов: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Альтернативный режим $1 в Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Альтернативный способ внедрения для скриптов с Firefox 59, + более быстрый, чем режим по умолчанию. Как и «Режим синхронизации page», он + также увеличивает потребление памяти, поэтому вы можете отключить его, если + ваши скрипты работают верно без этого параметра. labelFeedback: description: Label of link to feedback page. message: Обратная связь -labelFilterSort: - description: Label for sort filter. - message: Сортировать по $1 labelGeneral: description: Label for general settings. message: Общие настройки labelHelpTranslate: description: Label for link to localization guide in about tab - message: Помощь с переводом + message: Улучшить перевод labelHomepage: description: Label for home page in about tab. message: Домашняя страница labelHomepageURL: description: Label of script @homepageURL in custom meta data. - message: 'URL страницы скрипта:' + message: 'Адрес домашней страницы:' +labelIconURL: + description: Label for the input. + message: 'Адрес значка:' labelImportScriptData: description: Option to import script data along with scripts. - message: Импорт данных скрипта + message: Импортировать данные скриптов labelImportSettings: description: Label for option to import settings from zip file. - message: Импорт настроек + message: Импортировать настройки labelInclude: description: Label of @include rules. message: '@include правила включения' labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Режим инъекции по умолчанию: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Режим внедрения: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. - message: Установка скрипта... + message: Установка скрипта labelKeepOriginal: description: Option to keep the original match or ignore rules. message: Сохранить исходное соответствие labelLastUpdatedAt: description: Label shown on last updated time. - message: Дата последнего обновления $1 + message: 'Дата последнего обновления: $1' labelLineNumber: description: Label for line number jumper. - message: 'N строки:' + message: '№ строки:' labelMatch: description: Label of @match rules. message: '@match правила соответствия' labelName: description: Label of script name. message: 'Имя:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Запускать во фреймах:' labelNoName: description: Text as the name of a script when no @name is assigned. - message: Без имени - touched: false + message: Безымянный labelNoSearchScripts: description: Message shown when no script is found in search results. message: Скрипты не найдены. @@ -387,25 +539,27 @@ labelNotifyThisUpdated: A per-script option in editor to enable notification when this script is updated. The text follows "Allow update" checkbox option so it's like a continuation of the phrase. - message: '. Уведомления об обновлениях:' + message: '. Уведомление:' labelNotifyUpdates: description: Option to show notification when script is updated. message: Уведомлять об обновлениях скриптов labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: Игнорировать уведомления скриптов (вкладка настроек в редакторе) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Сортировка во всплывающем окне по $1 labelPrivacyPolicy: description: Label of link to privacy policy message: Политика конфиденциальности +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Повторная установка скрипта labelRelated: description: Label of related links. - message: 'Ссылки по теме: ' + message: 'Справочные ссылки: ' labelRemovedAt: description: Label for the time when the script is removed. - message: Удален в $1 + message: 'Дата удаления: $1' labelReplace: description: Label for replace input in search box. message: 'Заменить на: ' @@ -427,6 +581,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Настройки +labelShowOrder: + description: Label for option in dashboard -> script list + message: Показать номера порядка выполнения +labelShowVisited: + description: Label for option in dashboard -> script list + message: Показать время последнего посещения labelSync: description: Label for sync options. message: Синхронизация @@ -439,9 +599,12 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Авторизация... +labelSyncAutomatically: + description: Label for option to sync automatically. + message: Синхронизировать автоматически labelSyncDisabled: description: Label for option to disable sync service. - message: ничем + message: Нет labelSyncPassword: description: Label for input to hold password. message: 'Пароль: ' @@ -451,43 +614,70 @@ labelSyncReauthorize: touched: false labelSyncRevoke: description: Label for button to revoke authorization for a service. - message: Выйти + message: Отозвать labelSyncScriptStatus: description: Label for option to sync script status. - message: Синхронизировать статус скриптов. + message: Синхронизировать состояние скриптов labelSyncServerUrl: description: Label for input to hold server URL. - message: 'URL сервера:' + message: 'URL-адрес сервера:' labelSyncService: description: Label for sync service select. - message: Синхронизировать с + message: Синхронизация через labelSyncUsername: description: Label for input to hold username. message: 'Логин:' +labelTags: + description: Label for custom tags. + message: 'Метки (через пробел):' +labelTheme: + description: Label for the visual theme option. + message: 'Тема:' labelTranslator: description: Label of translator. message: 'Переводчики: ' touched: false labelUpdateURL: description: Label of script @updateURL in custom meta data. - message: 'URL обновления:' + message: 'URL-адрес обновления:' labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: Один столбец labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. - message: '' + message: Таблица +labelWidth: + description: Width. + message: 'Ширина:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Режим синхронизации $1 +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Включите только в случае, если у вас есть скрипт, который должен быть + запущен до начала загрузки страницы, и в настоящее время он выполняется + слишком поздно. Как и режим мгновенного внедрения в Tampermonkey, этот + вариант использует устаревший синхронный XHR, поэтому в Chrome/Chromium + будут предупреждения в консоли devtools, но вы можете игнорировать их, + поскольку негативные последствия незначительны. Скрыть предупреждения можно + щелчком ПКМ на одном из них. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (кроме инкогнито и сайтов с отключёнными куки) lastSync: description: Label for last sync timestamp. message: Последняя синхронизация в $1 learnBlacklist: description: Refers to a link to introduce blacklist patterns. - message: Подробнее о шаблонах черного списка + message: Подробнее о шаблонах чёрного списка. learnInjectionMode: description: Refers to a link to introduce injection modes. - message: Подробнее о режимах инъекций + message: Подробнее о режимах внедрения. menuCommands: description: Menu item to list script commands. message: Команды скриптов @@ -499,7 +689,7 @@ menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: Исключать... + message: Исключить... menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. message: |- @@ -507,7 +697,12 @@ menuExcludeHint: если вы включили эту опцию в общих настройках. Чтобы применить изменения к остальным вкладкам, перезагрузите их вручную. - Настраивается в \"Настройки\" редактора. + Регулируется в «Настройках» редактора. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Обратная связь menuFindScripts: description: Menu item to find scripts for a site. message: Найти скрипты для этого сайта @@ -515,37 +710,47 @@ menuInjectionFailed: description: Injection error. message: Не удалось внедрить некоторые скрипты. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' - message: Повторите попытку в "авто" режиме + description: Injection error fix, shown in case the default mode is "page". + message: Повторить попытку в режиме «auto» +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Подходящие отключённые скрипты menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. - message: Суб-фреймы скриптов + message: Скрипты только субфреймов menuMatchedScripts: description: Label for menu listing matched scripts. - message: Совпадающие скрипты + message: Подходящие скрипты menuNewScript: description: Menu item to create a new script. message: Создать новый скрипт menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Скрипты отключены menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Скрипты включены msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. - message: Проверка наличия обновлений... + message: Проверка обновлений… +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Щёлкните, чтобы открыть документацию MomentJS. Разрешённые маркеры: $1. + Используйте [квадратные скобки] для защиты буквенного текста. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of the script. - message: Ошибка извлечения ресурса! + message: Ошибка при получении ресурса! msgErrorFetchingScript: description: Message shown when Violentmonkey fails fetching a new version of the script. - message: Ошибка при загрузке скрипта! + message: Ошибка при получении скрипта! msgErrorFetchingUpdateInfo: description: Message shown when Violentmonkey fails fetching version data of the script. - message: Не удается проверить наличие обновлений. + message: Не удалось получить сведения об обновлениях. msgErrorLoadingData: description: >- Message shown on confirm page when the script to be installed cannot be @@ -563,7 +768,7 @@ msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito window. - message: 'Изменения, в режиме инкогнито, применяются и к основному профилю.' + message: Изменения, в режиме инкогнито, применяются и к основному профилю. msgInstalled: description: Message shown when a script is installed. message: Скрипт установлен. @@ -582,15 +787,27 @@ msgLoading: touched: false msgLoadingData: description: Message shown on confirm page when the script to be installed is loading. - message: Загрузка кода скрипта... + message: Загружаются данные скрипта… msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Загрузка зависимостей... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Не хватает необходимых ресурсов. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. - message: Конфликт пространства имен скрипта. Измените @name и @namespace. + message: |- + Конфликт пространства имён скрипта. + Пожалуйста, смените @name и @namespace здесь или исправьте этот скрипт. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Скрипт с такими же @name и @namespace уже установлен. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -599,42 +816,73 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Обновления не найдены. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Ошибка при обновлении скриптов. Щёлкните, чтобы открыть их. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Пожалуйста, пере-сохраните или переустановите эти скрипты:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: ' (код такой же)' msgSavedBlacklist: description: Message shown when blacklist are saved. message: Чёрный список сохранен. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Пользовательский стиль сохранен. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Параметры редактора обновлены. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: Шаблон нового скрипта обновлен. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Скрипт [$1] обновлен!' + message: Скрипт [$1] обновлён! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: Показать/скрыть msgSyncError: description: Message shown when sync failed. - message: Ошибка синхронизации. + message: Ошибка синхронизации! msgSyncInit: description: Message shown when sync service is initializing. - message: Инициализация... + message: Инициализация… msgSyncInitError: description: Message shown when sync fails in initialization. - message: Ошибка инициализации синхронизации! + message: Ошибка запуска синхронизации! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Ещё не авторизован. msgSyncReady: description: Message shown when sync will start soon. message: Ожидание синхронизации... + touched: false msgSyncing: description: Message shown when sync is in progress. - message: Синхронизация... + message: Выполняется синхронизация... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Ошибка синтаксиса? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + Скрипт не существовал или не совпал по URL-адресу при загрузке страницы, что + случается на сайтах с одностраничным приложением, таких как Фейсбук или + IИнстаграм, которые используют фальшивую навигацию. Вы можете перезагрузить + вкладку, чтобы запустить скрипт. Чтобы исправить скрипт, используйте @match + для всего сайта, а затем находите изменения с помощью MutationObserver или + API window.navigation. msgUpdated: description: Message shown when a script is updated/reinstalled. - message: Скрипт обновлен. + message: Скрипт обновлён. msgUpdating: description: Message shown when a new version of script is being fetched. message: Обновление... @@ -643,7 +891,7 @@ noValues: message: Значения не сохраняются optionEditorWindow: description: Label for the option in settings - message: Из всплывающего окна открывать редактор в новом окне + message: Открывать редактор из всплывающего меню в новом окне optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support @@ -653,19 +901,94 @@ optionEditorWindowHint: сохранении optionEditorWindowSimple: description: Label for the editor window type - message: Скрыть омнибокс + message: Скрывать адресную строку +optionPopup: + description: Label of the popup menu section in settings. + message: Всплывающее меню и значок optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. - message: Сначала включенные + message: Сначала включённые +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Группировать отключённые скрипты + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Группировать по стадии @run-at optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: Скрыть отключенные + message: Скрыть отключённые скрипты + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Показать отключённые скрипты + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. - message: Сначала показать включенные скрипты + message: Сначала показать включённые скрипты +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Тема интерфейса:' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: автоматически +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: тёмная +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: светлая +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Обновление +popupSettings: + description: Item inside the popup's "⋮" menu + message: Настройки всплывающего окна +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Правый щелчок: настройки всплывающего окна' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Только для чтения +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Автообновляемый скрипт доступен только для чтения, пока вы не отключите + обновления или не разрешите правку. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Разрешить правки +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (следующее обновление перезапишет их) +reinstall: + description: Button to reinstall a script + message: Переустановить +reloadTab: + description: Label of action to reload the tab + message: Обновить вкладку +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Если включено, активная вкладка будет перезагружаться при обнаружении + изменений и совпадении URL-адреса вкладки с этим скриптом. +removeAllScripts: + description: Button to remove all scripts + message: Удалить все скрипты + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: >- + Убрать все скрипты в «Корзину»? + + Она будет автоматически очищена через некоторое время. Вы также можете + открыть её и очистить самостоятельно. + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search - message: С учетом регистра + message: С учётом регистра searchUseRegex: description: Option to perform a regular expression search message: Регулярное выражение @@ -674,18 +997,86 @@ sideMenuAbout: message: О расширении sideMenuInstalled: description: 'Side menu: Installed scripts' - message: Установленные скрипты + message: Установлено sideMenuSettings: description: 'Side menu: Settings' message: Настройки -titleScriptUpdated: - description: Notification title for script updates. - message: Обновление +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Обновить страницу без скриптов +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Вы отключили пользовательские скрипты для этой страницы. Чтобы запустить их, + перезагрузите или перейдите на вкладку. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Сортировка по:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Прекратить отслеживание +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Цвет обычного значка +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: |- + Цвет значка, если скрипты не внедрились + (страница в чёрном списке или не поддерживается) titleSearchHint: description: Hover title for search icon in dashboard. message: |- * Клавиша добавляет текст в историю автозаполнения. * Поддерживается RegExp: /re/ и /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Клавиша Enter добавляет текст в историю автозаполнения + + * Все условия не зависят от регистра + + * Условия, разделённые пробелами, можно комбинировать + + * Поиск по метаданным: "Отличный скрипт" "Описание" + + * Поиск по тегам: #tag1 #tag2 + + * Поиск по имени скрипта: name: "прекрасное имя" + + * Поиск по коду скрипта: code: "замечательный код" + + * Отрицательный поиск: !#tag2 !name: "нежелательное" + + * Регулярные выражения: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? с пробелом" +toggleInjection: + description: Label for a keyboard shortcut. + message: Переключить инъекцию пользовательского скрипта +trackEdits: + description: Button in a script installation dialog. + message: Отслеживать внешние правки +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: >- + Держите эту страницу открытой, чтобы отслеживать свои правки в локальном + файле +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Обновить скрипты ($1) +updateScript: + description: Button to update one script. + message: Обновить +updateScriptsAll: + description: Command/button to update all scripts. + message: Обновить все скрипты +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Обновить скрипты текущей вкладки valueLabelKey: description: Label for key of a script value. message: Ключ (строка) @@ -695,6 +1086,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: Все значения (в формате JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: Значение (сериализуется как JSON или $1) +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: $1 строка visitWebsite: description: Label for link to open Violentmonkey website. message: Сайт расширения + touched: false diff --git a/src/_locales/sk/messages.yml b/src/_locales/sk/messages.yml index 4f0d351191..7ee737c45f 100644 --- a/src/_locales/sk/messages.yml +++ b/src/_locales/sk/messages.yml @@ -1,15 +1,31 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Použiť buttonCancel: description: Cancel button on dialog. message: Zrušiť +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Skontrolovať aktualizácie + touched: false buttonClose: description: Button to close window. message: Zavrieť buttonConfirmInstallation: description: Button to confirm installation of a script. message: Potvrdiť inštaláciu + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Potvrdiť preinštalovanie + touched: false buttonDisable: description: Button to disable a script. message: Zakázať +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: Stiahnuť motívy + touched: false buttonEdit: description: Button to edit a script. message: Upraviť @@ -65,6 +81,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Obnoviť +buttonResetSettings: + description: Button in settings page to reset all settings + message: Resetovať nastavenia buttonRestore: description: Button to restore a removed script. message: Obnoviť @@ -74,22 +93,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Uložiť & Zavrieť +buttonSaved: + description: Button text after saving. + message: Uložené buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Zobraziť stav editora buttonSupport: description: Button to open support page. message: Stránka podpory +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: Stiahnuť zmeny z repozitára +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: Odoslať zmeny do repozitára buttonUndo: description: Button to undo removement of a script. message: Späť - touched: false buttonUpdate: - description: Check a script for updates. - message: Skontrolovať aktualizácie + description: Button to update a script. + message: Aktualizovať buttonUpdateAll: description: Check all scripts for updates. message: Skontrolovať aktualizácie + touched: false buttonVacuum: description: Button to vacuum extension data. message: Prečistiť databázu @@ -99,14 +127,31 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Čistím databázu... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Automatická aktualizácia je pre tento skript zakázaná! + Kliknutím na tlačidlo OK ho napriek tomu aktualizujte. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Úpravy nie sú uložené! Kliknite na OK pre ich zrušenie alebo zrušiť pre ich ponechanie. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Vrátiť všetky zmeny vykonané v databáze (import, aktualizácia, úprava, + prispôsobenie) descBlacklist: - description: HTML Description for the global blacklist. - message: Webové adresy v tomto zozname nebudú injektované skriptami. + description: Description for the global injection blacklist. + message: Čierna listina spúšťania (skripty sa nespustia na zodpovedajúcich stránkach) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Sieťová čierna listina (skripty sa nepripoja na zodpovedajúce stránky ani + cookies) descCustomCSS: description: Description of custom CSS section. message: >- @@ -116,18 +161,45 @@ descEditorOptions: description: Description of editor options JSON section. message: >- Vlastné možnosti pre CodeMirror a doplnky v JSON objekte ako - {"indentUnit":2, "smartIndent":true} avšak niektoré nemusia byť - funkčné vo Violentmonkey. Pozri {"indentUnit":2, "smartIndent":true} avšak niektoré z nich + nemusia byť funkčné vo Violentmonkey. Pozri celý zoznam. Pre použitie vlastného motívu pre - CodeMirror zadajte sem názov jeho súboru ako "theme":3024-day" - a vložte aktuálny motív CSS do poľa - "Vlastný štýl" nižšie. + rel="noopener noreferrer">celý zoznam. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Boolovské možnosti môžu byť prepínané dvojitým kliknutím na hodnotu: + true = povolené, false = zakázané. Číselné + možnosti končiace na Delay, Interval, + Rate, Time sú v milisekundách. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Neštandardné možnosti: autocompleteOnTyping je oneskorenie v + milisekundách na zobrazenie návrhov automatického dopĺňania po písaní + (0 = zakázať), killTrailingSpaceOnSave automaticky + odstraňuje medzery na konci každého riadka pri ukladaní, + showTrailingSpace zobrazuje medzery na konci riadkov ako bodky. +descScriptTemplate: + description: Description of script template section. + message: >- + Podporované premenné: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + formát kompatibilný s MomentJS, napríklad <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: skupina +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: skryť +disabledScriptsSelector: + description: Label of the option. + message: 'Vypnuté skripty:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: zobraziť editHelpDocumention: description: Label in the editor help tab for the documentation link. - message: 'Dokumentácia k bloku metadát užívateľského skriptu a GM API:' + message: 'Dokumentácia k bloku metadát požívateľského skriptu a GM API:' editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. message: 'Klávesové skratky:' @@ -164,66 +236,113 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: Všetko + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: Zobraziť/upraviť celé úložisko hodnoty skriptu -editValueCancel: - description: Button to cancel modification of a script value. - message: Zrušiť -editValueSave: - description: Button to save modification of a script value. - message: Uložiť extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: >- - Správca užívateľských skriptov s otvoreným kódom a podporou viacerých + Správca používateľských skriptov s otvoreným kódom a podporou viacerých prehliadačov extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: Na čiernej listine v nastaveniach Violentmonkey failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) - message: |- + message: >- Violentmonkey nemôže spustiť skripty na tejto stránke - (najčastejší prípad: rozhranie prehliadačia alebo rozšírenia) + + (najčastejší prípad: rozhranie prehliadača alebo rozšírenia, blokované cez + politiku, web vyhľadávača v Opere) +failureReasonRestarted: + description: Shown in the popup. + message: >- + Violentmonkey sa reštartoval. Ak chcete spustiť používateľské skripty, znova + načítajte kartu. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Existuje niekoľko metód na inštaláciu alebo sledovanie úprav v lokálnom + súbore: <1> Pretiahnite súbor do otvorenej stránky alebo vyskakovacieho okna + Violentmonkey, vrátane tejto. <2> Nainštalujte lokálny HTTP server pre tento + súbor a otvorte ho cez stránku http://localhost. <3> V podrobnostiach pre + Violentmonkey na stránke chrome://extensions povoľte možnosť "Povoliť + prístup k webovým adresám súboru". Je to nebezpečné, pretože akýkoľvek + používateľský skript môže čítať akýkoľvek lokálny súbor. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: abecedy + message: abecedne filterExecutionOrder: description: Label for option to sort scripts in execution order. - message: spustenia + message: vykonanie filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: poslednej aktualizácie +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: čas poslednej návštevy +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: >- + Čas, kedy ste naposledy navštívili akúkoľvek stránku, ktorú ovplyvňuje tento + skript. filterScopeAll: description: Option in dashboard's search scope filter. message: Všetko + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Kód + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: Názov + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: veľkosť genericError: description: Label for generic error. message: Chyba genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: vypnuté genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: zapnuté genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: použiť globálne nastavenie +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + Vzory „#“ fungujú iba pri prvom otvorení stránky alebo opätovnom načítaní + karty: $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Kôš +helpForLocalFile: + description: Label of the checkbox. + message: Zobraziť pokyny pre pretiahnuté lokálne skripty +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: pre zodpovedajúce skripty ($1) +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: |- + Skript v sandboxe nemôže meniť tieto globálne vlastnosti: $1. + Ak chcete opraviť skript, buď pridajte `@grant none` na vypnutie + sadboxu, alebo použite `unsafeWindow` namiesto `window`. hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Vložte adresu:' @@ -238,15 +357,16 @@ hintVacuum: message: >- Zruší redundanciu a skúsi znova načítať chýbajúce zdroje do vyrovnávacej pamäte. +install: + description: Label for button to install a script. + message: Inštalovať installFrom: description: Label for button to install script from a userscript site. message: Inštalovať z $1 installOptionClose: description: Option to close confirm window after installation. message: Zavrieť po nainštalovaní -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Sledovať lokálne súbory predtým ako sa toto okno zavrie + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -267,18 +387,29 @@ labelAuthor: message: 'Autor: ' labelAutoReloadCurrentTab: description: Option to reload current tab after a script is switched on or off from menu. - message: Obnoviť aktuálny list po vypnutí alebo zapnutí skriptu z menu + message: Obnoviť aktuálny list po vypnutí alebo zapnutí skriptu z ponuky labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. - message: 'Aktuálne skripty budú spustené, kým kartu znovu nenačítate' + message: Aktuálne skripty budú spustené, kým kartu znovu nenačítate + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: 'Kontrolovať aktualizácie skriptu každý $1 deň, pre zakázanie použite 0' + message: Kontrolovať aktualizácie skriptu každý $1 deň, pre zakázanie použite 0 +labelBackup: + description: Label of the import/export section in settings. + message: Záloha + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Zálohovanie a údržba labelBadge: description: Label for option to show number on badge. - message: 'Zobraziť počítadlo: ' + message: 'Odznak:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Farby odznaku:' labelBadgeNone: description: Option to display nothing on badge. message: žiadne @@ -303,12 +434,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Export dát + touched: false labelDataImport: description: Section title of data import. message: Import dát + touched: false labelDonate: description: Label of link to donate page. message: Podporiť + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'Adresa pre sťahovanie:' @@ -321,6 +455,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: Editor +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: povoliť iba skripty labelExclude: description: Label of @exclude rules. message: pravidlá @exclude @@ -332,13 +469,20 @@ labelExportScriptData: message: Exportovať dáta skriptu labelExposeStatus: description: Option in advanced settings. - message: 'Zobraziť nainštalovanú verziu v katalógoch užívateľských skriptov: $1' + message: 'Zobraziť nainštalovanú verziu v katalógoch používateľských skriptov: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Alternatívny režim $1 vo Firefoxe +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Alternatívna metóda injektovania pre skripty od Firefoxu + 59, rýchlejšia ako predvolený režim. Podobne ako "Synchrónny režim stránky" + tiež zvyšuje spotrebu pamäte, takže ho môžete vypnúť, ak vaše skripty + fungujú správne bez tejto možnosti. labelFeedback: description: Label of link to feedback page. message: Spätná väzba -labelFilterSort: - description: Label for sort filter. - message: Zoradiť podľa $1 labelGeneral: description: Label for general settings. message: Všeobecné @@ -351,6 +495,9 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'Adresa domovskej stránky:' +labelIconURL: + description: Label for the input. + message: 'Adresa URL ikony:' labelImportScriptData: description: Option to import script data along with scripts. message: Importovať dáta skriptu @@ -361,8 +508,10 @@ labelInclude: description: Label of @include rules. message: pravidlá @include labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Predvolený režim vloženia: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Režim injektovania:' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: Inštalovanie skriptu @@ -381,10 +530,12 @@ labelMatch: labelName: description: Label of script name. message: 'Názov:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Spustiť v rámoch:' labelNoName: description: Text as the name of a script when no @name is assigned. message: Bez názvu - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: Nenašiel sa žiadny skript. @@ -400,12 +551,14 @@ labelNotifyUpdates: labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: ignorovať nastavenie upozornenia (záložka "Nastavenia" v editore) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Zoradiť skripty vo vyskakovacom okne podľa $1 labelPrivacyPolicy: description: Label of link to privacy policy message: Zásady ochrany osobných údajov +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Preinštalovanie skriptu labelRelated: description: Label of related links. message: 'Súvisiace odkazy: ' @@ -433,6 +586,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Nastavenia +labelShowOrder: + description: Label for option in dashboard -> script list + message: Zobraziť poradie vykonania príkazu +labelShowVisited: + description: Label for option in dashboard -> script list + message: Zobraziť čas poslednej návštevy labelSync: description: Label for sync options. message: Synchronizácia @@ -445,6 +604,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Autorizácia +labelSyncAutomatically: + description: Label for option to sync automatically. + message: Automaticky synchronizovať labelSyncDisabled: description: Label for option to disable sync service. message: Zakázané @@ -466,10 +628,16 @@ labelSyncServerUrl: message: 'Server URL: ' labelSyncService: description: Label for sync service select. - message: Synchronizovať na + message: Synchronizovať s labelSyncUsername: description: Label for input to hold username. message: 'Používateľské meno: ' +labelTags: + description: Label for custom tags. + message: Značka (oddelené medzerou) +labelTheme: + description: Label for the visual theme option. + message: 'Motív:' labelTranslator: description: Label of translator. message: 'Prekladateľ: ' @@ -485,6 +653,27 @@ labelViewSingleColumn: labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. message: Tabuľkové zobrazenie +labelWidth: + description: Width. + message: 'Šírka:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Synchrónny režim $1 +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Povoľte iba vtedy, ak máte skript, ktorý sa musí spustiť pred začatím + načítavania stránky a v súčasnosti sa spúšťa príliš neskoro. Rovnako ako + režim okamžitého injektovania v Tampermonkey, aj táto možnosť používa + zastaraný synchrónny XHR, takže v prehliadači Chrome/Chromium sa v konzole + vývojárskych nástrojoch zobrazia varovania, hoci ich môžete pokojne + ignorovať, pretože nepriaznivé účinky sú v tomto prípade zanedbateľné. + Varovania môžete natrvalo skryť kliknutím pravým tlačidlom myši. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (okrem inkognita a stránok so zakázanými cookies) lastSync: description: Label for last sync timestamp. message: Posledná synchronizácia $1 @@ -515,6 +704,11 @@ menuExcludeHint: Ak chcete použiť zmeny na všetkých ostatných kartách, obnovte ich ručne. Pre väčšiu flexibilitu použite kartu „Nastavenia“ editora. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Spätná väzba menuFindScripts: description: Menu item to find scripts for a site. message: Vyhľadať skripty pre túto stránku @@ -522,8 +716,13 @@ menuInjectionFailed: description: Injection error. message: Nepodarilo sa injektovať niektoré skripty. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: Vyskúšať znova v režime "auto" +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Zodpovedajúce zakázané skripty menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. message: Iba skripty podrámcov @@ -534,14 +733,19 @@ menuNewScript: description: Menu item to create a new script. message: Vytvoriť nový skript menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Skripty sú zakázané menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Skripty sú povolené msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Kontrola aktualizácií... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Kliknutím otvoríte dokumentáciu MomentJS. Povolené tokeny: $1. Na ochranu + doslovného textu použite [hranaté zátvorky]. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -570,7 +774,7 @@ msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito window. - message: 'Zmeny, ktoré vykonáte v režime inkognito budú použité aj v hlavnom profile.' + message: Zmeny, ktoré vykonáte v režime inkognito budú použité aj v hlavnom profile. msgInstalled: description: Message shown when a script is installed. message: Skript bol nainštalovaný. @@ -593,11 +797,23 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Načítavam závislosti... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Chýbajú požadované zdroje. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. - message: 'Konflikt menného priestoru skriptu! Prosím, upravte @name a @namespace.' + message: |- + Takýto skript je už nainštalovaný. + Buď použite iné @name a @namespace alebo namiesto toho upravte tento skript. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Skript s rovnakým @name a @namespace je už nainštalovaný. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -606,21 +822,36 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Nie sú žiadne aktualizácie. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Chyba pri aktualizácii skriptov. Kliknutím ich otvoríte. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Prosím, znovu uložte alebo nainštalujte tieto skripty:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: ' (kód je rovnaký)' msgSavedBlacklist: description: Message shown when blacklist are saved. message: Aktualizovaná čierna listina. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Aktualizovaný vlastný štýl. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Nastavenia editora boli aktualizované. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: Šablóna nového skriptu bola uktualizovaná. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Skript [$1] bol aktualizovaný!' + message: Skript [$1] bol aktualizovaný! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: Zobraziť/skryť @@ -633,12 +864,28 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Chyba inicializácie! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Zatiaľ nie je autorizovaný. msgSyncReady: description: Message shown when sync will start soon. message: Synchronizácia sa spustí o chvíľu... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Prebieha synchronizácia... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Chyba syntaxe? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + Skript pri načítaní stránky neexistoval alebo sa nezhodoval s URL adresou, + čo sa stáva na stránkach s jednostránkovou aplikáciou, ako je Facebook alebo + Instagram, ktoré používajú falošnú navigáciu. Ak chcete spustiť skript, + môžete kartu znova načítať. Ak chcete opraviť skript, použite @match pre + celú stránku, a potom zistite zmeny pomocou MutationObserver alebo API + window.navigation. msgUpdated: description: Message shown when a script is updated/reinstalled. message: Skript aktualizovaný. @@ -659,15 +906,90 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: Schovať pole omnibox +optionPopup: + description: Label of the popup menu section in settings. + message: Vyskakovacia ponuka a ikona optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: Najskôr povolené +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Zoskupiť zakázané skripty + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Zoskupiť podľa @run-at optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: Skryť zakázané + message: Skryť zakázané skripty + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Zobraziť zakázané skripty + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Zobraziť najskôr povolené skripty +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Motív rozhrania:' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: automatický +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: tmavý +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: svetlý +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Aktualizácia +popupSettings: + description: Item inside the popup's "⋮" menu + message: Nastavenia vyskakovacieho okna +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Kliknutie pravým tlačidlom myši: vyskakovacie okno s nastaveniami' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Iba na čítanie +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Automaticky aktualizovaný skript je určený len na čítanie, pokiaľ nezakážete + aktualizácie alebo nepovolíte úpravy. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Povoliť úpravy +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (ďalšia aktualizácia ich prepíše) +reinstall: + description: Button to reinstall a script + message: Preinštalovať +reloadTab: + description: Label of action to reload the tab + message: Znova načítať kartu +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Ak je povolené, aktívna karta sa znovu načíta, keď sa zistia zmeny a tento + skript sa zhoduje s adresou URL karty. +removeAllScripts: + description: Button to remove all scripts + message: Odstrániť všetky skripty. + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: >- + Presunúť všetky skripty do "Koša"? + + Po nejakej dobe sa automaticky vyprázdni. Môžete ho tiež otvoriť a + vyprázdniť manuálne. + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Rozlišovať veľké a malé písmená @@ -683,14 +1005,82 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Nastavenia -titleScriptUpdated: - description: Notification title for script updates. - message: Aktualizácia +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Načítať stránku bez používateľských skriptov +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Zakázali ste používateľské skripty pre túto stránku. Ak ich chcete znovu + spustiť, znovu načítajte alebo prejdite na kartu. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Zoradenie:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Zastaviť sledovanie +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Normálna farba odznaku +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Farba odznaku, keď je web neinjektovateľný (na čiernej listine alebo + nepodporovaný) titleSearchHint: description: Hover title for search icon in dashboard. message: |- * kláves pridá text do histórie automatického dopĺňania * je podporovaná syntax RegExp: /re/ a /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Kláves Enter pridáva text do histórie automatického dopĺňania + + * Všetky podmienky nerozlišujú veľkosť písmen + + * Podmienky oddelené medzerou môžu byť kombinované + + * Vyhľadávanie podľa metadát: "Úžasný skript" "Popis" + + * Vyhľadávanie podľa značiek: #znacka1 #znacka2 + + * Vyhľadávanie podľa názvu skriptu: name:"úžasný názov" + + * Vyhľadávanie podľa kódu skriptu: code:"úžasný kód" + + * Záporné vyhľadávanie: !#znacka2 !name:"nechcené" + + * Regulárne výrazy: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? s medzerou" +toggleInjection: + description: Label for a keyboard shortcut. + message: Prepnúť injektovanie používateľského skriptu +trackEdits: + description: Button in a script installation dialog. + message: Sledovať externé úpravy +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: >- + Nechajte túto stránku otvorenú, aby ste mohli sledovať svoje úpravy v + lokálnom súbore +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Aktualizovať skripty ($1) +updateScript: + description: Button to update one script. + message: Aktualizácia +updateScriptsAll: + description: Command/button to update all scripts. + message: Aktualizovať všetky skripty +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Aktualizovať skripty pre aktuálnu kartu valueLabelKey: description: Label for key of a script value. message: Kľúč (reťazec) @@ -700,6 +1090,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: Všetky hodnoty (serializované ako JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: Hodnota (serializovaná ako JSON alebo $1) +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: reťazec $1 visitWebsite: description: Label for link to open Violentmonkey website. message: Prejsť na stránku + touched: false diff --git a/src/_locales/sr/messages.yml b/src/_locales/sr/messages.yml index d9bc43a384..28335d61f5 100644 --- a/src/_locales/sr/messages.yml +++ b/src/_locales/sr/messages.yml @@ -1,30 +1,48 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Примени buttonCancel: description: Cancel button on dialog. message: Откажи +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Потражи ажурирања + touched: false buttonClose: description: Button to close window. message: Затвори buttonConfirmInstallation: description: Button to confirm installation of a script. message: Потврди инсталацију + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: '' + touched: false buttonDisable: description: Button to disable a script. message: Онемогући +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false buttonEdit: description: Button to edit a script. message: Уреди buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: '' + message: >- + Можете и да притиснете десним тастером миша, точкићем или у комбинацији са + Ctrl-ом на назив скрипте. buttonEmptyRecycleBin: description: Button to empty the recycle bin. - message: '' + message: Испразни отпад buttonEnable: description: Button to enable a script. message: Омогући buttonExportData: description: Button to open the data export dialog. - message: Извези као zip + message: Извези као ZIP buttonFilter: description: Button to show filters menu. message: Филтери @@ -34,7 +52,7 @@ buttonHome: message: Почетна страница buttonImportData: description: Button to choose a file for data import. - message: Увези из zip датотеке + message: Увези из ZIP-а… buttonInstallFromURL: description: Button to ask for URL of a user script. message: Инсталирај са адресе @@ -50,7 +68,7 @@ buttonOK: message: У реду buttonRecycleBin: description: Button to list scripts in recycle bin. - message: '' + message: Отпад buttonRemove: description: Button to remove a script. message: Уклони @@ -62,49 +80,80 @@ buttonReplaceAll: message: Све buttonReset: description: Button to reset to default values. - message: '' + message: Ресетуј +buttonResetSettings: + description: Button in settings page to reset all settings + message: Ресетуј подешавања buttonRestore: description: Button to restore a removed script. - message: '' + message: Врати buttonSave: description: Button to save modifications of a script. message: Сачувај buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Сачувај и затвори +buttonSaved: + description: Button text after saving. + message: Сачувано buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. - message: '' + message: Прикажи подешавања уређивача buttonSupport: description: Button to open support page. message: Страница подршке +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. - message: '' - touched: false + message: Опозови buttonUpdate: - description: Check a script for updates. - message: Провери ажурирања + description: Button to update a script. + message: Ажурирај buttonUpdateAll: description: Check all scripts for updates. message: Провери ажурирања за све + touched: false buttonVacuum: description: Button to vacuum extension data. - message: Избриши базу података + message: Обриши кеш buttonVacuumed: description: Message shown when data is vacuumed. - message: Подаци су избрисани + message: Кеш је обрисан buttonVacuuming: description: Message shown when data vacuum is in progress. - message: Брисање података... + message: Брисање кеша… +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Аутоматско ажурирање је онемогућено за ову скрипту. + Кликните на „У реду” да бисте је ипак ажурирали. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Измене нису сачуване! Кликните на ОК да бисте их одбацили или на откажи за наставак. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Вратите све промене направљене у бази података (увоз, ажурирање, уређивање, + прилагођавање) descBlacklist: - description: HTML Description for the global blacklist. - message: Адресе које се подударају у овом списку неће убацити скрипта + description: Description for the global injection blacklist. + message: >- + Црна листа за убризгавање (скрипте се неће покретати на одговарајућим + сајтовима) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Мрежна црна листа (скрипте се неће повезати са одговарајућим сајтовима и + њиховим колачићима) descCustomCSS: description: Description of custom CSS section. message: >- @@ -112,16 +161,46 @@ descCustomCSS: Ако нисте сигурни чему служи, молимо не уређујте. descEditorOptions: description: Description of editor options JSON section. + message: >- + Прилагођене опције за CodeMirror и додатке у JSON објекту као што је + {"indentUnit":2, "smartIndent":true}, међутим, имајте на уму да + неке од њих можда неће радити у Violentmonkey. Погледајте целу листу. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Булеан опције могу се пребацивати двоструким кликом на вредност: true + = омогућено, false = онемогућено. Нумеричке опције које се завршавају + на Delay, Interval, Rate, + Time су у милисекундама. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: '' +descScriptTemplate: + description: Description of script template section. + message: '' +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: '' +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: '' +disabledScriptsSelector: + description: Label of the option. + message: '' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' message: '' editHelpDocumention: description: Label in the editor help tab for the documentation link. message: '' editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. - message: '' + message: 'Пречице на тастатури:' editHowToHint: description: The text of the how-to link in the editor header. - message: '' + message: Користите други уређивач? editLabelMeta: description: Metadata section in settings tab of script editor. message: Прилагођени метаподаци @@ -130,10 +209,16 @@ editLabelSettings: message: Подешавања скрипти editLongLine: description: Shown in the editor in lines that were cut due to being too long - message: '' + message: Ред је предугачак editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long - message: '' + message: >- + Овај ред је предугачак па је његов текст скупљен да би се избегла кашњења + приликом уређивања. + + Можете подесити ограничење у напредним подешавањима, на пример: + + „maxDisplayLength“: 20000 editNavCode: description: Label of code tab in script editor. message: Код @@ -142,99 +227,138 @@ editNavSettings: message: Подешавања editNavValues: description: Label of values tab in script editor. - message: '' + message: Вредности editValueAll: description: Button to show/edit the entire script value storage. message: '' + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: '' -editValueSave: - description: Button to save modification of a script value. - message: '' + message: Прикажи/измени целокупно складиште вредности скрипте extDescription: - description: 'Description for this extension, will be displayed in web store' - message: '' + description: Description for this extension, will be displayed in web store + message: Менаџер корисничких скрипти отвореног кода који подржава много претраживача extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' - message: '' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: Стављено на црну листу у подешавањима Violentmonkey-а. failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) + message: >- + Violentmonkey не може да покрене корисничке скрипте на овој страници (чести + примери: кориснички интерфејс претраживача, додатак, блокирано политиком, + сајт за претрагу у Opera-у). +failureReasonRestarted: + description: Shown in the popup. + message: >- + Violentmonkey је поново покренут. Поново учитајте картицу да бисте покренули + корисничке скрипте. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. message: '' filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: алфабету + message: абецедним редом filterExecutionOrder: description: Label for option to sort scripts in execution order. - message: реду извршења + message: ред извршења filterLastUpdateOrder: description: Label for option to sort scripts by last update time. + message: време последњег ажурирања +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: '' + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: '' + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: '' + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: величина genericError: description: Label for generic error. - message: '' + message: Грешка genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' - message: '' + description: To indicate something is turned off or disabled, similar to "no". + message: искључено genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' - message: '' + description: To indicate something is turned on or enabled, similar to "yes". + message: на genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. - message: '' + message: користите глобално подешавање +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + „#“ обрасци раде само када се на почетку отвори сајт или поново учита + картица:$1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. + message: Отпад +helpForLocalFile: + description: Label of the checkbox. + message: Прикажи упутство за повучене и испуштене локалне скрипте. +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: за $1 одговарајуће скрипте +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Унесите адресу:' hintRecycleBin: description: Hint for recycle bin. - message: '' + message: Уклоњене скрипте су наведене овде и чуваће се 7 дана. hintUseDownloadURL: description: Shown as a place holder for @updateURL when it is not assigned message: Користи @downloadURL hintVacuum: description: Hint for vacuuming data. message: Одбаци вишак и поново учитај недостајуће податке у кеш. +install: + description: Label for button to install a script. + message: Инсталирај installFrom: description: Label for button to install script from a userscript site. message: Инсталирај са $1 installOptionClose: description: Option to close confirm window after installation. message: Затвори након инсталације -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Прати локалну датотеку пре него што се овај прозор затвори + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before window is closed. - message: '' + message: >- + Картица изворне датотеке треба да буде отворена у Firefox-у 68 и новијим + верзијама. labelAbout: description: Label shown on top of the about page. message: Информације о Violentmonkey touched: false labelAdvanced: description: Label for button to show advanced settings. - message: '' + message: Напредно labelAllowUpdate: description: Option to allow checking updates for a script. message: Дозволи ажурирања @@ -251,27 +375,38 @@ labelAutoReloadCurrentTabDisabled: Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: '' + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates message: '' +labelBackup: + description: Label of the import/export section in settings. + message: '' + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Прављење резервних копија и одржавање labelBadge: description: Label for option to show number on badge. - message: '' + message: 'Приказ на значки:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Боје значке:' labelBadgeNone: description: Option to display nothing on badge. - message: '' + message: ниједан labelBadgeTotal: description: Option to display total number of running scripts on badge. - message: '' + message: број покренутих скрипти labelBadgeUnique: description: Option to display number of unique running scripts on badge. - message: '' + message: број јединствених покренутих скрипти labelBlacklist: description: Label for global blacklist settings in security section. message: Црна листа labelContributors: description: Label for link to contributors. - message: '' + message: Сарадници labelCurrentLang: description: Label of current language. message: 'Тренутни језик:' @@ -281,24 +416,30 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Извоз података + touched: false labelDataImport: description: Section title of data import. message: Увоз података + touched: false labelDonate: description: Label of link to donate page. message: Донирај + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'Адреса преузимања:' labelEditValue: description: Label shown in the panel to edit a script value. - message: '' + message: Уређивање вредности скрипте labelEditValueAll: description: Label shown in the panel to edit the entire script value storage. - message: '' + message: Уређивање складишта скрипте labelEditor: description: Label for Editor settings - message: '' + message: Уређивач +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: само омогућене скрипте labelExclude: description: Label of @exclude rules. message: Правила изузимања @@ -310,28 +451,34 @@ labelExportScriptData: message: Извези податке скрипти labelExposeStatus: description: Option in advanced settings. + message: Изложите инсталирану верзију на сајтовима каталога корисничких скрипта:$1 +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Алтернативни режим $1 у Firefox-у +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. message: '' labelFeedback: description: Label of link to feedback page. message: Повратне информације -labelFilterSort: - description: Label for sort filter. - message: Сортирај по $1 labelGeneral: description: Label for general settings. message: Опште labelHelpTranslate: description: Label for link to localization guide in about tab - message: '' + message: Помоћ у преводу labelHomepage: description: Label for home page in about tab. - message: '' + message: Почетна страница labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'Адреса почетне странице:' +labelIconURL: + description: Label for the input. + message: 'URL иконе:' labelImportScriptData: description: Option to import script data along with scripts. - message: '' + message: увоз података скрипте labelImportSettings: description: Label for option to import settings from zip file. message: Увези подешавања @@ -339,8 +486,10 @@ labelInclude: description: Label of @include rules. message: Правила обухватања labelInjectionMode: - description: Label for default option to inject scripts. - message: '' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Режим убризгавања:' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: Инсталирање скрипте @@ -349,7 +498,7 @@ labelKeepOriginal: message: Задржи оригинал labelLastUpdatedAt: description: Label shown on last updated time. - message: '' + message: Последњи пут ажурирано у $1 labelLineNumber: description: Label for line number jumper. message: 'Број линије:' @@ -359,10 +508,12 @@ labelMatch: labelName: description: Label of script name. message: Име +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: '' labelNoName: description: Text as the name of a script when no @name is assigned. message: Без имена - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: Нема пронађених скрипти. @@ -371,19 +522,21 @@ labelNotifyThisUpdated: A per-script option in editor to enable notification when this script is updated. The text follows "Allow update" checkbox option so it's like a continuation of the phrase. - message: '' + message: ', онда обавести:' labelNotifyUpdates: description: Option to show notification when script is updated. message: Обавести о ажурирањима скрипти labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. - message: '' -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: '' + message: игнорисати обавештење по скрипти (картица „подешавања“ у уређивачу) labelPrivacyPolicy: description: Label of link to privacy policy - message: '' + message: Политика приватности +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Поновно инсталирање скрипте labelRelated: description: Label of related links. message: Сродне везе @@ -401,7 +554,7 @@ labelRunAtDefault: message: (Подразумевано) labelScriptTemplate: description: Label for custom script template. - message: '' + message: Прилагођени Шаблон Скрипте labelSearch: description: Label for search input in search box. message: 'Тражи:' @@ -411,24 +564,33 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Подешавања +labelShowOrder: + description: Label for option in dashboard -> script list + message: Прикажи позиције редоследа извршавања +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Синхронизација labelSyncAnonymous: description: Label for using anonymous account. - message: '' + message: Користите анонимни налог labelSyncAuthorize: description: Label for button to authorize a service. message: Одобри labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Одобравање +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: Ниједна labelSyncPassword: description: Label for input to hold password. - message: '' + message: 'Лозинка:' labelSyncReauthorize: description: Option to reauthorize sync service when expired. message: '' @@ -441,13 +603,19 @@ labelSyncScriptStatus: message: Статус синхронизовања скрипти labelSyncServerUrl: description: Label for input to hold server URL. - message: '' + message: 'URL сервера:' labelSyncService: description: Label for sync service select. message: Синхронизуј са labelSyncUsername: description: Label for input to hold username. - message: '' + message: 'Корисничко име:' +labelTags: + description: Label for custom tags. + message: 'Ознаке (раздвојене размаком):' +labelTheme: + description: Label for the visual theme option. + message: 'Тема:' labelTranslator: description: Label of translator. message: 'Преводилац:' @@ -459,10 +627,24 @@ labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: Једна колона labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. + message: Табеларно +labelWidth: + description: Width. message: '' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Синхрони $1 режим +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: '' +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (осим без архивирања и сајтова са онемогућеним колачићима) lastSync: description: Label for last sync timestamp. message: 'Последња синхронизација: $1' @@ -471,7 +653,7 @@ learnBlacklist: message: Сазнајте више о обрасцима за црну листу learnInjectionMode: description: Refers to a link to introduce injection modes. - message: '' + message: Сазнајте више о режимима убризгавања. menuCommands: description: Menu item to list script commands. message: Команде скрипте @@ -483,19 +665,29 @@ menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: '' + message: Искључи... menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. message: '' +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Повратне информације menuFindScripts: description: Menu item to find scripts for a site. message: Пронађи скрипте за овај сајт menuInjectionFailed: description: Injection error. - message: '' + message: Није могуће убацити неке скрипте. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' - message: '' + description: Injection error fix, shown in case the default mode is "page". + message: Покушајте поново у „аутоматском“ режиму +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Подударање онемогућених скрипти menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. message: '' @@ -504,21 +696,24 @@ menuMatchedScripts: message: Скрипте које се подударају menuNewScript: description: Menu item to create a new script. - message: '' + message: Направи нову скрипту menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Скрипта је онемогућена menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Скрипта је омогућена msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Провера ажурирања... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: '' msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of the script. - message: '' + message: Грешка при преузимању ресурса! msgErrorFetchingScript: description: Message shown when Violentmonkey fails fetching a new version of the script. message: Грешка при преузимању скрипте! @@ -542,7 +737,9 @@ msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito window. - message: '' + message: >- + Промене које унесете у режиму без архивирања примењују се и на ваш главни + профил. msgInstalled: description: Message shown when a script is installed. message: Скрипта је инсталирана. @@ -565,37 +762,62 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Учитавање захтева... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Недостају потребни ресурси. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. message: Неусаглашеност имена скрипти! Измените @name и @namespace. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Скрипта са истим @name и @namespace је већ инсталирана. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @downloadURL is provided. - message: Нова верзија је пронађена. + message: Доступна је нова верзија. msgNoUpdate: description: Message shown when there is no new version of a script. message: Нема пронађених ажурирања. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Грешка при ажурирању скрипти. Кликните да их отворите. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Поново сачувајте или поново инсталирајте ове скрипту/e:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (код је иста) msgSavedBlacklist: description: Message shown when blacklist are saved. message: Црна листа је ажурирана + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Прилагођени стил је ажуриран. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: '' + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: '' + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Скрипта [$1] је ажурирана.' + message: Скрипта [$1] је ажурирана. msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff - message: '' + message: Прикажи/сакриј msgSyncError: description: Message shown when sync failed. message: Грешка при синхронизацији @@ -605,12 +827,28 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Грешка при покретању! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Још није овлашћено. msgSyncReady: description: Message shown when sync will start soon. message: Синхронизација ће ускоро почети... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Синхронизовање... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Синтаксичка грешка? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + Скрипта није постојала или се није подударала са УРЛ-ом када се страница + учитала, што се дешава на сајтовима са једном страницом као што су fb или + instagram који користе лажну навигацију. Можете освежити таб да бисте + покренули скрипту. Да бисте исправили скрипту, користите @match за читав + сајт и затим откријте промене користећи MutationObserver или + window.navigation API. msgUpdated: description: Message shown when a script is updated/reinstalled. message: Скрипта је ажурирана. @@ -619,33 +857,104 @@ msgUpdating: message: Ажурирање... noValues: description: Label shown when there is no value for current script. - message: '' + message: Не чува се никаква вредност optionEditorWindow: description: Label for the option in settings - message: '' + message: Отворите уређивач из искачућег прозора у новом прозору optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support onBoundsChanged - message: '' + message: Положај прозора уређивача ће се памтити само при промени величине или чувању optionEditorWindowSimple: description: Label for the editor window type - message: '' + message: Сакриј омнибокс +optionPopup: + description: Label of the popup menu section in settings. + message: Искачући мени и икона optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. - message: '' + message: Прво омогућено +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Групно онемогућене скрипте + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Групиши по @run-at фази optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: '' + message: Сакриј онемогућене скрипте + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Прикажи онемогућене скрипте + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Прикажи прво омогућене скрипте +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Тема корисничког интерфејса:' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: аутоматски +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: мрачна +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: светла +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Ажурирај +popupSettings: + description: Item inside the popup's "⋮" menu + message: '' +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Само за читање +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Аутоматски ажурирана скрипта је само за читање осим ако не онемогућите + ажурирања или дозволите уређивање. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Дозволи измене +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (следеће ажурирање ће их заменити) +reinstall: + description: Button to reinstall a script + message: Поново инсталирај +reloadTab: + description: Label of action to reload the tab + message: Поново учитај картицу +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Ако је омогућено, активна табла ће се освежити када се открију промене и ако + се скрипта поклапа са URL-ом табле. +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search - message: '' + message: Осетљиво на велика и мала слова searchUseRegex: description: Option to perform a regular expression search - message: '' + message: Користи Regex sideMenuAbout: description: 'Side menu: About' message: Информације @@ -655,21 +964,76 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Подешавања -titleScriptUpdated: - description: Notification title for script updates. - message: Ажурирај +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Поново учитај страницу без корисничких скрипти +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Онемогућили сте корисничке скрипте за ову страницу. Да бисте их покренули, + поново учитајте картицу или се крећете по њој. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Зауставите праћење +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Нормална боја значке +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: Badge color when the site is non-injectable (blacklisted or unsupported) titleSearchHint: description: Hover title for search icon in dashboard. message: '' + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: '' +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: Пратите спољне измене +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: >- + Оставите ову страницу отвореном да бисте пратили своје измене у локалној + датотеци +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Ажурирајте скрипте ($1) +updateScript: + description: Button to update one script. + message: Ажурирај +updateScriptsAll: + description: Command/button to update all scripts. + message: Ажурирајте све скрипте +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Ажурирајте скрипте тренутне картице valueLabelKey: description: Label for key of a script value. - message: '' + message: Кључ (жица/стринг) valueLabelValue: description: Label for value of a script value. - message: '' + message: Вредност (сериализовано као JSON) valueLabelValueAll: description: Label for input of entire script value storage. + message: Све вредности (сериализовано као JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: '' + touched: false diff --git a/src/_locales/sv/messages.yml b/src/_locales/sv/messages.yml new file mode 100644 index 0000000000..e0545c8c71 --- /dev/null +++ b/src/_locales/sv/messages.yml @@ -0,0 +1,1085 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Verkställ +buttonCancel: + description: Cancel button on dialog. + message: Avbryt +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Sök efter uppdateringar + touched: false +buttonClose: + description: Button to close window. + message: Stäng +buttonConfirmInstallation: + description: Button to confirm installation of a script. + message: '' + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: '' + touched: false +buttonDisable: + description: Button to disable a script. + message: Inaktivera +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false +buttonEdit: + description: Button to edit a script. + message: Redigera +buttonEditClickHint: + description: Tooltip for the Edit button in popup. + message: Du kan också högerklicka, CTRL-klicka eller hjulklicka på skriptnamnet. +buttonEmptyRecycleBin: + description: Button to empty the recycle bin. + message: Töm papperskorgen nu! +buttonEnable: + description: Button to enable a script. + message: Aktivera +buttonExportData: + description: Button to open the data export dialog. + message: Exportera till ZIP +buttonFilter: + description: Button to show filters menu. + message: '' + touched: false +buttonHome: + description: Button to open homepage. + message: Hemsida +buttonImportData: + description: Button to choose a file for data import. + message: Importera från ZIP +buttonInstallFromURL: + description: Button to ask for URL of a user script. + message: Installera från webbadress +buttonInstallOptions: + description: Button to show options of installation confirm page. + message: '' + touched: false +buttonNew: + description: Button to create a new script. + message: Ny +buttonOK: + description: OK button on dialog. + message: OK +buttonRecycleBin: + description: Button to list scripts in recycle bin. + message: Papperskorg +buttonRemove: + description: Button to remove a script. + message: Ta bort +buttonReplace: + description: Button to replace the current match. + message: Ersätt +buttonReplaceAll: + description: Button to replace all matches. + message: Alla +buttonReset: + description: Button to reset to default values. + message: Återställ +buttonResetSettings: + description: Button in settings page to reset all settings + message: Återställ inställningar +buttonRestore: + description: Button to restore a removed script. + message: Återställ +buttonSave: + description: Button to save modifications of a script. + message: Spara +buttonSaveClose: + description: Button to save modifications of a script and then close the editing page. + message: Spara & stäng +buttonSaved: + description: Button text after saving. + message: Sparad +buttonShowEditorState: + description: Button to show the list of currently used CodeMirror options. + message: Visa redigerarens tillstånd +buttonSupport: + description: Button to open support page. + message: Supportsida +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' +buttonUndo: + description: Button to undo removement of a script. + message: Ångra +buttonUpdate: + description: Button to update a script. + message: Uppdatera +buttonUpdateAll: + description: Check all scripts for updates. + message: Sök efter uppdateringar för alla + touched: false +buttonVacuum: + description: Button to vacuum extension data. + message: Dammsug databasen +buttonVacuumed: + description: Message shown when data is vacuumed. + message: Data har dammsugits +buttonVacuuming: + description: Message shown when data vacuum is in progress. + message: Dammsuger data... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Automatisk uppdatering är inaktiverad för detta skript + Klicka på OK för att uppdatera det ändå. +confirmNotSaved: + description: Confirm message shown when there are unsaved script modifications. + message: |- + Ändringar har inte sparats! + Klicka på OK för att förkasta dem eller avbryt för att stanna. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Återställ alla ändringar som gjorts i databasen (importering, uppdatering, + redigering, anpassning) +descBlacklist: + description: Description for the global injection blacklist. + message: Injektionssvartlista (skript körs inte på matchande webbplatser) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Nätverkssvartlista (skript ansluter inte till matchande webbplatser och + deras cookies) +descCustomCSS: + description: Description of custom CSS section. + message: >- + Anpassad CSS för alternativsidan och skriptinstallationssidan. Om du inte är + säker på vad detta är till för, ändra inget. +descEditorOptions: + description: Description of editor options JSON section. + message: >- + Anpassade alternativ för CodeMirror och tillägg i JSON-objekt som + {"indentUnit":2, "smartIndent":true} Observera att vissa av dem + kanske inte fungerar i Violentmonkey. Se full lista. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Booleska alternativ kan växlas genom att dubbelklicka på värdet: + true = aktiverad, false = inaktiverad. Numeriska + alternativ som slutar med fördröjning, intervall, + värde eller tid är i millisekunder. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Icke-standardiserade alternativ: autocompleteOnTyping är en + fördröjning i millisekunder för att visa autoslutförande tips efter att ha + skrivit klart (0 = inaktivera), + killTrailingSpaceOnSave tar automatiskt bort efterföljande + mellanslag i varje rad när du sparar, showTrailingSpace visar + de efterföljande mellanslagen som punkter. +descScriptTemplate: + description: Description of script template section. + message: >- + Variabler som stöds: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + med MomentJS-kompatibelt format, för exempel <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: grupp +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: dölj +disabledScriptsSelector: + description: Label of the option. + message: 'Inaktiverade skript:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: visa +editHelpDocumention: + description: Label in the editor help tab for the documentation link. + message: 'Dokumentation om användarskriptmetadatablock och GM API:' +editHelpKeyboard: + description: Label in the editor help tab for the keyboard shortcuts. + message: 'Tangentbordsgenvägar:' +editHowToHint: + description: The text of the how-to link in the editor header. + message: Använd en annan redigerare? +editLabelMeta: + description: Metadata section in settings tab of script editor. + message: Anpassad metadata +editLabelSettings: + description: Settings section in settings tab of script editor. + message: Skriptinställningar +editLongLine: + description: Shown in the editor in lines that were cut due to being too long + message: Raden är för lång +editLongLineTooltip: + description: Tooltip shown in the editor in lines that were cut due to being too long + message: >- + Den här raden är för lång så texten är komprimerad för att undvika + fördröjningar vid redigering. + + Du kan justera gränsen i avancerade inställningar, till exempel: + + "maxDisplayLength": 20000 +editNavCode: + description: Label of code tab in script editor. + message: Kod +editNavSettings: + description: Label of settings tab in script editor. + message: Inställningar +editNavValues: + description: Label of values tab in script editor. + message: Värden +editValueAll: + description: Button to show/edit the entire script value storage. + message: '' + touched: false +editValueAllHint: + description: Tooltip for the 'all' button. + message: Visa/redigera hela skriptvärdeslagringen +extDescription: + description: Description for this extension, will be displayed in web store + message: En användarskripthanterare med öppen källkod som stöder många webbläsare +extName: + description: Name of this extension. + message: Violentmonkey +failureReasonBlacklisted: + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: Svartlistad i Violentmonkey's inställningar +failureReasonNoninjectable: + description: >- + Shown for URLs that cannot be processed (same places as + failureBlacklistedUrl) + message: >- + Violentmonkey kan inte köra användarskript på den här sidan + + (vanliga exempel: webbläsar-UI, tillägg, blockerad via policyer, en + sökmotorsida i Opera) +failureReasonRestarted: + description: Shown in the popup. + message: Violentmonkey har startats om. Ladda om fliken för att köra användarskript. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Det finns flera metoder för att installera eller spåra redigeringar i en + lokal fil: <1> Dra och släpp filen till en öppen Violentmonkey-sida eller + popup, inklusive denna. <2> Installera en lokal HTTP-server för denna fil + och öppna den via http://localhost. <3> Aktivera "Tillåt åtkomst till + filwebbadresser" i detaljer för Violentmonkey på sidan chrome://extensions. + Detta är farligt eftersom alla användarskript kan läsa alla lokala filer. +filterAlphabeticalOrder: + description: Label for option to sort scripts in alphabetical order. + message: alfabetisk +filterExecutionOrder: + description: Label for option to sort scripts in execution order. + message: exekution +filterLastUpdateOrder: + description: Label for option to sort scripts by last update time. + message: senaste uppdateringstid +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' +filterScopeAll: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterScopeCode: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterScopeName: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: storlek +genericError: + description: Label for generic error. + message: Fel +genericOff: + description: To indicate something is turned off or disabled, similar to "no". + message: av +genericOn: + description: To indicate something is turned on or enabled, similar to "yes". + message: på +genericUseGlobal: + description: To indicate some per-script option will use its analog in global settings. + message: använd globala inställningar +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + "#"-mönster fungerar endast när du öppnar webbplatsen eller laddar om + fliken: $1 +headerRecycleBin: + description: Text shown in the header when scripts in recycle bin are listed. + message: Papperskorg +helpForLocalFile: + description: Label of the checkbox. + message: Visa instruktionerna för drag och släpp för lokala skript +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: för $1 matchande skript +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' +hintInputURL: + description: Hint for a prompt box to input URL of a user script. + message: 'Ange webbadress:' +hintRecycleBin: + description: Hint for recycle bin. + message: Borttagna skript listas här och kommer att sparas i 7 dagar. +hintUseDownloadURL: + description: Shown as a place holder for @updateURL when it is not assigned + message: Använd @downloadURL +hintVacuum: + description: Hint for vacuuming data. + message: Förkasta redundansen och försök ladda om de saknade resurserna i cachen. +install: + description: Label for button to install a script. + message: Installera +installFrom: + description: Label for button to install script from a userscript site. + message: Installera från $1 +installOptionClose: + description: Option to close confirm window after installation. + message: '' + touched: false +installOptionTrackTooltip: + description: >- + Tooltip in Firefox 68+ for the option to track the loading local file before + window is closed. + message: Källfilsfliken bör hållas öppen i Firefox 68 och senare. +labelAbout: + description: Label shown on top of the about page. + message: '' + touched: false +labelAdvanced: + description: Label for button to show advanced settings. + message: Avancerat +labelAllowUpdate: + description: Option to allow checking updates for a script. + message: Tillåt uppdatering +labelAuthor: + description: Label of author shown in the details of a script. + message: 'Upphovsman: ' +labelAutoReloadCurrentTab: + description: Option to reload current tab after a script is switched on or off from menu. + message: >- + Ladda om aktuell flik efter att ha aktiverat/inaktiverat ett skript från + menyn +labelAutoReloadCurrentTabDisabled: + description: >- + Tooltip in menu after Violentmonkey is switched off if auto-reload is + disabled. + message: '' + touched: false +labelAutoUpdate: + description: Option to allow automatically checking scripts for updates + message: Sök efter skriptuppdateringar var $1 dag, ange 0 för att inaktivera +labelBackup: + description: Label of the import/export section in settings. + message: '' + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Säkerhetskopiera och underhåll +labelBadge: + description: Label for option to show number on badge. + message: 'Bricka:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Brickfärger: ' +labelBadgeNone: + description: Option to display nothing on badge. + message: inga +labelBadgeTotal: + description: Option to display total number of running scripts on badge. + message: antal skript som körs +labelBadgeUnique: + description: Option to display number of unique running scripts on badge. + message: antal unika skript som körs +labelBlacklist: + description: Label for global blacklist settings in security section. + message: Svartlista +labelContributors: + description: Label for link to contributors. + message: Bidragsgivare +labelCurrentLang: + description: Label of current language. + message: 'Aktuellt språk: ' +labelCustomCSS: + description: Label for custom CSS section. + message: Anpassad stil +labelDataExport: + description: Section title of data export. + message: '' + touched: false +labelDataImport: + description: Section title of data import. + message: '' + touched: false +labelDonate: + description: Label of link to donate page. + message: '' + touched: false +labelDownloadURL: + description: Label of script @downloadURL in custom meta data. + message: 'Nerladdningswebbadress:' +labelEditValue: + description: Label shown in the panel to edit a script value. + message: Redigera skriptvärde +labelEditValueAll: + description: Label shown in the panel to edit the entire script value storage. + message: Redigera skriptlagring +labelEditor: + description: Label for Editor settings + message: Redigerare +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: endast aktiva skript +labelExclude: + description: Label of @exclude rules. + message: '@uteslutna regler' +labelExcludeMatch: + description: Label of @exclude-match rules. + message: '@exclude-match regler' +labelExportScriptData: + description: Option to export script data along with scripts. + message: Exportera skriptdata +labelExposeStatus: + description: Option in advanced settings. + message: 'Visa installerad version på användarskriptkatalogsidor: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Alternativt $1-läge i Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + En alternativ injektionsmetod för skript sedan Firefox 59, + snabbare än standardläget. På samma sätt som "Synkront sidläge" ökar det + också minnesförbrukningen, så du kanske vill inaktivera det om dina skript + fungerar korrekt utan detta alternativ. +labelFeedback: + description: Label of link to feedback page. + message: Återkoppling +labelGeneral: + description: Label for general settings. + message: Allmänt +labelHelpTranslate: + description: Label for link to localization guide in about tab + message: Hjälp med översättning +labelHomepage: + description: Label for home page in about tab. + message: Hemsida +labelHomepageURL: + description: Label of script @homepageURL in custom meta data. + message: Hemsidans webbadress +labelIconURL: + description: Label for the input. + message: 'Ikonens webbadress:' +labelImportScriptData: + description: Option to import script data along with scripts. + message: Importera skriptdata +labelImportSettings: + description: Label for option to import settings from zip file. + message: Importera inställningar +labelInclude: + description: Label of @include rules. + message: '@inkluderade regler' +labelInjectionMode: + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Injektionsläge: ' +labelInstall: + description: Shown in the title of the confirm page while trying to install a script. + message: Installerar skript +labelKeepOriginal: + description: Option to keep the original match or ignore rules. + message: Behåll originalet +labelLastUpdatedAt: + description: Label shown on last updated time. + message: Senast uppdaterad $1 +labelLineNumber: + description: Label for line number jumper. + message: 'Radnr.: ' +labelMatch: + description: Label of @match rules. + message: '@matchande regler' +labelName: + description: Label of script name. + message: 'Namn:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Kör i ramar: ' +labelNoName: + description: Text as the name of a script when no @name is assigned. + message: Inget namn +labelNoSearchScripts: + description: Message shown when no script is found in search results. + message: Hittade inga skript. +labelNotifyThisUpdated: + description: >- + A per-script option in editor to enable notification when this script is + updated. The text follows "Allow update" checkbox option so it's like a + continuation of the phrase. + message: ', meddela sedan:' +labelNotifyUpdates: + description: Option to show notification when script is updated. + message: Visa meddelande om skriptuppdateringar +labelNotifyUpdatesGlobal: + description: Option to prioritize global notification option over script's setting. + message: ignorera avisering per skript ("inställningar" i redigeraren) +labelPrivacyPolicy: + description: Label of link to privacy policy + message: Integritetspolicy +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Återinstallera skriptet +labelRelated: + description: Label of related links. + message: 'Ladda om länkarna: ' +labelRemovedAt: + description: Label for the time when the script is removed. + message: Borttagen vid $1 +labelReplace: + description: Label for replace input in search box. + message: 'Ersatt med: ' +labelRunAt: + description: Label of script @run-at properties in custom meta data. + message: 'Kör vid: ' +labelRunAtDefault: + description: Shown when custom @run-at is not assigned. + message: (Standard) +labelScriptTemplate: + description: Label for custom script template. + message: Anpassad skriptmall +labelSearch: + description: Label for search input in search box. + message: 'Sök efter: ' +labelSearchScript: + description: Placeholder for script search box. + message: Sök skript... +labelSettings: + description: Label shown on the top of settings page + message: Inställningar +labelShowOrder: + description: Label for option in dashboard -> script list + message: Visa exekveringsorderpositioner +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' +labelSync: + description: Label for sync options. + message: Synka +labelSyncAnonymous: + description: Label for using anonymous account. + message: Använd anonymt konto +labelSyncAuthorize: + description: Label for button to authorize a service. + message: Auktorisera +labelSyncAuthorizing: + description: Label for button when authorization is in progress. + message: Auktoriserar +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' +labelSyncDisabled: + description: Label for option to disable sync service. + message: Inga +labelSyncPassword: + description: Label for input to hold password. + message: 'Lösenord: ' +labelSyncReauthorize: + description: Option to reauthorize sync service when expired. + message: '' + touched: false +labelSyncRevoke: + description: Label for button to revoke authorization for a service. + message: Återkalla +labelSyncScriptStatus: + description: Label for option to sync script status. + message: Skriptstatus för synkronisering +labelSyncServerUrl: + description: Label for input to hold server URL. + message: 'Serverwebbadress: ' +labelSyncService: + description: Label for sync service select. + message: Synka till +labelSyncUsername: + description: Label for input to hold username. + message: 'Användarnamn: ' +labelTags: + description: Label for custom tags. + message: 'Taggar (separera med mellanslag):' +labelTheme: + description: Label for the visual theme option. + message: 'Tema: ' +labelTranslator: + description: Label of translator. + message: '' + touched: false +labelUpdateURL: + description: Label of script @updateURL in custom meta data. + message: 'Uppdateringswebbadress:' +labelViewSingleColumn: + description: >- + Label for option in dashboard script list to show the scripts in single + column. + message: Enkel kolumn +labelViewTable: + description: Label for option in dashboard script list to show the scripts as a table. + message: Tabellvy +labelWidth: + description: Width. + message: 'Bredd:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Synkront $1-läge +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Aktivera endast om du har ett skript som måste köras innan sidan börjar + laddas och för närvarande körs för sent. Precis som omedelbart + injektionsläge i Tampermonkey använder det här alternativet den föråldrade + synkrona XHR, så i Chrome/Chromium kommer du att se varningar i + devtools-konsolen, även om du säkert kan ignorera dem eftersom de negativa + effekterna är försumbara i det här fallet. Du kan dölja varningarna för gott + genom att högerklicka på en. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (förutom inkognito och webbplatser med inaktiverade cookies) +lastSync: + description: Label for last sync timestamp. + message: Senaste synkronisering $1 +learnBlacklist: + description: Refers to a link to introduce blacklist patterns. + message: Läs mer om svartlistade mönster. +learnInjectionMode: + description: Refers to a link to introduce injection modes. + message: Lär dig mer om injektionslägen. +menuCommands: + description: Menu item to list script commands. + message: '' + touched: false +menuDashboard: + description: Label for menu item to open dashboard. + message: Öppna informationspanel +menuExclude: + description: >- + Shown in popup menu after clicking script's "..." dropdown so try to keep + the label short. + message: Uteslut... +menuExcludeHint: + description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. + message: >- + Den aktuella fliken kommer att laddas om automatiskt om du aktiverade det + här alternativet i allmänna inställningar. + + För att verkställa ändringar på alla andra flikar, ladda om dem manuellt. + + Använd fliken "Inställningar" i redigeraren för mer flexibilitet. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Återkoppling +menuFindScripts: + description: Menu item to find scripts for a site. + message: Hitta skript för den här webbplatsen +menuInjectionFailed: + description: Injection error. + message: Det gick inte att injicera vissa skript. +menuInjectionFailedFix: + description: Injection error fix, shown in case the default mode is "page". + message: Försök igen i "auto-läge" +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Matchade inaktiverade skript +menuMatchedFrameScripts: + description: Label for menu listing matching scripts in sub-frames. + message: Endast underramsskript +menuMatchedScripts: + description: Label for menu listing matched scripts. + message: Matchade skript +menuNewScript: + description: Menu item to create a new script. + message: Skapa ett nytt skript +menuScriptDisabled: + description: Menu item showing the status of Violentmonkey, when disabled. + message: Skript inaktiverade +menuScriptEnabled: + description: Menu item showing the status of Violentmonkey, when enabled. + message: Skript aktiverade +msgCheckingForUpdate: + description: Message shown when a script is being checked for updates by version numbers. + message: Sök efter uppdateringar... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Klicka för att öppna MomentJS-dokumentationen. Tillåtna tokens: $1. Använd + [hakparenteser] för att skydda det som är text. +msgErrorFetchingResource: + description: >- + Message shown when Violentmonkey fails fetching a resource/require/icon of + the script. + message: Fel vid hämtning av resurs! +msgErrorFetchingScript: + description: Message shown when Violentmonkey fails fetching a new version of the script. + message: Det gick inte att hämta skriptet! +msgErrorFetchingUpdateInfo: + description: Message shown when Violentmonkey fails fetching version data of the script. + message: Det gick inte att hämta uppdateringsinformationen! +msgErrorLoadingData: + description: >- + Message shown on confirm page when the script to be installed cannot be + loaded. + message: Det gick inte att ladda skriptdata. +msgErrorLoadingDependency: + description: Message shown when not all requirements are loaded successfully. + message: Fel vid laddning av behövande filer. +msgImported: + description: >- + Message shown after import. There is an argument referring to the count of + scripts imported. + message: $1 objekt importerades. +msgIncognitoChanges: + description: >- + Message shown in popup and installation tab when opened in an incognito + window. + message: Ändringar du gör i privatläget gäller även i din huvudprofil. +msgInstalled: + description: Message shown when a script is installed. + message: Skript installerat. +msgInvalidScript: + description: Message shown when script is invalid. + message: Ogiltigt skript! +msgLoadedData: + description: >- + Message shown in the confirm page when a javascript file to be installed is + loaded. + message: '' + touched: false +msgLoading: + description: Message shown in the options page before script list is loaded. + message: '' + touched: false +msgLoadingData: + description: Message shown on confirm page when the script to be installed is loading. + message: Laddar skriptdata... +msgLoadingDependency: + description: Message shown on confirm page when the requirements are being downloaded. + message: Laddar behövande filer... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Saknar nödvändiga resurser. +msgNamespaceConflict: + description: >- + Message shown when namespace of the new script conflicts with an existent + one. + message: >- + Ett sådant skript är redan installerat. + + Använd antingen ett annat @namn och @namnutrymme här eller redigera det + skriptet i stället. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Ett skript med samma @namn och @namnrymd är redan installerat. +msgNewVersion: + description: >- + Message shown when a new version of script is found by @updateURL, but no + @downloadURL is provided. + message: Ny version hittades. +msgNoUpdate: + description: Message shown when there is no new version of a script. + message: Ingen uppdatering hittades. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Fel vid uppdatering av skript. Klicka för att öppna dem. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Spara eller installera om dessa skript:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: ' (koden är densamma)' +msgSavedBlacklist: + description: Message shown when blacklist are saved. + message: '' + touched: false +msgSavedCustomCSS: + description: Message shown when custom CSS is saved. + message: '' + touched: false +msgSavedEditorOptions: + description: Message shown when editor options are saved. + message: '' + touched: false +msgSavedScriptTemplate: + description: Message shown when custom script template is saved. + message: '' + touched: false +msgScriptUpdated: + description: Notification message for script updates. + message: Skript [$1] är uppdaterad! +msgShowHide: + description: Tooltip or text shown on a toggle that shows/hides stuff + message: Visa/dölj +msgSyncError: + description: Message shown when sync failed. + message: Synkroniseringsfel! +msgSyncInit: + description: Message shown when sync service is initializing. + message: Initierar... +msgSyncInitError: + description: Message shown when sync fails in initialization. + message: Initieringsfel! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Inte auktoriserad ännu. +msgSyncReady: + description: Message shown when sync will start soon. + message: Synkroniseringen startar snart... + touched: false +msgSyncing: + description: Message shown when sync is in progress. + message: Synkronisering pågår... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Syntaxfel? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + Skriptet fanns inte eller matchade inte webbadressen när sidan laddades, + vilket händer på ensidiga applikationswebbplatser som Facebook eller + Instagram som använder falsk navigering. Du kan ladda om fliken för att köra + skriptet. För att fixa skriptet använd @match för hela webbplatsen och + upptäck sedan ändringar med MutationObserver eller window.navigation-API. +msgUpdated: + description: Message shown when a script is updated/reinstalled. + message: Skript uppdaterad. +msgUpdating: + description: Message shown when a new version of script is being fetched. + message: Uppdaterar... +noValues: + description: Label shown when there is no value for current script. + message: Inget lagrat värde +optionEditorWindow: + description: Label for the option in settings + message: Öppna redigeraren från popup i ett nytt fönster +optionEditorWindowHint: + description: >- + Tooltip for optionEditorWindow in case the browser doesn't support + onBoundsChanged + message: >- + Positionen på redigeringsfönstret kommer endast att kommas ihåg när du + ändrar storlek eller sparar +optionEditorWindowSimple: + description: Label for the editor window type + message: Dölj omnibox +optionPopup: + description: Label of the popup menu section in settings. + message: Popup-meny och ikon +optionPopupEnabledFirst: + description: Option to show enabled scripts first in popup. + message: Aktiverade först +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Gruppera inaktiverade skript + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Gruppera efter @körskede +optionPopupHideDisabled: + description: Option to hide disabled scripts in popup. + message: Dölj inaktiverade skript + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Visa inaktiverade skript + touched: false +optionShowEnabledFirst: + description: Option to show enabled scripts first in alphabetical order. + message: Visa aktiverade skript först +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'UI-tema: ' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: automatisk +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: mörkt +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: ljust +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Uppdatera +popupSettings: + description: Item inside the popup's "⋮" menu + message: Popup-inställningar +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Högerklick: popup-inställningar' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Skrivskyddad +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Automatiskt uppdaterade skript är skrivskyddade om du inte inaktiverar + uppdateringar eller tillåter redigering. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Tillåt redigering +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (nästa uppdatering kommer att skriva över dem) +reinstall: + description: Button to reinstall a script + message: Installera om +reloadTab: + description: Label of action to reload the tab + message: Ladda om fliken +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Aktiverad kommer den aktiva fliken att laddas om när ändringar upptäcks och + det här skriptet matchar flikens webbadress. +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false +searchCaseSensitive: + description: Option to perform a case-sensitive search + message: Skiftlägeskänslig +searchUseRegex: + description: Option to perform a regular expression search + message: Använd Regex +sideMenuAbout: + description: 'Side menu: About' + message: Om +sideMenuInstalled: + description: 'Side menu: Installed scripts' + message: Installerade skript +sideMenuSettings: + description: 'Side menu: Settings' + message: Inställningar +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Ladda om sidan utan användarskript +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Du har inaktiverat användarskript för den här sidan. För att köra dem, ladda + om eller navigera på fliken. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Sorteringsordning:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Sluta spåra +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Normal brickfärg +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: Brickfärg när platsen är icke-injicerbar (svartlistad eller stöds inte) +titleSearchHint: + description: Hover title for search icon in dashboard. + message: '' + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Returtangenten lägger till texten i historiken för + autoslutförande + + * Alla villkor är skiftlägesokänsliga + + * Utrymmesseparerade förhållanden kan kombineras + + * Sök efter metadata: "skriptnamn" "beskrivning" + + * Sök efter taggar: #tagg1 #tagg2 + + * Sök efter skriptnamn: name:"skriptnamn" + + * Sök efter skriptkod: code:"kod i skriptet" + + * Negativ sökning: !#tagg2 !name:"oönskat skript" + + * Reguljära uttryck: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? med mellanslag" +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: Spåra externa redigeringar +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: Håll den här sidan öppen för att spåra dina redigeringar i lokal fil +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Uppdatera skript ($1) +updateScript: + description: Button to update one script. + message: Uppdatera +updateScriptsAll: + description: Command/button to update all scripts. + message: Uppdatera alla skript +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Uppdatera nuvarande fliks skript +valueLabelKey: + description: Label for key of a script value. + message: Nyckel (sträng) +valueLabelValue: + description: Label for value of a script value. + message: Värden (serialiserade som JSON) +valueLabelValueAll: + description: Label for input of entire script value storage. + message: Alla värden (serialiserade som JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' +visitWebsite: + description: Label for link to open Violentmonkey website. + message: '' + touched: false diff --git a/src/_locales/th/messages.yml b/src/_locales/th/messages.yml new file mode 100644 index 0000000000..e1373b4bca --- /dev/null +++ b/src/_locales/th/messages.yml @@ -0,0 +1,983 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: '' +buttonCancel: + description: Cancel button on dialog. + message: '' +buttonCheckForUpdates: + description: Button to check a script for updates. + message: '' + touched: false +buttonClose: + description: Button to close window. + message: '' +buttonConfirmInstallation: + description: Button to confirm installation of a script. + message: '' + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: '' + touched: false +buttonDisable: + description: Button to disable a script. + message: '' +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false +buttonEdit: + description: Button to edit a script. + message: '' +buttonEditClickHint: + description: Tooltip for the Edit button in popup. + message: '' +buttonEmptyRecycleBin: + description: Button to empty the recycle bin. + message: '' +buttonEnable: + description: Button to enable a script. + message: '' +buttonExportData: + description: Button to open the data export dialog. + message: '' +buttonFilter: + description: Button to show filters menu. + message: '' + touched: false +buttonHome: + description: Button to open homepage. + message: '' +buttonImportData: + description: Button to choose a file for data import. + message: '' +buttonInstallFromURL: + description: Button to ask for URL of a user script. + message: '' +buttonInstallOptions: + description: Button to show options of installation confirm page. + message: '' + touched: false +buttonNew: + description: Button to create a new script. + message: '' +buttonOK: + description: OK button on dialog. + message: '' +buttonRecycleBin: + description: Button to list scripts in recycle bin. + message: '' +buttonRemove: + description: Button to remove a script. + message: '' +buttonReplace: + description: Button to replace the current match. + message: '' +buttonReplaceAll: + description: Button to replace all matches. + message: '' +buttonReset: + description: Button to reset to default values. + message: '' +buttonResetSettings: + description: Button in settings page to reset all settings + message: '' +buttonRestore: + description: Button to restore a removed script. + message: '' +buttonSave: + description: Button to save modifications of a script. + message: '' +buttonSaveClose: + description: Button to save modifications of a script and then close the editing page. + message: '' +buttonSaved: + description: Button text after saving. + message: '' +buttonShowEditorState: + description: Button to show the list of currently used CodeMirror options. + message: '' +buttonSupport: + description: Button to open support page. + message: '' +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' +buttonUndo: + description: Button to undo removement of a script. + message: '' +buttonUpdate: + description: Button to update a script. + message: '' +buttonUpdateAll: + description: Check all scripts for updates. + message: '' + touched: false +buttonVacuum: + description: Button to vacuum extension data. + message: '' +buttonVacuumed: + description: Message shown when data is vacuumed. + message: '' +buttonVacuuming: + description: Message shown when data vacuum is in progress. + message: '' +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: '' +confirmNotSaved: + description: Confirm message shown when there are unsaved script modifications. + message: '' +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: '' +descBlacklist: + description: Description for the global injection blacklist. + message: '' +descBlacklistNet: + description: Description for the global network blacklist. + message: '' +descCustomCSS: + description: Description of custom CSS section. + message: '' +descEditorOptions: + description: Description of editor options JSON section. + message: '' +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: '' +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: '' +descScriptTemplate: + description: Description of script template section. + message: '' +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: '' +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: '' +disabledScriptsSelector: + description: Label of the option. + message: '' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: '' +editHelpDocumention: + description: Label in the editor help tab for the documentation link. + message: '' +editHelpKeyboard: + description: Label in the editor help tab for the keyboard shortcuts. + message: '' +editHowToHint: + description: The text of the how-to link in the editor header. + message: '' +editLabelMeta: + description: Metadata section in settings tab of script editor. + message: '' +editLabelSettings: + description: Settings section in settings tab of script editor. + message: '' +editLongLine: + description: Shown in the editor in lines that were cut due to being too long + message: '' +editLongLineTooltip: + description: Tooltip shown in the editor in lines that were cut due to being too long + message: '' +editNavCode: + description: Label of code tab in script editor. + message: '' +editNavSettings: + description: Label of settings tab in script editor. + message: '' +editNavValues: + description: Label of values tab in script editor. + message: '' +editValueAll: + description: Button to show/edit the entire script value storage. + message: '' + touched: false +editValueAllHint: + description: Tooltip for the 'all' button. + message: '' +extDescription: + description: Description for this extension, will be displayed in web store + message: '' +extName: + description: Name of this extension. + message: '' +failureReasonBlacklisted: + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: '' +failureReasonNoninjectable: + description: >- + Shown for URLs that cannot be processed (same places as + failureBlacklistedUrl) + message: '' +failureReasonRestarted: + description: Shown in the popup. + message: '' +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: '' +filterAlphabeticalOrder: + description: Label for option to sort scripts in alphabetical order. + message: '' +filterExecutionOrder: + description: Label for option to sort scripts in execution order. + message: '' +filterLastUpdateOrder: + description: Label for option to sort scripts by last update time. + message: '' +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' +filterScopeAll: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterScopeCode: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterScopeName: + description: Option in dashboard's search scope filter. + message: '' + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: '' +genericError: + description: Label for generic error. + message: '' +genericOff: + description: To indicate something is turned off or disabled, similar to "no". + message: '' +genericOn: + description: To indicate something is turned on or enabled, similar to "yes". + message: '' +genericUseGlobal: + description: To indicate some per-script option will use its analog in global settings. + message: '' +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: '' +headerRecycleBin: + description: Text shown in the header when scripts in recycle bin are listed. + message: '' +helpForLocalFile: + description: Label of the checkbox. + message: '' +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: '' +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' +hintInputURL: + description: Hint for a prompt box to input URL of a user script. + message: '' +hintRecycleBin: + description: Hint for recycle bin. + message: '' +hintUseDownloadURL: + description: Shown as a place holder for @updateURL when it is not assigned + message: '' +hintVacuum: + description: Hint for vacuuming data. + message: '' +install: + description: Label for button to install a script. + message: '' +installFrom: + description: Label for button to install script from a userscript site. + message: '' +installOptionClose: + description: Option to close confirm window after installation. + message: '' + touched: false +installOptionTrackTooltip: + description: >- + Tooltip in Firefox 68+ for the option to track the loading local file before + window is closed. + message: '' +labelAbout: + description: Label shown on top of the about page. + message: '' + touched: false +labelAdvanced: + description: Label for button to show advanced settings. + message: '' +labelAllowUpdate: + description: Option to allow checking updates for a script. + message: '' +labelAuthor: + description: Label of author shown in the details of a script. + message: '' +labelAutoReloadCurrentTab: + description: Option to reload current tab after a script is switched on or off from menu. + message: '' +labelAutoReloadCurrentTabDisabled: + description: >- + Tooltip in menu after Violentmonkey is switched off if auto-reload is + disabled. + message: '' + touched: false +labelAutoUpdate: + description: Option to allow automatically checking scripts for updates + message: '' +labelBackup: + description: Label of the import/export section in settings. + message: '' + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: '' +labelBadge: + description: Label for option to show number on badge. + message: '' +labelBadgeColors: + description: Label for option group to set badge colors. + message: '' +labelBadgeNone: + description: Option to display nothing on badge. + message: '' +labelBadgeTotal: + description: Option to display total number of running scripts on badge. + message: '' +labelBadgeUnique: + description: Option to display number of unique running scripts on badge. + message: '' +labelBlacklist: + description: Label for global blacklist settings in security section. + message: '' +labelContributors: + description: Label for link to contributors. + message: '' +labelCurrentLang: + description: Label of current language. + message: '' +labelCustomCSS: + description: Label for custom CSS section. + message: '' +labelDataExport: + description: Section title of data export. + message: '' + touched: false +labelDataImport: + description: Section title of data import. + message: '' + touched: false +labelDonate: + description: Label of link to donate page. + message: '' + touched: false +labelDownloadURL: + description: Label of script @downloadURL in custom meta data. + message: '' +labelEditValue: + description: Label shown in the panel to edit a script value. + message: '' +labelEditValueAll: + description: Label shown in the panel to edit the entire script value storage. + message: '' +labelEditor: + description: Label for Editor settings + message: '' +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: '' +labelExclude: + description: Label of @exclude rules. + message: '' +labelExcludeMatch: + description: Label of @exclude-match rules. + message: '' +labelExportScriptData: + description: Option to export script data along with scripts. + message: '' +labelExposeStatus: + description: Option in advanced settings. + message: '' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: '' +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: '' +labelFeedback: + description: Label of link to feedback page. + message: '' +labelGeneral: + description: Label for general settings. + message: '' +labelHelpTranslate: + description: Label for link to localization guide in about tab + message: '' +labelHomepage: + description: Label for home page in about tab. + message: '' +labelHomepageURL: + description: Label of script @homepageURL in custom meta data. + message: '' +labelIconURL: + description: Label for the input. + message: '' +labelImportScriptData: + description: Option to import script data along with scripts. + message: '' +labelImportSettings: + description: Label for option to import settings from zip file. + message: '' +labelInclude: + description: Label of @include rules. + message: '' +labelInjectionMode: + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: '' +labelInstall: + description: Shown in the title of the confirm page while trying to install a script. + message: '' +labelKeepOriginal: + description: Option to keep the original match or ignore rules. + message: '' +labelLastUpdatedAt: + description: Label shown on last updated time. + message: '' +labelLineNumber: + description: Label for line number jumper. + message: '' +labelMatch: + description: Label of @match rules. + message: '' +labelName: + description: Label of script name. + message: '' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: '' +labelNoName: + description: Text as the name of a script when no @name is assigned. + message: '' +labelNoSearchScripts: + description: Message shown when no script is found in search results. + message: '' +labelNotifyThisUpdated: + description: >- + A per-script option in editor to enable notification when this script is + updated. The text follows "Allow update" checkbox option so it's like a + continuation of the phrase. + message: '' +labelNotifyUpdates: + description: Option to show notification when script is updated. + message: '' +labelNotifyUpdatesGlobal: + description: Option to prioritize global notification option over script's setting. + message: '' +labelPrivacyPolicy: + description: Label of link to privacy policy + message: '' +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: '' +labelRelated: + description: Label of related links. + message: '' +labelRemovedAt: + description: Label for the time when the script is removed. + message: '' +labelReplace: + description: Label for replace input in search box. + message: '' +labelRunAt: + description: Label of script @run-at properties in custom meta data. + message: '' +labelRunAtDefault: + description: Shown when custom @run-at is not assigned. + message: '' +labelScriptTemplate: + description: Label for custom script template. + message: '' +labelSearch: + description: Label for search input in search box. + message: '' +labelSearchScript: + description: Placeholder for script search box. + message: '' +labelSettings: + description: Label shown on the top of settings page + message: '' +labelShowOrder: + description: Label for option in dashboard -> script list + message: '' +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' +labelSync: + description: Label for sync options. + message: '' +labelSyncAnonymous: + description: Label for using anonymous account. + message: '' +labelSyncAuthorize: + description: Label for button to authorize a service. + message: '' +labelSyncAuthorizing: + description: Label for button when authorization is in progress. + message: '' +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' +labelSyncDisabled: + description: Label for option to disable sync service. + message: '' +labelSyncPassword: + description: Label for input to hold password. + message: '' +labelSyncReauthorize: + description: Option to reauthorize sync service when expired. + message: '' + touched: false +labelSyncRevoke: + description: Label for button to revoke authorization for a service. + message: '' +labelSyncScriptStatus: + description: Label for option to sync script status. + message: '' +labelSyncServerUrl: + description: Label for input to hold server URL. + message: '' +labelSyncService: + description: Label for sync service select. + message: '' +labelSyncUsername: + description: Label for input to hold username. + message: '' +labelTags: + description: Label for custom tags. + message: '' +labelTheme: + description: Label for the visual theme option. + message: '' +labelTranslator: + description: Label of translator. + message: '' + touched: false +labelUpdateURL: + description: Label of script @updateURL in custom meta data. + message: '' +labelViewSingleColumn: + description: >- + Label for option in dashboard script list to show the scripts in single + column. + message: '' +labelViewTable: + description: Label for option in dashboard script list to show the scripts as a table. + message: '' +labelWidth: + description: Width. + message: '' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: '' +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: '' +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: '' +lastSync: + description: Label for last sync timestamp. + message: '' +learnBlacklist: + description: Refers to a link to introduce blacklist patterns. + message: '' +learnInjectionMode: + description: Refers to a link to introduce injection modes. + message: '' +menuCommands: + description: Menu item to list script commands. + message: '' + touched: false +menuDashboard: + description: Label for menu item to open dashboard. + message: '' +menuExclude: + description: >- + Shown in popup menu after clicking script's "..." dropdown so try to keep + the label short. + message: '' +menuExcludeHint: + description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. + message: '' +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: '' +menuFindScripts: + description: Menu item to find scripts for a site. + message: '' +menuInjectionFailed: + description: Injection error. + message: '' +menuInjectionFailedFix: + description: Injection error fix, shown in case the default mode is "page". + message: '' +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: '' +menuMatchedFrameScripts: + description: Label for menu listing matching scripts in sub-frames. + message: '' +menuMatchedScripts: + description: Label for menu listing matched scripts. + message: '' +menuNewScript: + description: Menu item to create a new script. + message: '' +menuScriptDisabled: + description: Menu item showing the status of Violentmonkey, when disabled. + message: '' +menuScriptEnabled: + description: Menu item showing the status of Violentmonkey, when enabled. + message: '' +msgCheckingForUpdate: + description: Message shown when a script is being checked for updates by version numbers. + message: '' +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: '' +msgErrorFetchingResource: + description: >- + Message shown when Violentmonkey fails fetching a resource/require/icon of + the script. + message: '' +msgErrorFetchingScript: + description: Message shown when Violentmonkey fails fetching a new version of the script. + message: '' +msgErrorFetchingUpdateInfo: + description: Message shown when Violentmonkey fails fetching version data of the script. + message: '' +msgErrorLoadingData: + description: >- + Message shown on confirm page when the script to be installed cannot be + loaded. + message: '' +msgErrorLoadingDependency: + description: Message shown when not all requirements are loaded successfully. + message: '' +msgImported: + description: >- + Message shown after import. There is an argument referring to the count of + scripts imported. + message: '' +msgIncognitoChanges: + description: >- + Message shown in popup and installation tab when opened in an incognito + window. + message: '' +msgInstalled: + description: Message shown when a script is installed. + message: '' +msgInvalidScript: + description: Message shown when script is invalid. + message: '' +msgLoadedData: + description: >- + Message shown in the confirm page when a javascript file to be installed is + loaded. + message: '' + touched: false +msgLoading: + description: Message shown in the options page before script list is loaded. + message: '' + touched: false +msgLoadingData: + description: Message shown on confirm page when the script to be installed is loading. + message: '' +msgLoadingDependency: + description: Message shown on confirm page when the requirements are being downloaded. + message: '' +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: '' +msgNamespaceConflict: + description: >- + Message shown when namespace of the new script conflicts with an existent + one. + message: '' +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: '' +msgNewVersion: + description: >- + Message shown when a new version of script is found by @updateURL, but no + @downloadURL is provided. + message: '' +msgNoUpdate: + description: Message shown when there is no new version of a script. + message: '' +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: '' +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: '' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: '' +msgSavedBlacklist: + description: Message shown when blacklist are saved. + message: '' + touched: false +msgSavedCustomCSS: + description: Message shown when custom CSS is saved. + message: '' + touched: false +msgSavedEditorOptions: + description: Message shown when editor options are saved. + message: '' + touched: false +msgSavedScriptTemplate: + description: Message shown when custom script template is saved. + message: '' + touched: false +msgScriptUpdated: + description: Notification message for script updates. + message: '' +msgShowHide: + description: Tooltip or text shown on a toggle that shows/hides stuff + message: '' +msgSyncError: + description: Message shown when sync failed. + message: '' +msgSyncInit: + description: Message shown when sync service is initializing. + message: '' +msgSyncInitError: + description: Message shown when sync fails in initialization. + message: '' +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: '' +msgSyncReady: + description: Message shown when sync will start soon. + message: '' + touched: false +msgSyncing: + description: Message shown when sync is in progress. + message: '' +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: '' +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: '' +msgUpdated: + description: Message shown when a script is updated/reinstalled. + message: '' +msgUpdating: + description: Message shown when a new version of script is being fetched. + message: '' +noValues: + description: Label shown when there is no value for current script. + message: '' +optionEditorWindow: + description: Label for the option in settings + message: '' +optionEditorWindowHint: + description: >- + Tooltip for optionEditorWindow in case the browser doesn't support + onBoundsChanged + message: '' +optionEditorWindowSimple: + description: Label for the editor window type + message: '' +optionPopup: + description: Label of the popup menu section in settings. + message: '' +optionPopupEnabledFirst: + description: Option to show enabled scripts first in popup. + message: '' +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: '' + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: '' +optionPopupHideDisabled: + description: Option to hide disabled scripts in popup. + message: '' + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: '' + touched: false +optionShowEnabledFirst: + description: Option to show enabled scripts first in alphabetical order. + message: '' +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: '' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: '' +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: '' +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: '' +popupSettings: + description: Item inside the popup's "⋮" menu + message: '' +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: '' +readonlyNote: + description: Warning when trying to type in the editor. + message: '' +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: '' +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: '' +reinstall: + description: Button to reinstall a script + message: '' +reloadTab: + description: Label of action to reload the tab + message: '' +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: '' +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false +searchCaseSensitive: + description: Option to perform a case-sensitive search + message: '' +searchUseRegex: + description: Option to perform a regular expression search + message: '' +sideMenuAbout: + description: 'Side menu: About' + message: '' +sideMenuInstalled: + description: 'Side menu: Installed scripts' + message: '' +sideMenuSettings: + description: 'Side menu: Settings' + message: '' +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: '' +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: '' +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: '' +titleBadgeColor: + description: Tooltip for option to set badge color. + message: '' +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: '' +titleSearchHint: + description: Hover title for search icon in dashboard. + message: '' + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: '' +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: '' +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: '' +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: '' +updateScript: + description: Button to update one script. + message: '' +updateScriptsAll: + description: Command/button to update all scripts. + message: '' +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: '' +valueLabelKey: + description: Label for key of a script value. + message: '' +valueLabelValue: + description: Label for value of a script value. + message: '' +valueLabelValueAll: + description: Label for input of entire script value storage. + message: '' +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' +visitWebsite: + description: Label for link to open Violentmonkey website. + message: '' + touched: false diff --git a/src/_locales/tr/messages.yml b/src/_locales/tr/messages.yml index b7f10374e6..6259528ae6 100644 --- a/src/_locales/tr/messages.yml +++ b/src/_locales/tr/messages.yml @@ -1,27 +1,45 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Uygula buttonCancel: description: Cancel button on dialog. message: İptal +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Güncellemeleri denetle + touched: false buttonClose: description: Button to close window. message: Kapat buttonConfirmInstallation: description: Button to confirm installation of a script. - message: Yüklemeyi onayla + message: Kurulumu onayla + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Yeniden kurulumu onayla + touched: false buttonDisable: description: Button to disable a script. - message: Deaktif + message: Devre dışı bırak +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false buttonEdit: description: Button to edit a script. message: Düzenle buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: '' + message: >- + Ayrıca betik adına sağ tıklayabilir, Ctrl tuşuna basarak tıklayabilir veya + tekerlekle tıklayabilirsiniz. buttonEmptyRecycleBin: description: Button to empty the recycle bin. - message: Geri dönüşüm kutusunu boşalt! + message: Geri dönüşüm kutusunu hemen boşalt! buttonEnable: description: Button to enable a script. - message: Aktif + message: Etkinleştir buttonExportData: description: Button to open the data export dialog. message: Zip olarak dışarı aktar @@ -31,13 +49,13 @@ buttonFilter: touched: false buttonHome: description: Button to open homepage. - message: Anasayfa + message: Ana sayfa buttonImportData: description: Button to choose a file for data import. - message: Veriyi içeri zip olarak aktar + message: Zip dosyasından içeri aktar buttonInstallFromURL: description: Button to ask for URL of a user script. - message: URL üzerinden yükle + message: URL'den yükle buttonInstallOptions: description: Button to show options of installation confirm page. message: Ayarlar @@ -63,64 +81,127 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Sıfırla +buttonResetSettings: + description: Button in settings page to reset all settings + message: Ayarları sıfırla buttonRestore: description: Button to restore a removed script. - message: Geri Yükle + message: Geri yükle buttonSave: description: Button to save modifications of a script. message: Kaydet buttonSaveClose: description: Button to save modifications of a script and then close the editing page. - message: Kaydet & Kapat + message: Kaydet ve kapat +buttonSaved: + description: Button text after saving. + message: Kaydedildi buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Düzenleyici durumunu göster buttonSupport: description: Button to open support page. - message: Destek Sayfası + message: Destek sayfası +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. - message: Geri Al - touched: false + message: Geri al buttonUpdate: - description: Check a script for updates. - message: Güncellemeleri denetle + description: Button to update a script. + message: Güncelle buttonUpdateAll: description: Check all scripts for updates. - message: Yeni güncellemeleri kontrol et + message: Güncellemeleri denetle + touched: false buttonVacuum: description: Button to vacuum extension data. - message: Verileri al + message: Veritabanını vakumla buttonVacuumed: description: Message shown when data is vacuumed. - message: Veriler içeri aktarıldı + message: Veriler vakumlandı buttonVacuuming: description: Message shown when data vacuum is in progress. - message: Veri içeri aktarılıyor... + message: Veriler vakumlanıyor... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Bu betik için otomatik güncelleme devre dışı! + Yine de güncellemek isterseniz Tamam'a tıklayın. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- - Yapılan ayarlar kaydedilemedi! - Gözardı etmek için Tamam tıklayın veya İptal ile ayarları düzenleyin. + Değişiklikler kaydedilmedi! + Göz ardı etmek için Tamam'a, düzenlemeye devam etmek için İptal'e tıklayın. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Veritabanında yapılan tüm değişiklikleri geri al (içe aktarma, güncelleme, + düzenleme, özelleştirme) descBlacklist: - description: HTML Description for the global blacklist. - message: Listede bulunan URL girişleri script tarafından eklenmeyecek. + description: Description for the global injection blacklist. + message: Enjeksiyon kara listesi (Eşleşen sitelerdeki betikler çalışmayacaktır) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Ağ kara listesi (Betikler eşleşen sitelere ve o sitelerin çerezlerine + bağlanmayacaktır) descCustomCSS: description: Description of custom CSS section. message: >- - Seçenekler sayfası ve script yükleme sayfası için geçerli CSS. Eğer ne - yaptığınızdan emin değilseniz, lütfen düzenlemeyiniz. + Seçenekler sayfası ve betik yükleme sayfası için özelleştirilmiş CSS. Ne işe + yaradığını bilmiyorsanız lütfen burayı düzenlemeyin. descEditorOptions: description: Description of editor options JSON section. message: >- - CodeMirror ve JSON eklentileri için {"indentUnit":2, - "smartIndent":true}1 gibi özelleştirilmiş ayarlar, ancak bazıları - Violentmonkey'de çalışmayabililir. Tüm listeyi görmek için {"indentUnit":2, + "smartIndent":true} gibi özelleştirilmiş ayarlar. Bazılarının + Violentmonkey'de çalışmayabileceğini unutmayın. Tüm listeyi görmek için tıklayın + rel="noopener noreferrer">tıklayın. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Boolean operatörlerini değelerine çift tıklayarak değiştirebilirsiniz: + true = etkin, false = devre dışı. + Delay, Interval, Rate, + Time ile biten sayısal seçeneklerin değerleri milisaniye + cinsindendir. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Standart dışı seçenekler: autocompleteOnTyping, yazmaya + başladığınızda otomatik tamamlama ipuçlarının gösterileceği milisaniye + cinsinden gecikme süresidir (0 = devre dışı). + killTrailingSpaceOnSave kayıt sırasında satır sonlarındaki + boşlukları otomatik olarak kaldırır. showTrailingSpace satır + sonlarındaki boşlukları nokta şeklinde gösterir. +descScriptTemplate: + description: Description of script template section. + message: >- + Desteklenen değerler: <{{name}}>, <{{url}}>, <{{date}}>, MomentJS uyumlu + biçimde <{{date:format}}> (örn. <{{date:YYYY-AA-GG}}>). +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: grupla +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: gizle +disabledScriptsSelector: + description: Label of the option. + message: 'Devre dışı betikler:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: göster editHelpDocumention: description: Label in the editor help tab for the documentation link. - message: 'Userscript metaveri blok ve GM API dökümanları:' + message: 'Kullanıcı betiği meta veri bloku ve GM API belgeleri:' editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. message: 'Klavye kısayolları:' @@ -129,16 +210,22 @@ editHowToHint: message: Başka düzenleyici kullan? editLabelMeta: description: Metadata section in settings tab of script editor. - message: Geçerli metadata + message: Özel meta veriler editLabelSettings: description: Settings section in settings tab of script editor. - message: Script ayarları + message: Betik ayarları editLongLine: description: Shown in the editor in lines that were cut due to being too long - message: '' + message: Satır çok uzun editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long - message: '' + message: >- + Bu satır çok uzun olduğundan dolayı düzenleme yaparken yaşanabilecek + gecikmeleri önlemek için daraltıldı. + + Gelişmiş ayarlar bölümünden bu limiti değiştirebilirsiniz. Örneğin: + + "maxDisplayLength": 20000 editNavCode: description: Label of code tab in script editor. message: Kod @@ -151,90 +238,137 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: '' + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: İptal -editValueSave: - description: Button to save modification of a script value. - message: Kaydet + message: Betiğin tüm değerler deposunu göster/düzenle extDescription: - description: 'Description for this extension, will be displayed in web store' - message: Bir çok tarayıcı destekleyen açık kaynak userscript yöneticisi + description: Description for this extension, will be displayed in web store + message: >- + Birçok tarayıcıyı destekleyen açık kaynak kodlu bir kullanıcı betiği + yöneticisi extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' - message: Violentmonkey ayarlarında Kara listeye eklenmiş + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: Violentmonkey ayarlarında kara listede failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) - message: |- - Violentmonkey bu sayfada userscript çalıştıramıyor - (örnek: tarayıcı arayüzü veya eklenti sayfası) + message: >- + Violentmonkey bu sayfada kullanıcı betiklerini çalıştıramıyor + + (olağan şüpheliler: tarayıcı arayüzü, uzantı sayfası, ilkeler aracılığıyla + engelleme, Opera'da arama motoru sayfası) +failureReasonRestarted: + description: Shown in the popup. + message: >- + Violentmonkey yeniden başlatıldı. Kullanıcı betiklerini çalıştırmak için + lütfen sekmeyi yenileyin. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Yerel bir dosyayı yüklemenin ve düzenlemeleri izlemenin birkaç yöntem + vardır: + + <1>Dosyayı sürükleyip açık bir Violentmonkey sayfasına veya açılır + penceresine bırakabilirsiniz (bu sayfa dahil). <2>Dosya için yerel bir HTTP + sunucusu kurup http://localhost üzerinden açabilirsiniz. + <3>chrome://extensions sayfasındaki Violentmonkey ayrıntılarında "Dosya + URL'lerine erişime izin ver" seçeneğini etkinleştirebilirsiniz. Ancak bu + tehlikelidir çünkü tüm kullanıcı betikleri tüm yerel dosyaları okuyabilir. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: alfabetik sıra + message: alfabetik filterExecutionOrder: description: Label for option to sort scripts in execution order. message: çalıştırma sırası filterLastUpdateOrder: description: Label for option to sort scripts by last update time. - message: son güncelleme zamanı + message: son güncelleme zamanına göre +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: son ziyaret +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: Bu betiğin hedeflediği herhangi bir siteyi ziyaret ettiğiniz son tarih. filterScopeAll: description: Option in dashboard's search scope filter. message: Tümü + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Kod + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: İsim + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: boyuta göre genericError: description: Label for generic error. - message: '' + message: Hata genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: kapalı genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: açık genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. - message: genel ayarları kullan + message: global ayarı kullan +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + "#" şablonları yalnızca siteyi ilk açtığınızda veya sekmeyi yenilediğinizde + çalışır: $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Geri Dönüşüm Kutusu +helpForLocalFile: + description: Label of the checkbox. + message: Sürüklenip bırakılan yerel betikler için talimatları göster +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: eşleşen $1 betik +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. - message: 'URL Girdisi:' + message: 'URL''yi girin:' hintRecycleBin: description: Hint for recycle bin. - message: Kaldırılan scriptler 7 gün boyunca burada listelenecek ve tutulacak. + message: Kaldırılan betikler 7 gün boyunca burada listelenecek ve tutulacaktır. hintUseDownloadURL: description: Shown as a place holder for @updateURL when it is not assigned - message: '@downloadURL Kullan' + message: '@downloadURL kullan' hintVacuum: description: Hint for vacuuming data. - message: Fazlalığı atın ve eksik kaynakları önbellekte yeniden yüklemeyi deneyin. + message: Fazlalıkları at ve eksik kaynakları önbelleğe geri yüklemeyi dene. +install: + description: Label for button to install a script. + message: Yükle installFrom: description: Label for button to install script from a userscript site. - message: $1 tarafından yüklendi + message: $1 sitesinden yükle installOptionClose: description: Option to close confirm window after installation. message: Yüklemeden sonra kapat -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Bu pencere kapanmadan önce yerel dosyayı takip et + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before window is closed. - message: Firefox 68 ve sonrası için kaynak sekmesi açık tutulmalıdır. + message: Firefox 68 ve sonrasında kaynak dosyası sekmesi açık tutulmalıdır. labelAbout: description: Label shown on top of the about page. message: Violentmonkey hakkında @@ -244,35 +378,44 @@ labelAdvanced: message: Gelişmiş labelAllowUpdate: description: Option to allow checking updates for a script. - message: Güncellemelere izin ver + message: Güncellemeye izin ver labelAuthor: description: Label of author shown in the details of a script. - message: 'Yazar: ' + message: 'Geliştiren: ' labelAutoReloadCurrentTab: description: Option to reload current tab after a script is switched on or off from menu. - message: >- - Bir script dosyasını menüden açıp / kapattıktan sonra geçerli sekmeyi - yeniden yükle + message: Bir betiği menüden kapattığımda/açtığımda geçerli sekmeyi yenile labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. - message: '' + message: Geçerli betikler sayfa yenilenene kadar çalışmaya devam edecektir + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: 'Script güncellemelerini $1 günde bir kontrol et, kapatmak için 0 seçin' + message: Betik güncellemelerini $1 günde bir kontrol et. Kapatmak isterseniz 0 yazın +labelBackup: + description: Label of the import/export section in settings. + message: Yedekle + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Yedekleme ve Bakım labelBadge: description: Label for option to show number on badge. - message: 'Simge üzerinde göster:' + message: 'Rozet:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Rozet renkleri: ' labelBadgeNone: description: Option to display nothing on badge. message: hiçbir şey labelBadgeTotal: description: Option to display total number of running scripts on badge. - message: çalışan script sayısı + message: çalışan betik sayısı labelBadgeUnique: description: Option to display number of unique running scripts on badge. - message: çalışan benzersiz script sayısı + message: çalışan eşsiz betik sayısı labelBlacklist: description: Label for global blacklist settings in security section. message: Kara Liste @@ -281,31 +424,37 @@ labelContributors: message: Katkıda bulunanlar labelCurrentLang: description: Label of current language. - message: 'Geçerli Dil: ' + message: 'Geçerli dil: ' labelCustomCSS: description: Label for custom CSS section. - message: Geçerli Stil + message: Özel Stil labelDataExport: description: Section title of data export. message: Veri dışarı aktar + touched: false labelDataImport: description: Section title of data import. message: Veri içeri aktar + touched: false labelDonate: description: Label of link to donate page. message: Bağış yap + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. - message: 'İndirme URL:' + message: 'İndirme URL''si:' labelEditValue: description: Label shown in the panel to edit a script value. - message: Script değerini düzenle + message: Betik değerini düzenliyorsunuz labelEditValueAll: description: Label shown in the panel to edit the entire script value storage. - message: '' + message: Betik değer deposunu düzenliyorsunuz labelEditor: description: Label for Editor settings message: Düzenleyici +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: yalnızca etkin betikler labelExclude: description: Label of @exclude rules. message: '@exclude kuralları' @@ -314,31 +463,42 @@ labelExcludeMatch: message: '@exclude-match kuralları' labelExportScriptData: description: Option to export script data along with scripts. - message: Script verilerini dışarı aktar + message: Betik verilerini dışarı aktar labelExposeStatus: description: Option in advanced settings. - message: 'Yüklü sürümü userscript katalog sitesinde göster: $1' + message: 'Yüklü sürümü kullanıcı betiği katalog sitelerine bildir: $1' +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Firefox'ta alternatif $1 modu +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + betikleri için Firefox 59'dan beri kullanılabilen + alternatif bir enjeksiyon yöntemi. Varsayılan moddan daha hızlıdır. "Eş + zamanlı sayfa modu" ile benzer şekilde bellek tüketimini artırır. Bu + nedenle, betikleriniz bu seçenek olmadan düzgün çalışıyorsa devre dışı + bırakmayı düşünebilirsiniz. labelFeedback: description: Label of link to feedback page. message: Geri bildirim -labelFilterSort: - description: Label for sort filter. - message: $1 ile sırala labelGeneral: description: Label for general settings. message: Genel labelHelpTranslate: description: Label for link to localization guide in about tab - message: Çevirmeye yardım edin + message: Çeviriye yardımcı olun labelHomepage: description: Label for home page in about tab. - message: Anasayfa + message: Ana sayfa labelHomepageURL: description: Label of script @homepageURL in custom meta data. - message: 'Anasayfa URL:' + message: 'Ana sayfa URL''si:' +labelIconURL: + description: Label for the input. + message: 'Simge URL''si:' labelImportScriptData: description: Option to import script data along with scripts. - message: '' + message: Betik verilerini içeri aktar labelImportSettings: description: Label for option to import settings from zip file. message: Ayarları içeri aktar @@ -346,53 +506,59 @@ labelInclude: description: Label of @include rules. message: '@include kuralları' labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Varsayılan enjeksiyon modu:' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Enjeksiyon modu: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. - message: Script yükleniyor + message: Betik yükleniyor labelKeepOriginal: description: Option to keep the original match or ignore rules. - message: Orijinali koru + message: Orijinalini koru labelLastUpdatedAt: description: Label shown on last updated time. - message: 'Son güncelleme $1 ' + message: 'Son güncelleme: $1 ' labelLineNumber: description: Label for line number jumper. - message: 'Satır no.: ' + message: 'Satır no: ' labelMatch: description: Label of @match rules. message: '@match kuralları' labelName: description: Label of script name. message: 'İsim:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Frame içinde çalıştır:' labelNoName: description: Text as the name of a script when no @name is assigned. message: İsimsiz - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. - message: Script bulunamadı. + message: Hiç betik bulunamadı. labelNotifyThisUpdated: description: >- A per-script option in editor to enable notification when this script is updated. The text follows "Allow update" checkbox option so it's like a continuation of the phrase. - message: ', ve bildirim göster:' + message: ' ve bildir:' labelNotifyUpdates: description: Option to show notification when script is updated. - message: Script güncellemelerini bildir + message: Betik güncellemelerini bildir labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: >- - script bazında bildirim ayarını görmezden gel (düzenleyicideki "ayarlar" + betiklerin özel bildirim ayarlarını görmezden gel (düzenleyicideki "ayarlar" sekmesindeki) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Açılan listede scriptleri $1 göster labelPrivacyPolicy: description: Label of link to privacy policy - message: Gizlilik Sözleşmesi + message: Gizlilik Politikası +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Betik yeniden yükleniyor labelRelated: description: Label of related links. message: 'İlgili bağlantılar: ' @@ -404,160 +570,219 @@ labelReplace: message: 'Değiştir: ' labelRunAt: description: Label of script @run-at properties in custom meta data. - message: 'Çalışma yeri: ' + message: 'Çalışacağı an: ' labelRunAtDefault: description: Shown when custom @run-at is not assigned. message: (Varsayılan) labelScriptTemplate: description: Label for custom script template. - message: Özelleştirilmiş Script Şablonu + message: Özel Betik Şablonu labelSearch: description: Label for search input in search box. - message: 'Arama: ' + message: 'Ara: ' labelSearchScript: description: Placeholder for script search box. - message: Script Aranıyor... + message: Betiklerde ara... labelSettings: description: Label shown on the top of settings page message: Ayarlar +labelShowOrder: + description: Label for option in dashboard -> script list + message: Çalıştırma sıralarını göster +labelShowVisited: + description: Label for option in dashboard -> script list + message: Son ziyaret tarihini göster labelSync: description: Label for sync options. - message: Eşitle + message: Eşitleme labelSyncAnonymous: description: Label for using anonymous account. message: Anonim hesap kullan labelSyncAuthorize: description: Label for button to authorize a service. - message: Yetkilendirme + message: Yetkilendir labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Yetkilendiriliyor +labelSyncAutomatically: + description: Label for option to sync automatically. + message: Otomatik olarak eşitle labelSyncDisabled: description: Label for option to disable sync service. - message: Yok + message: Hiçbiri labelSyncPassword: description: Label for input to hold password. - message: 'Parola:' + message: 'Parola: ' labelSyncReauthorize: description: Option to reauthorize sync service when expired. message: '' touched: false labelSyncRevoke: description: Label for button to revoke authorization for a service. - message: İptal edildi + message: İptal et labelSyncScriptStatus: description: Label for option to sync script status. - message: Eşitleme script durumu + message: Betik durumunu eşitle labelSyncServerUrl: description: Label for input to hold server URL. - message: 'Sunucu Adresi:' + message: 'Sunucu URL''si: ' labelSyncService: description: Label for sync service select. - message: Eşitleyen + message: Eşitleme hizmeti labelSyncUsername: description: Label for input to hold username. - message: 'Kullanıcı adı:' + message: 'Kullanıcı adı: ' +labelTags: + description: Label for custom tags. + message: 'Etiketler (Boşlukla ayırın):' +labelTheme: + description: Label for the visual theme option. + message: 'Tema: ' labelTranslator: description: Label of translator. message: 'Çeviren: ' touched: false labelUpdateURL: description: Label of script @updateURL in custom meta data. - message: 'Güncelleme URL:' + message: 'Güncelleme URL''si:' labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: Tek sütun labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. - message: '' + message: Tablo görünümü +labelWidth: + description: Width. + message: 'Genişlik:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Eş zamanlı $1 modu +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Sayfa yüklenmeden önce çalışması gereken bir betiğiniz varsa ve şu anda bu + betik çok geç çalışıyorsa bu seçeneği etkinleştirin. Tampermonkey'deki + "anında enjeksiyon" moduna benzer şekilde, bu seçenek de artık kullanılmayan + eş zamanlı XHR teknolojisini kullanmaktadır. Bu sebeple Chrome/Chromium + geliştirici araçları konsolunda uyarılar görebilirsiniz. Ancak yan etkiler + bu durumda önemsiz olduğu için bu hataları görmezden gelebilirsiniz. + Uyarılarıdan birine sağ tıklayarak bu uyarıları tamamen gizleyebilirsiniz. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (gizli pencereler ve çerezleri devre dışı bırakılmış siteler hariç) lastSync: description: Label for last sync timestamp. - message: Son eşitleme zamanı $1 + message: 'Son eşitleme: $1' learnBlacklist: description: Refers to a link to introduce blacklist patterns. - message: Kara liste şablonları hakkında daha fazla öğren. + message: Kara liste şablonları hakkında bilgi alın. learnInjectionMode: description: Refers to a link to introduce injection modes. - message: Enjeksiyon modları hakkında daha fazlasını öğren. + message: Enjeksiyon modları hakkında bilgi alın. menuCommands: description: Menu item to list script commands. message: Script komutları touched: false menuDashboard: description: Label for menu item to open dashboard. - message: Paneli Aç + message: Paneli aç menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: '' + message: Dışla... menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. - message: '' + message: >- + Genel ayarlardan bu seçeneği açarsanız geçerli sekme otomatik olarak + yenilenecektir. + + Değişiklikleri diğer sekmelere de uygulamak için onları manuel olarak + yenilemelisiniz. + + Daha esnek ayarlar için düzenleyicinin "Ayarlar" sekmesini + kullanabilirsiniz. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Geri bildirim menuFindScripts: description: Menu item to find scripts for a site. - message: Bu siteye özel scriptleri bul + message: Bu siteye özel betikleri bul menuInjectionFailed: description: Injection error. - message: '' + message: Bazı betikler enjekte edilemedi. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' - message: '' + description: Injection error fix, shown in case the default mode is "page". + message: '"Otomatik" modda yeniden dene' +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Eşleşen devre dışı betikler menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. - message: Sub-frame özel scriptler + message: Alt çerçeveye özel betikler menuMatchedScripts: description: Label for menu listing matched scripts. - message: Seçilen scriptler + message: Eşleşen betikler menuNewScript: description: Menu item to create a new script. - message: Yeni script oluştur + message: Yeni betik oluştur menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' - message: Scriptler deaktif edildi + description: Menu item showing the status of Violentmonkey, when disabled. + message: Betikler devre dışı menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' - message: Scriptler aktif edildi + description: Menu item showing the status of Violentmonkey, when enabled. + message: Betikler etkin msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Güncellemeler denetleniyor... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + MomentJS dokümantasyonunu açmak için tıklayın. Kabul edilen token'lar: $1. + Metinleri korumak için [köşeli parantez] kullanabilirsiniz. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of the script. - message: Kaynak güncelleme hatası! + message: Kaynak getirilirken hata oluştu! msgErrorFetchingScript: description: Message shown when Violentmonkey fails fetching a new version of the script. - message: Script güncelleme hatası! + message: Betik getirilirken hata oluştu! msgErrorFetchingUpdateInfo: description: Message shown when Violentmonkey fails fetching version data of the script. - message: Bilgileri güncellerken hata oluştu. + message: Güncelleme bilgileri alınamadı. msgErrorLoadingData: description: >- Message shown on confirm page when the script to be installed cannot be loaded. - message: Script verisi yüklenirken hata oluştu. + message: Betik verileri yüklenemedi. msgErrorLoadingDependency: description: Message shown when not all requirements are loaded successfully. - message: Script yüklemesinde veriler tam yüklenmedi. + message: Bağımlılıklar yüklenirken hata oluştu. msgImported: description: >- Message shown after import. There is an argument referring to the count of scripts imported. - message: $1 adet içeriye aktarıldı. + message: $1 kayıt içeri aktarıldı. msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito window. - message: Gizli modda yaptığınız değişiklikler de asıl profilinize etki eder. + message: Gizli modda yaptığınız değişiklikler de ana profilinize etki eder. msgInstalled: description: Message shown when a script is installed. - message: Script yüklendi. + message: Betik yüklendi. msgInvalidScript: description: Message shown when script is invalid. - message: Geçersiz Script! + message: Betik geçersiz! msgLoadedData: description: >- Message shown in the confirm page when a javascript file to be installed is @@ -570,43 +795,68 @@ msgLoading: touched: false msgLoadingData: description: Message shown on confirm page when the script to be installed is loading. - message: Script verileri yükleniyor... + message: Betik verileri yükleniyor... msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. - message: Veriler yükleniyor... ($1/$2) + message: Bağımlılıklar yükleniyor... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Gerekli kaynaklar eksik. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. - message: >- - Script isiminde anlaşmazlık! Lütfen @name ve @namespace alanlarını - düzenleyin. + message: |- + Böyle bir betik zaten yüklenmiş. + Lütfen farklı bir @name ve @namespace kullanın veya diğer betiği düzenleyin. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: Aynı @name ve @namespace'e sahip bir betik zaten yüklü. msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @downloadURL is provided. - message: Yeni versiyon bulundu. + message: Yeni sürüm bulundu. msgNoUpdate: description: Message shown when there is no new version of a script. message: Güncelleme bulunamadı. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Betikler güncellenirken hata oluştu. Açmak için tıklayın. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Lütfen bu betikleri yeniden kaydedin veya yeniden yükleyin:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (kod aynı) msgSavedBlacklist: description: Message shown when blacklist are saved. message: Kara liste güncellendi. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. - message: Geçerli stil güncellendi. + message: Özel stil güncellendi. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Düzenleyici ayarları kaydedildi. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. - message: Özelleştirilmiş script şablonu kaydedildi. + message: Özelleştirilmiş betik şablonu kaydedildi. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Script [$1] güncellendi!' + message: Betik [$1] güncellendi! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff - message: '' + message: Göster/gizle msgSyncError: description: Message shown when sync failed. message: Eşitleme hatası! @@ -615,45 +865,134 @@ msgSyncInit: message: Başlatılıyor... msgSyncInitError: description: Message shown when sync fails in initialization. - message: Başlatma error! + message: Başlatma hatası! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Henüz yetkilendirilmemiş. msgSyncReady: description: Message shown when sync will start soon. message: Eşitleme az sonra başlayacak... + touched: false msgSyncing: description: Message shown when sync is in progress. - message: Şu anda eşitleniyor... + message: Eşitleme devam ediyor... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Söz dizimi hatası? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + Sayfa yüklendiğinde bu betik mevcut değildi veya URL ile eşleşmedi. Sahte + navigasyon kullanan Facebook ve Instagram gibi tek sayfa uygulamasında bu + sorun yaşanabilir. Betiği çalıştırmak içn sekmeyi yenileyebilirsiniz. Betiği + düzeltmek isterseniz sitenin tamamı için @match kullanabilir, ardından + değişiklikleri MutationObserver veya window.navigation API ile tespit + edebilirsiniz. msgUpdated: description: Message shown when a script is updated/reinstalled. - message: Script güncellendi. + message: Betik güncellendi. msgUpdating: description: Message shown when a new version of script is being fetched. message: Güncelleniyor... noValues: description: Label shown when there is no value for current script. - message: Kayıtlı değer yok. + message: Kayıtlı değer yok optionEditorWindow: description: Label for the option in settings - message: '' + message: Düzenleyiciyi yeni pencerede aç optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support onBoundsChanged - message: '' + message: >- + Düzenleyici penceresinin konumu yalnızca yeniden boyutlandırıldığında veya + kaydedildiğinde hatırlanacaktır optionEditorWindowSimple: description: Label for the editor window type - message: '' + message: Arama/adres çubuğunu gizle +optionPopup: + description: Label of the popup menu section in settings. + message: Açılır pencere menüsü ve simgesi optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. - message: Önce etkin scriptler + message: Önce etkin betikler +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Devre dışı betikleri grupla + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: '@run-at aşamasına göre grupla' optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: Etkin olmayan scriptleri gizle + message: Devre dışı betikleri gizle + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Devre dışı betikleri göster + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Önce etkin betikleri göster +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Arayüz teması: ' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: otomatik +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: koyu +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: açık +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Güncelleme +popupSettings: + description: Item inside the popup's "⋮" menu + message: Açılır pencere ayarları +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Sağ tıklama: açılır pencere ayarları' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Salt okunur +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Güncellemeleri devre dışı bırakmadığınız veya düzenlemeye izin vermediğiniz + sürece, otomatik olarak güncellenen betikler salt okunurdur. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Düzenlemelere izin ver +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (bir sonraki güncelleme bunların üzerine yazacaktır) +reinstall: + description: Button to reinstall a script + message: Yeniden yükle +reloadTab: + description: Label of action to reload the tab + message: Sekmeyi yenile +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Bunu etkinleştirirseniz, değişiklil algılandığında ve bu betik sekmenin + URL'siyle eşleştiğinde etkin sekme yenilenir. +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search - message: Büyük/küçük harfe duyarlı + message: Büyük-küçük harfe duyarlı searchUseRegex: description: Option to perform a regular expression search message: Regex kullan @@ -662,27 +1001,100 @@ sideMenuAbout: message: Hakkında sideMenuInstalled: description: 'Side menu: Installed scripts' - message: Yüklenen Scriptler + message: Yüklenmiş betikler sideMenuSettings: description: 'Side menu: Settings' message: Ayarlar -titleScriptUpdated: - description: Notification title for script updates. - message: Güncelleme +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: Kullanıcı betikleri olmadan sayfayı yenile +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Bu sayfada kullanıcı betiklerini devre dışı bıraktınız. Betikleri + çalıştırmak isterseniz sekmeyi yenileyin veya sekmede gezinin. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Sıralama:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: İzlemeyi durdur +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Normal rozet rengi +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Enjekte edilemeyen (kara listedeki veya desteklenmeyen) sitelerde rozet + rengi titleSearchHint: description: Hover title for search icon in dashboard. message: |- * tuşu metni otomatik tamamlama geçmişine ekler * RegExp sözdizimi desteklenmektedir: /re/ ve /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Enter tuşu metni otomatik tamamlama geçmişine ekler + + * Tüm koşullar büyük-küçük harfe duyarlıdır + + * Koşullar boşlukla ayrılarak birlikte kullanılabilir + + * Meta verisi arama: "Şahane Betik" "Açıklama" + + * Etiket arama: #etiket1 #etiket2 + + * Betik ismi arama: name:"şahane isim" + + * Betik kodunda arama: code:"şahane kod" + + * Negatif arama: !#etiket2 !name:"istenmeyen" + + * Düzenli ifadeler: /\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? with space" +toggleInjection: + description: Label for a keyboard shortcut. + message: Kullanıcı betiği enjeksiyonunu aç/kapat +trackEdits: + description: Button in a script installation dialog. + message: Harici düzenlemeleri izle +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: Düzenlemelerinizi yerel dosyada izlemek için bu sayfayı açık tutun +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Betikleri güncelle ($1) +updateScript: + description: Button to update one script. + message: Güncelle +updateScriptsAll: + description: Command/button to update all scripts. + message: Tüm betikleri güncelle +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Geçerli sekmedeki betikleri güncelle valueLabelKey: description: Label for key of a script value. message: Anahtar (string) valueLabelValue: description: Label for value of a script value. - message: Değer (JSON olarak yazılmış) + message: Değer (JSON biçiminde serileştirilmiş) valueLabelValueAll: description: Label for input of entire script value storage. + message: Tüm değerler (JSON biçiminde serileştirilmiş) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: Siteyi ziyaret et + touched: false diff --git a/src/_locales/uk/messages.yml b/src/_locales/uk/messages.yml index ff26fb66e1..ff23822c40 100644 --- a/src/_locales/uk/messages.yml +++ b/src/_locales/uk/messages.yml @@ -1,24 +1,42 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Застосувати buttonCancel: description: Cancel button on dialog. message: Скасувати +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Перевірити оновлення + touched: false buttonClose: description: Button to close window. message: Закрити buttonConfirmInstallation: description: Button to confirm installation of a script. message: Встановити + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Перевстановити + touched: false buttonDisable: description: Button to disable a script. message: Вимкнути +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: '' + touched: false buttonEdit: description: Button to edit a script. message: Редагувати buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: '' + message: >- + Ви також можете клацнути правою кнопкою миші, Ctrl+клацнути, клацнути + коліщатком на назві сценарію. buttonEmptyRecycleBin: description: Button to empty the recycle bin. - message: '' + message: Очистити кошик зараз buttonEnable: description: Button to enable a script. message: Ввімкнути @@ -50,7 +68,7 @@ buttonOK: message: OK buttonRecycleBin: description: Button to list scripts in recycle bin. - message: '' + message: Кошик buttonRemove: description: Button to remove a script. message: Видалити @@ -62,32 +80,44 @@ buttonReplaceAll: message: Замінити всі buttonReset: description: Button to reset to default values. + message: Скинути +buttonResetSettings: + description: Button in settings page to reset all settings message: '' buttonRestore: description: Button to restore a removed script. - message: '' + message: Відновити buttonSave: description: Button to save modifications of a script. message: Зберегти buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Зберегти й закрити +buttonSaved: + description: Button text after saving. + message: '' buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. - message: '' + message: Показати стан редактора buttonSupport: description: Button to open support page. message: Сторінка підтримки +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. message: Скасувати - touched: false buttonUpdate: - description: Check a script for updates. - message: Перевірити оновлення + description: Button to update a script. + message: Оновити buttonUpdateAll: description: Check all scripts for updates. message: Перевірити оновлення скриптів + touched: false buttonVacuum: description: Button to vacuum extension data. message: Очистити кеш @@ -97,16 +127,27 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Очищення даних кешу... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: '' confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Зміни не збережено! Натисніть OK, щоб вийти або Скасувати, щоб повернутися. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: '' descBlacklist: - description: HTML Description for the global blacklist. + description: Description for the global injection blacklist. message: >- Скрипти не будуть вводитися в документи, URL яких збігаються з правилами цього списку. +descBlacklistNet: + description: Description for the global network blacklist. + message: '' descCustomCSS: description: Description of custom CSS section. message: >- @@ -114,16 +155,42 @@ descCustomCSS: скриптів. Якщо Ви не знаєте, що це, будь ласка, не редагуйте цей текст. descEditorOptions: description: Description of editor options JSON section. + message: >- + Опції для редактора CodeMirror та його доповнень у форматі JSON, наприклад + {"indentUnit":2, "smartIndent":true} Майте на увазі, що деякі + опції можуть не працювати в Violentmonkey. Дивитись повний список. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: '' +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: '' +descScriptTemplate: + description: Description of script template section. + message: '' +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: '' +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: '' +disabledScriptsSelector: + description: Label of the option. + message: '' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' message: '' editHelpDocumention: description: Label in the editor help tab for the documentation link. - message: '' + message: 'Документація по блоку метаданих скриптів та GM API:' editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. - message: '' + message: Шорткати клавіатури editHowToHint: description: The text of the how-to link in the editor header. - message: '' + message: Використовувати інший редактор? editLabelMeta: description: Metadata section in settings tab of script editor. message: Користувацькі meta-дані @@ -132,10 +199,16 @@ editLabelSettings: message: Налаштування скрипта editLongLine: description: Shown in the editor in lines that were cut due to being too long - message: '' + message: Рядок надто довгий editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long - message: '' + message: >- + Цей рядок занадто довгий і його текст згорнутий, щоб уникнути затримок при + редагуванні. + + Ви можете налаштувати ліміт в розширених налаштуваннях, наприклад: + + "maxDisplayLength\": 20000 editNavCode: description: Label of code tab in script editor. message: Код @@ -144,21 +217,16 @@ editNavSettings: message: Налаштування editNavValues: description: Label of values tab in script editor. - message: '' + message: Значення editValueAll: description: Button to show/edit the entire script value storage. message: '' + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: '' -editValueSave: - description: Button to save modification of a script value. - message: '' + message: Показати/редагувати сховище значень скрипту extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: >- Violentmonkey дозволяє користувачеві автоматично вводити скрипти в сторінки, щоб змінювати їх вигляд чи поведінку. @@ -166,12 +234,22 @@ extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' - message: '' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: Чорний список Violentmonkey failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) + message: >- + Violentmonkey не може запускати користувацькі скрипти на цій сторінці + + (типові приклади: інтерфейс браузера, розширення, заблоковані через + політики, пошуковий двигун в Opera) +failureReasonRestarted: + description: Shown in the popup. + message: '' +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. message: '' filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. @@ -181,64 +259,95 @@ filterExecutionOrder: message: порядку виконання filterLastUpdateOrder: description: Label for option to sort scripts by last update time. + message: даті оновлення +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. message: '' filterScopeAll: description: Option in dashboard's search scope filter. - message: '' + message: Всі + touched: false filterScopeCode: description: Option in dashboard's search scope filter. - message: '' + message: Код + touched: false filterScopeName: description: Option in dashboard's search scope filter. - message: '' + message: Ім'я + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: розміру genericError: description: Label for generic error. - message: '' + message: Помилка genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' - message: '' + description: To indicate something is turned off or disabled, similar to "no". + message: вимкнено genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' - message: '' + description: To indicate something is turned on or enabled, similar to "yes". + message: увімкнено genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. + message: використовувати глобальні налаштування +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). message: '' headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. + message: Кошик +helpForLocalFile: + description: Label of the checkbox. + message: '' +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: '' +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'Введіть URL:' hintRecycleBin: description: Hint for recycle bin. - message: '' + message: Видалені скрипти перераховані тут і зберігатимуться протягом 7 днів. hintUseDownloadURL: description: Shown as a place holder for @updateURL when it is not assigned message: Використовувати @downloadURL hintVacuum: description: Hint for vacuuming data. message: Очистити зайвий кеш та спробувати довантажити відсутні ресурси. +install: + description: Label for button to install a script. + message: '' installFrom: description: Label for button to install script from a userscript site. message: Встановити з $1 installOptionClose: description: Option to close confirm window after installation. message: Закрити після встановлення. -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: 'Стежити за оновленнями локального файлу, доки відкрите це вікно.' + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before window is closed. - message: '' + message: >- + Вкладка вихідного файлу повинна залишатися відкритою у Firefox 68 та + новішому. labelAbout: description: Label shown on top of the about page. message: Про додаток Violentmonkey touched: false labelAdvanced: description: Label for button to show advanced settings. - message: '' + message: Розширені labelAllowUpdate: description: Option to allow checking updates for a script. message: Дозволити оновлення @@ -252,13 +361,24 @@ labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. - message: '' + message: Поточні скрипти працюватимуть до перезавантаження вкладки. + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates + message: Перевіряти оновлення скриптів кожні $1 днів, використовувати 0 для вимкнення +labelBackup: + description: Label of the import/export section in settings. + message: Бекап + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. message: '' labelBadge: description: Label for option to show number on badge. message: 'Показувати на значку: ' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Кольори бейджа:' labelBadgeNone: description: Option to display nothing on badge. message: нічого @@ -273,7 +393,7 @@ labelBlacklist: message: Чорний список labelContributors: description: Label for link to contributors. - message: '' + message: Учасники labelCurrentLang: description: Label of current language. message: 'Поточна мова: ' @@ -283,24 +403,30 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Експорт даних + touched: false labelDataImport: description: Section title of data import. message: Імпорт даних + touched: false labelDonate: description: Label of link to donate page. message: Підтримати проект фінансово + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'Джерело завантаження:' labelEditValue: description: Label shown in the panel to edit a script value. - message: '' + message: Редагування значень скрипта labelEditValueAll: description: Label shown in the panel to edit the entire script value storage. - message: '' + message: Редагування сховища скриптів labelEditor: description: Label for Editor settings - message: '' + message: Редактор +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: лише увімкнені скрипти labelExclude: description: Label of @exclude rules. message: 'Не застосовувати на (правила виключень @exclude):' @@ -312,28 +438,34 @@ labelExportScriptData: message: Налаштування скрипта labelExposeStatus: description: Option in advanced settings. + message: Показувати версію встановленого скрипту на сайтах каталогів скриптів:$1 +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Альтернативний $1 режим у Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. message: '' labelFeedback: description: Label of link to feedback page. message: Зворотній зв'язок -labelFilterSort: - description: Label for sort filter. - message: Сортувати по $1 labelGeneral: description: Label for general settings. message: Загальні налаштування labelHelpTranslate: description: Label for link to localization guide in about tab - message: '' + message: Допомога з перекладом labelHomepage: description: Label for home page in about tab. - message: '' + message: Домашня сторінка labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'Дом.сторінка:' +labelIconURL: + description: Label for the input. + message: '' labelImportScriptData: description: Option to import script data along with scripts. - message: '' + message: Імпорт даних скриптів labelImportSettings: description: Label for option to import settings from zip file. message: Імпортувати налаштування. @@ -341,7 +473,9 @@ labelInclude: description: Label of @include rules. message: 'Застосовувати на (правила включень @include):' labelInjectionMode: - description: Label for default option to inject scripts. + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. message: '' labelInstall: description: Shown in the title of the confirm page while trying to install a script. @@ -351,7 +485,7 @@ labelKeepOriginal: message: Використовувати оригінальні правила. labelLastUpdatedAt: description: Label shown on last updated time. - message: '' + message: Останнє оновлення о $1 labelLineNumber: description: Label for line number jumper. message: 'Номер рядка:' @@ -361,10 +495,12 @@ labelMatch: labelName: description: Label of script name. message: 'Назва:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Запускати у фреймах:' labelNoName: description: Text as the name of a script when no @name is assigned. message: Новий скрипт - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: Скрипти не знайдено. @@ -379,19 +515,21 @@ labelNotifyUpdates: message: Повідомляти про оновлення скриптів. labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. - message: '' -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: '' + message: Ігнорувати повідомлення скриптів (вкладка налаштувань у редакторі) labelPrivacyPolicy: description: Label of link to privacy policy - message: '' + message: Політика конфіденційності +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Перевстановлення скрипта labelRelated: description: Label of related links. message: 'Посилання: ' labelRemovedAt: description: Label for the time when the script is removed. - message: '' + message: Видалено о $1 labelReplace: description: Label for replace input in search box. message: 'Замінити на: ' @@ -403,7 +541,7 @@ labelRunAtDefault: message: (за замовчуванням) labelScriptTemplate: description: Label for custom script template. - message: '' + message: Шаблон користувацького скрипта labelSearch: description: Label for search input in search box. message: 'Знайти: ' @@ -413,24 +551,33 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Налаштування +labelShowOrder: + description: Label for option in dashboard -> script list + message: Показати позиції порядку виконання +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Синхронізація з хмарним сховищем labelSyncAnonymous: description: Label for using anonymous account. - message: '' + message: Використовувати анонімний обліковий запис labelSyncAuthorize: description: Label for button to authorize a service. message: Підключити labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Авторизація... +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: нічим labelSyncPassword: description: Label for input to hold password. - message: '' + message: 'Пароль:' labelSyncReauthorize: description: Option to reauthorize sync service when expired. message: '' @@ -443,13 +590,19 @@ labelSyncScriptStatus: message: Синхронізувати статус скриптів. labelSyncServerUrl: description: Label for input to hold server URL. - message: '' + message: 'URL сервера:' labelSyncService: description: Label for sync service select. message: Синхронізувати з labelSyncUsername: description: Label for input to hold username. + message: 'Ім''я користувача:' +labelTags: + description: Label for custom tags. message: '' +labelTheme: + description: Label for the visual theme option. + message: 'Тема:' labelTranslator: description: Label of translator. message: 'Перекладачі: ' @@ -461,9 +614,29 @@ labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: Один стовпець labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. + message: Табличний вигляд +labelWidth: + description: Width. + message: '' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: '' +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Увімкніть лише у випадку, якщо у вас є скрипт, який повинен бути запущений + до початку завантаження сторінки, і зараз він виконується занадто пізно. Як + і режим Instant injection в Tampermonkey, цей варіант використовує + застарілий синхронний XHR, тому Chrome/Chromium будуть попередження в + консолі розробника, але ви можете ігнорувати їх, оскільки негативні наслідки + незначні. Приховати попередження можна натиснувши ПКМ на одному з них +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. message: '' lastSync: description: Label for last sync timestamp. @@ -473,7 +646,7 @@ learnBlacklist: message: Детальніше про чорні списки. learnInjectionMode: description: Refers to a link to introduce injection modes. - message: '' + message: Докладніше про режими введення menuCommands: description: Menu item to list script commands. message: Команди скриптів @@ -485,42 +658,63 @@ menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: '' + message: Виключати... menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. - message: '' + message: >- + Поточна вкладка буде автоматично перезавантажена, якщо ви увімкнули цю опцію + в загальних налаштуваннях. + + Щоб застосувати зміни до інших вкладок, перезавантажте їх вручну. + + Використовуйте вкладку "Налаштування" редактора для гнучкості. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Зворотній зв'язок menuFindScripts: description: Menu item to find scripts for a site. message: Знайти скрипти для сайту menuInjectionFailed: description: Injection error. - message: '' + message: Не вдалося ввести деякі скрипти. menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' - message: '' + description: Injection error fix, shown in case the default mode is "page". + message: Повторити спробу в режимі "авто" +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Відключені скрипти, що збігаються menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. - message: '' + message: Тільки скрипти саб-фреймів menuMatchedScripts: description: Label for menu listing matched scripts. message: Співпадаючі скрипти menuNewScript: description: Menu item to create a new script. - message: '' + message: Створити новий скрипт menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Скрипти вимкнені menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Скрипти ввімкнені msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Перевірка наявності оновлень... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Натисніть, щоб відкрити документ MomentJS. Дозволені маркери: $1. + Використовуйте [квадратні дужки] для захисту буквального тексту. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of the script. - message: '' + message: Помилка вилучення ресурсу! msgErrorFetchingScript: description: Message shown when Violentmonkey fails fetching a new version of the script. message: Помилка завантаження скрипта! @@ -544,7 +738,9 @@ msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito window. - message: '' + message: >- + Зміни, зроблені в режимі інкогніто, також застосовуються до вашого основного + профілю. msgInstalled: description: Message shown when a script is installed. message: Скрипт встановлено. @@ -567,11 +763,21 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Завантаження необхідних компонентів... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Відсутні необхідні ресурси. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. message: Конфлікт простору імен скриптів. Змініть @name і @namespace. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: '' msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -580,24 +786,39 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Оновлення не знайдено. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Помилка оновлення скриптів. Натисніть, щоб відкрити їх. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Повторно збережіть або перевстановіть ці скрипти:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (код такий самий) msgSavedBlacklist: description: Message shown when blacklist are saved. message: Чорний список збережено. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Користувацькі стилі CSS збережено. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. - message: '' + message: Параметри редактора оновлено. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. - message: '' + message: Шаблон користувацького скрипта оновлено. + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Скрипт [$1] оновлено!' + message: Скрипт [$1] оновлено! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff - message: '' + message: Показати/сховати msgSyncError: description: Message shown when sync failed. message: Помилка синхронізації. @@ -607,12 +828,22 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Помилка ініціалізації синхронізації. +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Ще не авторизовано. msgSyncReady: description: Message shown when sync will start soon. message: Синхронізація скоро почнеться... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Синхронізація... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Синтаксична помилка? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: '' msgUpdated: description: Message shown when a script is updated/reinstalled. message: Скрипт оновлено. @@ -621,33 +852,102 @@ msgUpdating: message: Оновлення... noValues: description: Label shown when there is no value for current script. - message: '' + message: Немає значень для збереження optionEditorWindow: description: Label for the option in settings - message: '' + message: Відкривати редактор зі спливаючого вікна в новому вікні optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support onBoundsChanged - message: '' + message: >- + Положення вікна редактора буде запам'ятовуватися лише після зміни розміру + або збереження optionEditorWindowSimple: description: Label for the editor window type - message: '' + message: Приховати омнібокс +optionPopup: + description: Label of the popup menu section in settings. + message: Спливаюче меню та значок optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. + message: Спочатку включені +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Групувати відключені скрипти + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. message: '' optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: '' + message: Сховати відключені скрипти + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Показати відключені скрипти + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Показувати ввімкнені скрипти першими +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'Тема інтерфейсу:' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: автоматична +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: темна +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: світла +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Оновлення +popupSettings: + description: Item inside the popup's "⋮" menu + message: '' +popupSettingsHint: + description: Hint for the config button in the popup + message: '' +readonly: + description: Text in the editor's header for non-editable scripts. + message: '' +readonlyNote: + description: Warning when trying to type in the editor. + message: '' +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: '' +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: '' +reinstall: + description: Button to reinstall a script + message: '' +reloadTab: + description: Label of action to reload the tab + message: '' +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: '' +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search - message: '' + message: З урахуванням регістру searchUseRegex: description: Option to perform a regular expression search - message: '' + message: Використовувати Регулярний вираз (Regex) sideMenuAbout: description: 'Side menu: About' message: Про розширення @@ -657,21 +957,76 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Налаштування -titleScriptUpdated: - description: Notification title for script updates. - message: Оновлення +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: '' +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: '' +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: '' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: '' +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Колір звичайного значка +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Колір значка, якщо скрипти не запустилися (сторінка в чорному списку або не + підтримується) titleSearchHint: description: Hover title for search icon in dashboard. + message: |- + * Клавіша додає текст до історії автозаповнення. + * Підтримується RegExp: /re/ та /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: '' +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: '' +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: '' +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: '' +updateScript: + description: Button to update one script. + message: Оновлення +updateScriptsAll: + description: Command/button to update all scripts. + message: '' +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. message: '' valueLabelKey: description: Label for key of a script value. - message: '' + message: Ключ (рядок) valueLabelValue: description: Label for value of a script value. - message: '' + message: Значення (у форматі JSON) valueLabelValueAll: description: Label for input of entire script value storage. + message: Усі значення (у форматі JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' message: '' visitWebsite: description: Label for link to open Violentmonkey website. - message: '' + message: Відвідати веб-сайт + touched: false diff --git a/src/_locales/vi/messages.yml b/src/_locales/vi/messages.yml index 3a25edb428..3cf5e08b0b 100644 --- a/src/_locales/vi/messages.yml +++ b/src/_locales/vi/messages.yml @@ -1,21 +1,37 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: Áp dụng buttonCancel: description: Cancel button on dialog. message: Hủy +buttonCheckForUpdates: + description: Button to check a script for updates. + message: Kiểm tra cập nhật + touched: false buttonClose: description: Button to close window. message: Đóng buttonConfirmInstallation: description: Button to confirm installation of a script. message: Xác nhận cài đặt + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: Xác nhận cài đặt lại + touched: false buttonDisable: description: Button to disable a script. message: Vô hiệu hoá +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: Tải xuống chủ đề + touched: false buttonEdit: description: Button to edit a script. message: Sửa buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: '' + message: Bạn có thể bấm chuột phải, giữ Ctrl và click, bấm chuột giữa vào tên script. buttonEmptyRecycleBin: description: Button to empty the recycle bin. message: Thùng rác trống! @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: Thiết lập lại +buttonResetSettings: + description: Button in settings page to reset all settings + message: Đặt lại cài đặt buttonRestore: description: Button to restore a removed script. message: Khôi phục @@ -72,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: Lưu & Đóng +buttonSaved: + description: Button text after saving. + message: Đã lưu buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: Các cài đặt mẫu buttonSupport: description: Button to open support page. message: Hỗ trợ +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. message: Hoàn tác - touched: false buttonUpdate: - description: Check a script for updates. - message: Kiểm tra cập nhật + description: Button to update a script. + message: Cập nhật buttonUpdateAll: description: Check all scripts for updates. message: Kiểm tra cập nhật tất cả + touched: false buttonVacuum: description: Button to vacuum extension data. message: Làm trống dữ liệu @@ -97,14 +125,31 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: Làm trống dữ liệu... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + Tự động cập nhật bị vô hiệu hóa cho kịch bản này! + Nhấp Đồng ý để cập nhật nó. confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- Thay đổi chưa được lưu! Bấm Đồng ý để không lưu hoặc Huỷ để quay lại và sửa. +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: >- + Hoàn nguyên tất cả các thay đổi được thực hiện cho cơ sở dữ liệu (nhập, cập + nhật, chỉnh sửa, tùy chỉnh) descBlacklist: - description: HTML Description for the global blacklist. - message: URL trong danh sách này sẽ không bị bỏ qua. + description: Description for the global injection blacklist. + message: Danh sách đen chặn tiêm mã (script sẽ không chạy trong các trang web khớp) +descBlacklistNet: + description: Description for the global network blacklist. + message: >- + Danh sách đen mạng (các script sẽ không kết nối đến các trang web khớp và + cookie của chúng). descCustomCSS: description: Description of custom CSS section. message: >- @@ -118,6 +163,37 @@ descEditorOptions: một số cài đặt có thể không hoạt động trong Violentmonkey. Xem chi tiết. +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + Các tùy chọn Boolean có thể được bật bằng cách nhấp đúp vào giá trị + true = enabled, false = disabled. Tùy chọn số kết + thúc bằng Delay, Interval, Rate, + Time tính bằng mili giây. +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + Các tùy chọn không chuẩn: 1autocompleteOnTyping1 là độ trễ tính bằng mili + giây để hiển thị gợi ý tự động hoàn thành sau khi nhập (202 = tắt), + 3killTrailingSpaceOnSave3 tự động xóa khoảng trắng ở cuối mỗi dòng khi lưu, + 4showTrailingSpace4 hiển thị khoảng trắng ở cuối dòng dưới dạng dấu chấm. +descScriptTemplate: + description: Description of script template section. + message: >- + Các biến được hỗ trợ: <{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}> + với định dạng tương thích MomentJS, ví dụ như <{{date:YYYY-MM-DD}}>. +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: Nhóm +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: Ẩn +disabledScriptsSelector: + description: Label of the option. + message: 'Tập lệnh bị vô hiệu hóa:' +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: Hiện editHelpDocumention: description: Label in the editor help tab for the documentation link. message: 'Các tài liệu về API đọc ở đây:' @@ -135,10 +211,16 @@ editLabelSettings: message: Cài đặt Script editLongLine: description: Shown in the editor in lines that were cut due to being too long - message: '' + message: Dòng quá dài editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long - message: '' + message: >- + Dòng này quá dài nên văn bản của nó được thu gọn để tránh bị chậm trễ khi + chỉnh sửa. + + Bạn có thể điều chỉnh giới hạn trong cài đặt nâng cao, ví dụ: + + "maxDisplayLength": 20000 editNavCode: description: Label of code tab in script editor. message: Mã nguồn @@ -151,62 +233,101 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: Tất cả + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: Huỷ -editValueSave: - description: Button to save modification of a script value. - message: Lưu + message: Hiển thị/chỉnh sửa toàn bộ bộ lưu trữ giá trị script extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: Hỗ trợ chạy userscript trên trình duyệt. extName: description: Name of this extension. message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: Đã ở trong danh sách đen của Violentmonkey failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) message: Violentmonkey không hoạt động ở đây. +failureReasonRestarted: + description: Shown in the popup. + message: >- + Violentmonkey đã được khởi động lại. Vui lòng tải lại tab để chạy + userscripts. +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + Có một số phương pháp để cài đặt hoặc theo dõi chỉnh sửa trong một tệp cục + bộ: <1> Kéo và thả tệp vào một trang hoặc cửa sổ Violentmonkey đang mở, bao + gồm cả trang này. <2> Cài đặt một máy chủ HTTP cục bộ cho tệp này và mở nó + qua http://localhost. <3> Bật "Cho phép truy cập vào URL tệp" trong chi tiết + cho Violentmonkey trong trang chrome://extensions. Điều này nguy hiểm vì bất + kỳ tập lệnh người dùng nào cũng có thể đọc bất kỳ tệp cục bộ nào. filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. - message: thứ tự bảng chứ cái + message: Theo bảng chữ cái filterExecutionOrder: description: Label for option to sort scripts in execution order. - message: thứ tự thực thi + message: Thực thi filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: cập nhật cuối +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: Hiện tất cả + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: Cách viết mã + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: Tên + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: kích thước genericError: description: Label for generic error. - message: '' + message: Lỗi genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: tắt genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: bật genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: Sử dụng cài đặt khuyên dùng +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: >- + Mẫu "#" (dấu thăng) chỉ hoạt động khi mở trang web lần đầu hoặc tải lại tab: + $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: Thùng rác +helpForLocalFile: + description: Label of the checkbox. + message: Hiển thị hướng dẫn cho việc kéo và thả các tập lệnh cục bộ. +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: Đối với các tập lệnh khớp với $1 +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 'URL đầu vào:' @@ -219,15 +340,16 @@ hintUseDownloadURL: hintVacuum: description: Hint for vacuuming data. message: Bỏ qua dữ liệu thừa và cố gắng tải lại nguồn dữ liệu thiếu từ bộ nhớ đệm. +install: + description: Label for button to install a script. + message: Cài đặt installFrom: description: Label for button to install script from a userscript site. message: Cài đặt từ $1 installOptionClose: description: Option to close confirm window after installation. message: Đóng sau khi cài đặt xong -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: Theo dõi tệp cục bộ trước khi cửa sổ này đóng + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -253,13 +375,24 @@ labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. - message: '' + message: Những script hiện tại sẽ vẫn tiếp tục chạy cho đến khi bạn tải lại thẻ + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: 'Kiểm tra cập nhật mỗi $1 ngày, 0 = tắt' + message: Kiểm tra cập nhật mỗi $1 ngày, 0 = tắt +labelBackup: + description: Label of the import/export section in settings. + message: Sao lưu + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: Sao lưu và bảo trì labelBadge: description: Label for option to show number on badge. - message: 'Hiển thị ở huy hiệu: ' + message: 'Huy hiệu:' +labelBadgeColors: + description: Label for option group to set badge colors. + message: 'Màu huy hiệu: ' labelBadgeNone: description: Option to display nothing on badge. message: Không có gì @@ -284,12 +417,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: Xuất dữ liệu + touched: false labelDataImport: description: Section title of data import. message: Nhập dữ liệu + touched: false labelDonate: description: Label of link to donate page. message: Đóng góp + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 'URL tải về:' @@ -298,10 +434,13 @@ labelEditValue: message: Sửa giá trị script labelEditValueAll: description: Label shown in the panel to edit the entire script value storage. - message: '' + message: Đang chỉnh sửa lưu trữ script labelEditor: description: Label for Editor settings message: Trình chỉnh sửa +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: Chỉ bật kịch bản labelExclude: description: Label of @exclude rules. message: Bỏ qua @@ -313,13 +452,22 @@ labelExportScriptData: message: Xuất dữ liệu script labelExposeStatus: description: Option in advanced settings. - message: '' + message: >- + Hiển thị phiên bản đã cài đặt trên các trang web danh mục phiên bản người + dùng: $1 +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: Chế độ $1 thay thế trong Firefox +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + Kể từ Firefox 59, phương pháp tiêm thay thế cho 1 kịch bản nhanh hơn chế độ + mặc định. Tương tự như "Sync Page Mode", nó cũng làm tăng mức tiêu thụ bộ + nhớ, vì vậy nếu kịch bản của bạn hoạt động tốt mà không có tùy chọn này, bạn + có thể muốn vô hiệu hóa nó. labelFeedback: description: Label of link to feedback page. message: Phản hồi -labelFilterSort: - description: Label for sort filter. - message: Sắp xếp theo $1 labelGeneral: description: Label for general settings. message: Chung @@ -332,9 +480,12 @@ labelHomepage: labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 'Liên kết trang chủ:' +labelIconURL: + description: Label for the input. + message: 'URL biểu tượng:' labelImportScriptData: description: Option to import script data along with scripts. - message: '' + message: Nhập dữ liệu script labelImportSettings: description: Label for option to import settings from zip file. message: Nhập cài đặt @@ -342,8 +493,10 @@ labelInclude: description: Label of @include rules. message: Thực thi labelInjectionMode: - description: Label for default option to inject scripts. - message: 'Chế độ tiêm mặc định: ' + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 'Chế độ tiêm: ' labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: Đang cài đặt script @@ -362,10 +515,12 @@ labelMatch: labelName: description: Label of script name. message: 'Tên:' +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 'Chạy trong frames: ' labelNoName: description: Text as the name of a script when no @name is assigned. message: Không có tên - touched: false labelNoSearchScripts: description: Message shown when no script is found in search results. message: Không thấy script nào @@ -383,12 +538,14 @@ labelNotifyUpdatesGlobal: message: >- Bỏ qua thông báo trên mỗi script (thay đổi trong cài đặt ở mục chỉnh sửa script) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: Sắp xếp scripts trong popup theo $1 labelPrivacyPolicy: description: Label of link to privacy policy message: Chính sách bảo mật +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: Cài đặt lại script labelRelated: description: Label of related links. message: 'Liên kết gợi ý: ' @@ -416,6 +573,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: Cài đặt +labelShowOrder: + description: Label for option in dashboard -> script list + message: Hiển thị thứ tự sắp xếp thực thi +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: Đồng bộ @@ -428,6 +591,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: Đang xác thực +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: Không có @@ -453,6 +619,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: 'Tài khoản:' +labelTags: + description: Label for custom tags. + message: 'Thẻ (cách nhau bằng dấu cách):' +labelTheme: + description: Label for the visual theme option. + message: 'Giao diện: ' labelTranslator: description: Label of translator. message: 'Dịch bởi: ' @@ -464,10 +636,31 @@ labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: Một cột labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. - message: '' + message: Chế độ xem bảng +labelWidth: + description: Width. + message: 'Chiều rộng:' +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: Chế độ đồng bộ $1 +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + Chỉ bật nếu bạn có một tập lệnh cần chạy trước khi trang bắt đầu đang tải và + hiện nó đang chạy quá muộn. Cũng giống như tiêm tức thì trong Tampermonkey, + tùy chọn này đang sử dụng XHR đồng bộ không được dùng nữa, vì vậy trong + Chrome / Chromium, bạn sẽ thấy cảnh báo trong bảng điều khiển devtools, mặc + dù bạn có thể bỏ qua chúng một cách an toàn vì các tác động xấu là không + đáng kể trong trường hợp này. Bạn có thể ẩn các cảnh báo bằng cách nhấp + chuột phải vào một cảnh báo. +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (trừ chế độ ẩn danh và các trang web với cookie bị vô hiệu hóa) lastSync: description: Label for last sync timestamp. message: Đồng bộ cuối lúc $1 @@ -488,19 +681,36 @@ menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: '' + message: Bỏ qua... menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. - message: '' + message: >- + Tab hiện tại sẽ tự động tải lại nếu bạn đã bật tùy chọn này nói chung cài + đặt. + + Để áp dụng các thay đổi cho tất cả các tab khác, vui lòng tải lại chúng theo + cách thủ công. + + Sử dụng tab "Cài đặt" của trình chỉnh sửa để linh hoạt hơn. +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: Phản hồi menuFindScripts: description: Menu item to find scripts for a site. message: Tìm script cho trang này menuInjectionFailed: description: Injection error. - message: '' + message: Không thể tiêm một số script menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' - message: '' + description: Injection error fix, shown in case the default mode is "page". + message: Thử lại ở chế độ "tự động" +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: Các script bị vô hiệu hóa phù hợp menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. message: Chỉ hoạt động ở Khung phụ @@ -511,14 +721,19 @@ menuNewScript: description: Menu item to create a new script. message: Tạo script mới menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: Đã vô hiệu hoá script menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: Đã kích hoạt script msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: Đang kiểm tra cập nhật... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: >- + Nhấp để mở tài liệu MomentJS. Mã thông báo được phép: $1. Sử dụng [hình + vuông ngoặc] để bảo vệ văn bản theo nghĩa đen. msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -572,11 +787,21 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: Đang tải thư viện... ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: Thiếu tài nguyên bắt buộc. msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. message: Xung đột namespace! Vui lòng sửa @name và @namespace. +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: "Một tập lệnh có cùng @name và @namespace đã được cài đặt.\n\n\_" msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -585,24 +810,39 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: Không có cập nhật mới. +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: Lỗi cập nhật tập lệnh. Nhấn vào đây để mở chúng. +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 'Vui lòng lưu lại hoặc cài đặt lại (các) tập lệnh này:' +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: ' (mã nguồn không đổi)' msgSavedBlacklist: description: Message shown when blacklist are saved. message: Danh sách đen đã cập nhật. + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: Giao diện tùy chỉnh đã cập nhật. + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: Đã cập nhật cài đặt. + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: Script template tuỳ chỉnh đã được lưu + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 'Script [$1] đã được cập nhật!' + message: Script [$1] đã được cập nhật! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff - message: '' + message: Hiện/ẩn msgSyncError: description: Message shown when sync failed. message: Đồng bộ lỗi! @@ -612,12 +852,27 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: Khởi tạo lỗi! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: Chưa được ủy quyền. msgSyncReady: description: Message shown when sync will start soon. message: Đồng bộ sẽ chạy sớm... + touched: false msgSyncing: description: Message shown when sync is in progress. message: Đang đồng bộ... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: Lỗi cú pháp? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + Tập lệnh không tồn tại hoặc không khớp với URL khi trang được tải, điều này + xảy ra trong các trang Ứng dụng một trang như fb hoặc instagram đang sử dụng + điều hướng giả mạo. Bạn có thể tải lại tab để chạy tập lệnh. Để sửa tập + lệnh, hãy sử dụng @match cho toàn bộ trang web, sau đó phát hiện các thay + đổi bằng MutationObserver hoặc API window.navigation. msgUpdated: description: Message shown when a script is updated/reinstalled. message: Đã cập nhật script. @@ -629,24 +884,97 @@ noValues: message: Không có giá trị nào được lưu trữ optionEditorWindow: description: Label for the option in settings - message: '' + message: Mở trình chỉnh sửa trong popup ở của sổ mới optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support onBoundsChanged - message: '' + message: Editor window position will be remembered only on resizing or saving optionEditorWindowSimple: description: Label for the editor window type - message: '' + message: >- + Vị trí cửa sổ trình chỉnh sửa sẽ chỉ được ghi nhớ khi thay đổi kích thước + hoặc lưu +optionPopup: + description: Label of the popup menu section in settings. + message: Popup menu và icon optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: Đã bật ở trước +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: Nhóm các script bị vô hiệu hoá + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: Nhóm theo giai đoạn @run-at optionPopupHideDisabled: description: Option to hide disabled scripts in popup. message: Ẩn script bị tắt + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: Ẩn script bị vô hiệu hoá + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. message: Hiển thị script được bật ở trước +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: Chủ đề UI +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: Tự động +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: Tối +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: Sáng +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: Cập nhật +popupSettings: + description: Item inside the popup's "⋮" menu + message: Cài đặt menu ngữ cảnh +popupSettingsHint: + description: Hint for the config button in the popup + message: 'Nhấp chuột phải: cài đặt menu ngữ cảnh (popup)' +readonly: + description: Text in the editor's header for non-editable scripts. + message: Chỉ đọc +readonlyNote: + description: Warning when trying to type in the editor. + message: >- + Tập lệnh tự động cập nhật chỉ được phép đọc trừ khi bạn tắt cập nhật hoặc + cho phép chỉnh sửa. +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: Cho phép chỉnh sửa +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (Cập nhật tiếp theo sẽ ghi đè chúng) +reinstall: + description: Button to reinstall a script + message: Cài đặt lại +reloadTab: + description: Label of action to reload the tab + message: Tải lại trang +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: >- + Nếu được bật, tab đang hoạt động sẽ được tải lại khi phát hiện có thay đổi + và tập lệnh này khớp với URL của tab. +removeAllScripts: + description: Button to remove all scripts + message: '' + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: '' + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: Phân biệt hoa thường @@ -662,12 +990,67 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: Cài đặt -titleScriptUpdated: - description: Notification title for script updates. - message: Cập nhật +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: "Tải lại trang không có tập lệnh người dùng\n\n\_" +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: >- + Bạn đã tắt userscript (tập lệnh người dùng) cho trang này. Để chạy chúng, + hãy tải lại hoặc điều hướng tab. +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 'Thứ tự sắp xếp:' +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: Dừng theo dõi +titleBadgeColor: + description: Tooltip for option to set badge color. + message: Màu huy hiệu bình thường +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: >- + Màu huy hiệu khi trang web không thể đưa vào (danh sách đen hoặc không được + hỗ trợ) titleSearchHint: description: Hover title for search icon in dashboard. message: Nhập chính xác hoặc gần giống nội dung cần tìm + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + Phím 1Enter1 thêm văn bản vào lịch sử tự động hoàn thànhTất cả các điều kiện + không phân biệt chữ hoa chữ thườngCác điều kiện phân tách bằng dấu cách có + thể được kết hợpTìm kiếm theo siêu dữ liệu: 2"Kịch bản tuyệt vời" "Mô + tả"2Tìm kiếm theo thẻ: 3#thẻ1 #thẻ23Tìm kiếm theo tên kịch bản: 4name:"tên + tuyệt vời"4Tìm kiếm theo mã kịch bản: 5code:"mã tuyệt vời"5Tìm kiếm phủ + định: 6!#thẻ2 !name:"không mong muốn"6Biểu thức chính quy: 7/\w+?/7, + 8/\w+?/gi8, 9name:/\w+?/9, 10name+re:"\w+? có khoảng trắng"10 +toggleInjection: + description: Label for a keyboard shortcut. + message: '' +trackEdits: + description: Button in a script installation dialog. + message: Theo dõi chỉnh sửa bên ngoài +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: Giữ trang này mở để theo dõi các chỉnh sửa của bạn trong tệp cục bộ +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: Cập nhật tập lệnh ($1) +updateScript: + description: Button to update one script. + message: Cập nhật +updateScriptsAll: + description: Command/button to update all scripts. + message: Cập nhật tất cả tập lệnh +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: Cập nhật các tập lệnh của tab hiện tại valueLabelKey: description: Label for key of a script value. message: Khóa (chuỗi) @@ -676,7 +1059,14 @@ valueLabelValue: message: Giá trị (đã đưa về dạng JSON) valueLabelValueAll: description: Label for input of entire script value storage. + message: Tất cả giá trị (đã đưa về dạng JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: Truy cập trang web + touched: false diff --git a/src/_locales/zh_CN/messages.yml b/src/_locales/zh_CN/messages.yml index d68fe90698..b47120498f 100644 --- a/src/_locales/zh_CN/messages.yml +++ b/src/_locales/zh_CN/messages.yml @@ -1,21 +1,37 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: 应用 buttonCancel: description: Cancel button on dialog. message: 取消 +buttonCheckForUpdates: + description: Button to check a script for updates. + message: 检查更新 + touched: false buttonClose: description: Button to close window. message: 关闭 buttonConfirmInstallation: description: Button to confirm installation of a script. message: 确认安装 + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: 确认重新安装 + touched: false buttonDisable: description: Button to disable a script. message: 禁用 +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: 下载主题 + touched: false buttonEdit: description: Button to edit a script. message: 编辑 buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: 你也可以右键点击、Ctrl+点击、滚轮点击脚本名字 + message: 你也可以右键点击、Ctrl+点击、滚轮点击脚本名称。 buttonEmptyRecycleBin: description: Button to empty the recycle bin. message: 立即清空回收站! @@ -47,7 +63,7 @@ buttonNew: message: 新建 buttonOK: description: OK button on dialog. - message: 确定 + message: 完成 buttonRecycleBin: description: Button to list scripts in recycle bin. message: 回收站 @@ -63,6 +79,9 @@ buttonReplaceAll: buttonReset: description: Button to reset to default values. message: 重置 +buttonResetSettings: + description: Button in settings page to reset all settings + message: 重置设置 buttonRestore: description: Button to restore a removed script. message: 还原 @@ -72,22 +91,31 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: 保存并关闭 +buttonSaved: + description: Button text after saving. + message: 已保存 buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: 显示编辑器状态 buttonSupport: description: Button to open support page. message: 支持页面 +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: 拉取一次到本地 +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: 推送一次到远端 buttonUndo: description: Button to undo removement of a script. message: 撤销 - touched: false buttonUpdate: - description: Check a script for updates. - message: 查找更新 + description: Button to update a script. + message: 更新 buttonUpdateAll: description: Check all scripts for updates. message: 全部更新 + touched: false buttonVacuum: description: Button to vacuum extension data. message: 整理数据库 @@ -97,27 +125,67 @@ buttonVacuumed: buttonVacuuming: description: Message shown when data vacuum is in progress. message: 正在整理... +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + 此脚本的自动更新已被禁用! + 点击确认继续更新。 confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- 修改尚未保存! 点击确定放弃修改或点击取消停留此页面。 +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: 撤销所有数据变更(导入、更新、编辑、自定义) descBlacklist: - description: HTML Description for the global blacklist. - message: 此列表匹配的URL将不会被注入脚本。 + description: Description for the global injection blacklist. + message: 注入黑名单(脚本不会在匹配的网站上运行) +descBlacklistNet: + description: Description for the global network blacklist. + message: 网络黑名单(脚本不会连接到匹配的网站及其 Cookie) descCustomCSS: description: Description of custom CSS section. message: 这里可以为管理页面和脚本安装页面设置自定义CSS。如果你不确定它的意思,请不要修改。 descEditorOptions: description: Description of editor options JSON section. message: >- - CodeMirror及其扩展的自定义配置,使用一个JSON对象,如{"indentUnit":2, - "smartIndent":true},注意某些配置可能不被支持。见{"indentUnit":2, + "smartIndent":true},但请注意,其中一些选项在 Violentmonkey 中可能不起作用。 查看完整列表。如果要使用CodeMirror的自定义主题,可以像"theme": - "3024-day"这样指定主题的名字,并把实际的主题CSS粘贴到下面的“自定义样式”输入框。 + rel="noopener noreferrer">完整列表。 +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + 布尔选项可以通过双击值来切换:true 表示启用,false 表示停用。以 + DelayIntervalRateTime + 结尾的数值选项单位为毫秒。 +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + 非标准选项:autocompleteOnTyping + 是在打字后显示自动完成提示的延迟时间(以毫秒计,0 + 表示停用),killTrailingSpaceOnSave + 在保存时自动移除每一行的尾随空格,showTrailingSpace将尾随空格显示为点。 +descScriptTemplate: + description: Description of script template section. + message: >- + 支持的变量:<{{name}}>、<{{url}}>、<{{date}}>、<{{date:format}}> 使用 MomentJS 支持的格式,如 + <{{date:YYYY-MM-DD}}>。 +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: 分组 +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: 隐藏 +disabledScriptsSelector: + description: Label of the option. + message: 禁用的脚本: +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: 显示 editHelpDocumention: description: Label in the editor help tab for the documentation link. message: 用户脚本 metadata 区域和 GM API 的相关文档: @@ -154,31 +222,35 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: 全部 + touched: false editValueAllHint: description: Tooltip for the 'all' button. message: 展示/编辑脚本的所有数据 -editValueCancel: - description: Button to cancel modification of a script value. - message: 取消 -editValueSave: - description: Button to save modification of a script value. - message: 保存 extDescription: - description: 'Description for this extension, will be displayed in web store' + description: Description for this extension, will be displayed in web store message: 一个开源的用户脚本管理器,支持很多浏览器 extName: description: Name of this extension. message: 暴力猴 failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) message: 暴力猴黑名单设置阻止运行 failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) message: |- - 暴力猴无法在此页面执行用户脚本 - (如:浏览器界面、扩展页面) + 暴力猴无法在当前页面执行用户脚本 + (常见原因:浏览器/扩展内置页面、被策略屏蔽、Opera 下的搜索页面需要特殊设置) +failureReasonRestarted: + description: Shown in the popup. + message: 暴力猴已重新启动。请重新载入标签页以执行用户脚本。 +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + 有几种方法可以在本地文件中安装或跟踪编辑: <1> 将文件拖放到打开的暴力猴页面或弹出窗口中,包括此页面。 <2>为此文件安装本地 HTTP + 服务器并通过 http://localhost 打开它。 <3> 在 chrome://extensions 页面中为暴力猴启用“允许访问文件 + URL”。但这是危险的,因为任何用户脚本都可以读取任何本地文件。 filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. message: 字母顺序 @@ -187,31 +259,59 @@ filterExecutionOrder: message: 执行顺序 filterLastUpdateOrder: description: Label for option to sort scripts by last update time. - message: 最近更新时间 + message: 最近更新 +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: 最近访问时间 +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: 最近访问任何用到这个脚本的网站的时间。 filterScopeAll: description: Option in dashboard's search scope filter. message: 全部 + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: 代码 + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: 名字 + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: 大小 genericError: description: Label for generic error. message: 错误 genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' - message: 关 + description: To indicate something is turned off or disabled, similar to "no". + message: 关闭 genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' - message: 开 + description: To indicate something is turned on or enabled, similar to "yes". + message: 开启 genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: 使用全局设置 +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: '"#" 模式仅在首次打开网站或重新加载标签页时有效: $1' headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: 回收站 +helpForLocalFile: + description: Label of the checkbox. + message: 显示拖放本地脚本的详细说明 +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: 对 $1 匹配的脚本 +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 请输入地址: @@ -224,15 +324,16 @@ hintUseDownloadURL: hintVacuum: description: Hint for vacuuming data. message: 丢弃多余的数据,并尝试重新获取缺失的资源。 +install: + description: Label for button to install a script. + message: 安装 installFrom: description: Label for button to install script from a userscript site. message: 从$1安装 installOptionClose: description: Option to close confirm window after installation. message: 安装完成后关闭 -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: 本窗口关闭前跟踪打开的本地文件同步更新 + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before @@ -259,12 +360,23 @@ labelAutoReloadCurrentTabDisabled: Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. message: 当前脚本在你刷新页面前将保持运行 + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates message: 每 $1 天检查一次脚本更新,0 表示禁用 +labelBackup: + description: Label of the import/export section in settings. + message: 备份 + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: 备份和维护 labelBadge: description: Label for option to show number on badge. - message: 在扩展图标上显示: + message: 徽标: +labelBadgeColors: + description: Label for option group to set badge colors. + message: 徽标颜色: labelBadgeNone: description: Option to display nothing on badge. message: 无 @@ -289,12 +401,15 @@ labelCustomCSS: labelDataExport: description: Section title of data export. message: 数据导出 + touched: false labelDataImport: description: Section title of data import. message: 数据导入 + touched: false labelDonate: description: Label of link to donate page. message: 捐助 + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. message: 下载更新地址: @@ -307,6 +422,9 @@ labelEditValueAll: labelEditor: description: Label for Editor settings message: 编辑器 +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: 仅启用项 labelExclude: description: Label of @exclude rules. message: '@exclude 规则' @@ -319,24 +437,32 @@ labelExportScriptData: labelExposeStatus: description: Option in advanced settings. message: 在用户脚本收录站点上暴露已安装的版本信息:$1 +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: 在 Firefox 中切换 $1 模式 +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + 从 Firefox 59 开始,可以切换到此种比默认方案更快的 注入方式。和“同步 page + 模式”类似,它也会增加内存消耗,所以如果在不开启此功能的情况下你的脚本依然正常运行,则可以考虑禁用。 labelFeedback: description: Label of link to feedback page. message: 反馈 -labelFilterSort: - description: Label for sort filter. - message: 按 $1 排序 labelGeneral: description: Label for general settings. message: 通用 labelHelpTranslate: description: Label for link to localization guide in about tab - message: Help with translation + message: 帮助翻译 labelHomepage: description: Label for home page in about tab. message: 主页 labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 主页地址: +labelIconURL: + description: Label for the input. + message: 图标地址: labelImportScriptData: description: Option to import script data along with scripts. message: 导入脚本数据 @@ -347,8 +473,10 @@ labelInclude: description: Label of @include rules. message: '@include 规则' labelInjectionMode: - description: Label for default option to inject scripts. - message: 默认注入模式: + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 注入模式: labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: 安装脚本 @@ -366,11 +494,13 @@ labelMatch: message: '@match 规则' labelName: description: Label of script name. - message: 名字: + message: 名称: +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: "在内联框架中运行:\_" labelNoName: description: Text as the name of a script when no @name is assigned. - message: 名字为空 - touched: false + message: 名称为空 labelNoSearchScripts: description: Message shown when no script is found in search results. message: 未找到脚本! @@ -386,12 +516,14 @@ labelNotifyUpdates: labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. message: 忽略每个脚本的提示(编辑器中的“设置”页) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: 弹出菜单中的脚本按$1排序 labelPrivacyPolicy: description: Label of link to privacy policy message: 隐私策略 +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: 重新安装脚本 labelRelated: description: Label of related links. message: 相关链接: @@ -419,6 +551,12 @@ labelSearchScript: labelSettings: description: Label shown on the top of settings page message: 设置 +labelShowOrder: + description: Label for option in dashboard -> script list + message: 显示执行序号 +labelShowVisited: + description: Label for option in dashboard -> script list + message: 显示最近访问时间 labelSync: description: Label for sync options. message: 同步 @@ -431,6 +569,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: 授权中 +labelSyncAutomatically: + description: Label for option to sync automatically. + message: 自动同步 labelSyncDisabled: description: Label for option to disable sync service. message: 无 @@ -456,6 +597,12 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: 用户名: +labelTags: + description: Label for custom tags. + message: 标签(空格分隔): +labelTheme: + description: Label for the visual theme option. + message: '主题:' labelTranslator: description: Label of translator. message: 翻译者: @@ -471,6 +618,23 @@ labelViewSingleColumn: labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. message: 表格视图 +labelWidth: + description: Width. + message: 宽度: +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: 同步 $1 模式 +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + 仅当您有一个脚本需要在页面开始加载前运行,并且目前它运行得太晚时才启用。就像 Tampermonkey + 中的即时注入模式一样,这个选项使用的是被废弃的同步 XHR,所以在 Chrome/Chromium + 中,您会在开发者控制台看到警告,尽管您可以安全地忽略它们,因为在这种情况下的不利影响可以忽略不计。 您可以通过右键点击其中一个来永久隐藏这些警告。 +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (隐身模式和禁用 Cookie 的网站除外) lastSync: description: Label for last sync timestamp. message: 最近同步时间为 $1 @@ -498,6 +662,11 @@ menuExcludeHint: 如果你在设置中开启了相关选项,那么当前标签页将会自动重载。 其他标签页则需要手动刷新才能应用这些更新。 在脚本编辑器中的“设置”页可以进行更多配置。 +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: 反馈 menuFindScripts: description: Menu item to find scripts for a site. message: 为此站点查找脚本 @@ -505,8 +674,13 @@ menuInjectionFailed: description: Injection error. message: 部分脚本无法注入。 menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' + description: Injection error fix, shown in case the default mode is "page". message: 使用“auto”模式重试 +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: 匹配但禁用的脚本 menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. message: 仅在frame中执行的脚本 @@ -517,14 +691,17 @@ menuNewScript: description: Menu item to create a new script. message: 创建新脚本 menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: 脚本已禁用 menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: 脚本已启用 msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: 正在检查更新... +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: 单击打开 MomentJS 文档。允许的标记:$1。使用 [方括号] 包裹明文文本。 msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of @@ -576,11 +753,21 @@ msgLoadingData: msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. message: 正在加载脚本依赖...($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: 依赖的资源缺失。 msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. message: 脚本命名空间冲突,请修改@name和@namespace! +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: 已经安装了一个拥有同样的 @name 和 @namespace 的脚本。 msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @@ -589,18 +776,33 @@ msgNewVersion: msgNoUpdate: description: Message shown when there is no new version of a script. message: 未发现新版本。 +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: 更新脚本失败。点击打开他们。 +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 请重新保存或安装这些脚本: +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (代码一致) msgSavedBlacklist: description: Message shown when blacklist are saved. message: 黑名单已更新! + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: 自定义样式已更新! + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: 编辑器配置已更新! + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: 自定义脚本模板已更新! + touched: false msgScriptUpdated: description: Notification message for script updates. message: 脚本【$1】已更新! @@ -616,12 +818,25 @@ msgSyncInit: msgSyncInitError: description: Message shown when sync fails in initialization. message: 初始化失败! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: 还没有授权。 msgSyncReady: description: Message shown when sync will start soon. message: 即将开始同步... + touched: false msgSyncing: description: Message shown when sync is in progress. message: 正在同步... +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: 语法错误? +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + 当页面载入时,如果脚本不存在或与网址不匹配,这种情况通常发生在像是 fb 或 instagram + 这类使用假导航的单页应用网站中。您可以重新载入标签页以执行脚本。要修正脚本,请使用 @match 匹配整个网站,然后利用 + MutationObserver 或 window.navigation API 来侦测变化。 msgUpdated: description: Message shown when a script is updated/reinstalled. message: 脚本已更新。 @@ -642,15 +857,84 @@ optionEditorWindowHint: optionEditorWindowSimple: description: Label for the editor window type message: 隐藏地址栏 +optionPopup: + description: Label of the popup menu section in settings. + message: 弹出式菜单和图标 optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. message: 启用项优先 +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: 分组禁用的脚本 + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: 按照 @run-at 阶段分组 optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: 隐藏禁用项 + message: 隐藏禁用的脚本 + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: 显示禁用的脚本 + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. - message: 先显示被启用的脚本 + message: 优先显示启用的脚本 +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: 'UI 主题:' +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: 自动 +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: 深色 +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: 浅色 +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: 更新 +popupSettings: + description: Item inside the popup's "⋮" menu + message: 弹窗设置 +popupSettingsHint: + description: Hint for the config button in the popup + message: 右键点击:弹窗设置 +readonly: + description: Text in the editor's header for non-editable scripts. + message: 只读 +readonlyNote: + description: Warning when trying to type in the editor. + message: 除非禁用更新或允许编辑,否则自动更新的脚本是只读的。 +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: 允许编辑 +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (下次更新将覆盖它们) +reinstall: + description: Button to reinstall a script + message: 重装 +reloadTab: + description: Label of action to reload the tab + message: 刷新标签页 +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: 如果勾选,则在检测到脚本更新且匹配当前标签页的 URL 时,将自动刷新当前标签页。 +removeAllScripts: + description: Button to remove all scripts + message: 删除所有脚本 + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: |- + 移动所有脚本到回收站? + 它在一段时间后会自动清空。你也可以打开它并主动清空。 + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: 区分大小写 @@ -666,14 +950,76 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: 设置 -titleScriptUpdated: - description: Notification title for script updates. - message: 更新 +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: 刷新标签页并禁用用户脚本 +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: 你已在当前页面禁用用户脚本,刷新或跳转页面后将会重新启用。 +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 排序方式: +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: 停止跟踪 +titleBadgeColor: + description: Tooltip for option to set badge color. + message: 正常情况下的徽标颜色 +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: 页面无法注入脚本(列入黑名单或不支持)时的徽标颜色 titleSearchHint: description: Hover title for search icon in dashboard. message: |- * 键把文本加入自动补全历史 * 支持正则表达式:/re/ 和 /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * Enter 键会将搜索内容加入自动补全历史 + + * 所有条件不区分大小写 + + * 多个条件可以通过空格分开组合使用 + + * 搜索元数据:"Awesome Script" "Description" + + * 搜索标签:#tag1 #tag2 + + * 搜索脚本名称:name:"awesome name" + + * 搜索脚本代码:code:"awesome code" + + * 排除搜索:!#tag2 !name:"unwanted" + + * 正则表达式:/\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? with space" +toggleInjection: + description: Label for a keyboard shortcut. + message: 切换用户脚本注入 +trackEdits: + description: Button in a script installation dialog. + message: 跟踪外部编辑 +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: 保持此页面打开以跟踪本地文件中所做的编辑 +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: 更新脚本($1) +updateScript: + description: Button to update one script. + message: 更新 +updateScriptsAll: + description: Command/button to update all scripts. + message: 更新所有脚本 +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: 更新当前选项卡的脚本 valueLabelKey: description: Label for key of a script value. message: 键名(字符串) @@ -683,6 +1029,13 @@ valueLabelValue: valueLabelValueAll: description: Label for input of entire script value storage. message: 所有数据(JSON格式) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' visitWebsite: description: Label for link to open Violentmonkey website. message: 访问网站 + touched: false diff --git a/src/_locales/zh_TW/messages.yml b/src/_locales/zh_TW/messages.yml index 5e75c29b0c..ce7f67813f 100644 --- a/src/_locales/zh_TW/messages.yml +++ b/src/_locales/zh_TW/messages.yml @@ -1,50 +1,66 @@ +buttonApply: + description: Apply button on dialog that applies changes without closing the dialog. + message: 套用 buttonCancel: description: Cancel button on dialog. message: 取消 +buttonCheckForUpdates: + description: Button to check a script for updates. + message: 檢查更新 + touched: false buttonClose: description: Button to close window. message: 關閉 buttonConfirmInstallation: description: Button to confirm installation of a script. message: 確認安裝 + touched: false +buttonConfirmReinstallation: + description: Button to confirm re-installation when the script is already installed. + message: 確認重新安裝 + touched: false buttonDisable: description: Button to disable a script. message: 停用 +buttonDownloadThemes: + description: Button in Violentmonkey settings for editor. + message: 下載佈景主題 + touched: false buttonEdit: description: Button to edit a script. message: 編輯 buttonEditClickHint: description: Tooltip for the Edit button in popup. - message: 您也可以在腳本名稱上按右鍵、Ctrl + 按一下、按中鍵。 + message: 您也可以在腳本名稱上按右鍵、Ctrl + 按一下或按滑鼠中鍵 buttonEmptyRecycleBin: description: Button to empty the recycle bin. - message: 現在清空資源回收桶! + message: 立即清空資源回收桶! buttonEnable: description: Button to enable a script. message: 啟用 buttonExportData: description: Button to open the data export dialog. - message: 匯出為zip檔 + message: 匯出為 ZIP 壓縮檔 buttonFilter: description: Button to show filters menu. - message: 過濾器 + message: 篩選器 touched: false buttonHome: description: Button to open homepage. - message: 主頁 + message: 首頁 buttonImportData: description: Button to choose a file for data import. - message: 從zip檔匯入 + message: 從 ZIP 壓縮檔匯入 buttonInstallFromURL: description: Button to ask for URL of a user script. - message: 通過網址安裝 + message: 從網址安裝 buttonInstallOptions: description: Button to show options of installation confirm page. message: 選項 touched: false buttonNew: description: Button to create a new script. - message: 新建 + message: 新增腳本 buttonOK: description: OK button on dialog. message: 確定 @@ -59,10 +75,13 @@ buttonReplace: message: 取代 buttonReplaceAll: description: Button to replace all matches. - message: 全部 + message: 全部取代 buttonReset: description: Button to reset to default values. message: 重設 +buttonResetSettings: + description: Button in settings page to reset all settings + message: 重設設定 buttonRestore: description: Button to restore a removed script. message: 還原 @@ -72,68 +91,123 @@ buttonSave: buttonSaveClose: description: Button to save modifications of a script and then close the editing page. message: 儲存並關閉 +buttonSaved: + description: Button text after saving. + message: 已儲存 buttonShowEditorState: description: Button to show the list of currently used CodeMirror options. message: 顯示編輯器狀態 buttonSupport: description: Button to open support page. - message: 支援頁面 + message: 技術支援 +buttonSyncPullOnce: + description: Button to sync scripts from remote to local for once + message: '' +buttonSyncPushOnce: + description: Button to sync scripts from local to remote for once + message: '' buttonUndo: description: Button to undo removement of a script. message: 復原 - touched: false buttonUpdate: - description: Check a script for updates. - message: 檢查更新 + description: Button to update a script. + message: 更新 buttonUpdateAll: description: Check all scripts for updates. - message: 全部檢查更新 + message: 檢查所有腳本的更新 + touched: false buttonVacuum: description: Button to vacuum extension data. - message: 整理數據庫 + message: 清理資料庫 buttonVacuumed: description: Message shown when data is vacuumed. - message: 數據已整理 + message: 資料已清理完成 buttonVacuuming: description: Message shown when data vacuum is in progress. - message: 正在整理… + message: 正在清理資料… +confirmManualUpdate: + description: Confirm message shown when clicking update button for a single script. + message: |- + 此腳本已停用自動更新功能! + 若您仍想進行更新,請按下「確定」進行更新。 confirmNotSaved: description: Confirm message shown when there are unsaved script modifications. message: |- - 修改尚未儲存! - 點選確定放棄修改或點選取消停留此頁面。 + 您的修改尚未儲存! + 點選「確定」放棄修改,或點選「取消」繼續編輯。 +confirmUndoImport: + description: >- + Tooltip of the Undo button after importing a zip in settings. The same text + is also shown as a confirmation dialog after clicking this button. + message: 還原所有資料庫的變更(包含匯入、更新、編輯和自訂設定)。 descBlacklist: - description: HTML Description for the global blacklist. - message: 符合此列表的URL將不會被注入腳本。 + description: Description for the global injection blacklist. + message: 腳本插入黑名單(腳本不會在符合的網站上執行) +descBlacklistNet: + description: Description for the global network blacklist. + message: 網路黑名單(腳本無法連線至符合的網站及其 Cookie) descCustomCSS: description: Description of custom CSS section. - message: 這裡可以為管理頁面和腳本安裝頁面設定自訂CSS。如果你不確定它的意思,請不要修改。 + message: 設定選項頁面和腳本安裝頁面的自訂 CSS 樣式。 如果您不清楚這部分的用途,建議您不要更改此設定。 descEditorOptions: description: Description of editor options JSON section. - message: '' + message: >- + CodeMirror 和外掛程式的自訂選項,使用 JSON 物件進行設定,例如 {"indentUnit":2, + "smartIndent":true}。請注意, 某些選項在 Violentmonkey 中可能無法運作。詳細的選項,請參考完整清單。 +descEditorOptionsGeneric: + description: Description of editor options in VM settings. + message: >- + 布林值選項可以透過點選兩下數值來切換: true 表示啟用,false 表示停用。以 + DelayIntervalRateTime + 結尾的數值選項單位為毫秒。 +descEditorOptionsVM: + description: Description of nonstandard editor options in VM settings. + message: >- + 非標準選項:autocompleteOnTyping 是打字後顯示自動完成提示的延遲時間(毫秒,0 + 表示停用),killTrailingSpaceOnSave 會 + 在儲存時自動移除每一行的尾隨空白,showTrailingSpace 則會以點狀的方式顯示行尾空白。 +descScriptTemplate: + description: Description of script template section. + message: >- + 支援的變數包括:<{{name}}>, <{{url}}>, <{{date}}>, <{{date:format}}>,並且與 MomentJS + 相容的格式,例如 <{{date:YYYY-MM-DD}}>。 +disabledScriptsGroup: + description: 'Used as "Disabled scripts: group"' + message: 分組 +disabledScriptsHide: + description: 'Used as "Disabled scripts: hide"' + message: 隱藏 +disabledScriptsSelector: + description: Label of the option. + message: 已停用的腳本: +disabledScriptsShow: + description: 'Used as "Disabled scripts: show"' + message: 顯示 editHelpDocumention: description: Label in the editor help tab for the documentation link. - message: 使用者腳本詮釋資料區塊和 GM API 的文件: + message: 使用者腳本中繼資料區塊和 GM API 的文件: editHelpKeyboard: description: Label in the editor help tab for the keyboard shortcuts. message: 鍵盤快捷鍵: editHowToHint: description: The text of the how-to link in the editor header. - message: 使用另一個編輯器? + message: 使用其他編輯器? editLabelMeta: description: Metadata section in settings tab of script editor. - message: 自訂詮釋資料 + message: 自訂中繼資料 editLabelSettings: description: Settings section in settings tab of script editor. message: 腳本設定 editLongLine: description: Shown in the editor in lines that were cut due to being too long - message: 行太長 + message: 此行過長 editLongLineTooltip: description: Tooltip shown in the editor in lines that were cut due to being too long message: |- - 這一行太長了,所以為了避免編輯時有延遲,文字被摺疊了。 - 您可以在進階設定調整限制,例如: + 這一行太長了,因此其文字已被摺疊以避免編輯時延遲。 + 您可以在進階設定中調整限制,例如: "maxDisplayLength": 20000 editNavCode: description: Label of code tab in script editor. @@ -147,31 +221,38 @@ editNavValues: editValueAll: description: Button to show/edit the entire script value storage. message: 全部 + touched: false editValueAllHint: description: Tooltip for the 'all' button. - message: '' -editValueCancel: - description: Button to cancel modification of a script value. - message: 取消 -editValueSave: - description: Button to save modification of a script value. - message: 儲存 + message: 顯示/編輯整個腳本的儲存值 extDescription: - description: 'Description for this extension, will be displayed in web store' - message: 開放原始碼的使用者腳本管理器,支援多種瀏覽器 + description: Description for this extension, will be displayed in web store + message: 開放原始碼且支援多種瀏覽器的使用者腳本管理器 extName: description: Name of this extension. - message: 暴力猴 + message: Violentmonkey failureReasonBlacklisted: - description: 'Shown for blacklisted URLs (in the extension icon tooltip, in the popup)' - message: 在暴力猴的設定被加入黑名單 + description: Shown for blacklisted URLs (in the extension icon tooltip, in the popup) + message: 在 Violentmonkey 的設定中被列入黑名單 failureReasonNoninjectable: description: >- Shown for URLs that cannot be processed (same places as failureBlacklistedUrl) message: |- - 暴力猴無法在這個頁面執行使用者腳本 - (常見的例子:瀏覽器使用者介面或是擴充套件) + Violentmonkey 無法在此頁面執行使用者腳本 + + (常見情形可能是由於頁面屬於瀏覽器的使用者介面、擴充功能、被安全政策封鎖, + 或者是 Opera 瀏覽器中的搜尋引擎網站) +failureReasonRestarted: + description: Shown in the popup. + message: Violentmonkey 已經重新啟動。請重新載入分頁以執行使用者腳本。 +fileInstallBlocked: + description: Text in the installer when a local file is dropped into the browser window. + message: >- + 您可以使用以下幾種方法來安裝或追蹤本機檔案的編輯:<1> 直接將檔案拖放到已開啟的 Violentmonkey 頁面或彈出視窗中, 包含這個視窗。 + <2> 為該檔案架設一個本機 HTTP 伺服器,然後透過 http://localhost 的網址來存取它。 <3> 透過 Chrome + 瀏覽器的「擴充功能」頁面 chrome://extensions,啟用 Violentmonkey 的「允許存取檔案網址」。請注意,此操作具有風險, + 因為這將允許所有使用者腳本讀取您電腦上的任何檔案。 filterAlphabeticalOrder: description: Label for option to sort scripts in alphabetical order. message: 字母順序 @@ -181,59 +262,88 @@ filterExecutionOrder: filterLastUpdateOrder: description: Label for option to sort scripts by last update time. message: 上次更新時間 +filterLastVisitOrder: + description: >- + Label for option to sort scripts by the last time the user visited any site + targeted by this script. + message: '' +filterLastVisitOrderTooltip: + description: Tooltip for option to sort scripts. + message: '' filterScopeAll: description: Option in dashboard's search scope filter. message: 全部 + touched: false filterScopeCode: description: Option in dashboard's search scope filter. message: 程式碼 + touched: false filterScopeName: description: Option in dashboard's search scope filter. message: 名稱 + touched: false +filterSize: + description: Label for option to sort scripts by size. + message: 大小 genericError: description: Label for generic error. message: 錯誤 genericOff: - description: 'To indicate something is turned off or disabled, similar to "no".' + description: To indicate something is turned off or disabled, similar to "no". message: 關閉 genericOn: - description: 'To indicate something is turned on or enabled, similar to "yes".' + description: To indicate something is turned on or enabled, similar to "yes". message: 開啟 genericUseGlobal: description: To indicate some per-script option will use its analog in global settings. message: 使用全域設定 +hashPatternWarning: + description: Warning shown in the editor, $1 is the pattern(s). + message: 「#」模式僅在第一次開啟網站或重新載入分頁時有效: $1 headerRecycleBin: description: Text shown in the header when scripts in recycle bin are listed. message: 資源回收桶 +helpForLocalFile: + description: Label of the checkbox. + message: 顯示如何透過「拖放」來新增本機腳本的說明 +hintForBatchAction: + description: Hint shown next to the buttons for batch actions. + message: 對 $1 個符合的腳本 +hintGrantless: + description: >- + 1) This is a tooltip so preserve the line breaks to ensure it's not too + wide. 2) Don't translate back-quoted terms like `window` or `@grant none`. + message: '' hintInputURL: description: Hint for a prompt box to input URL of a user script. message: 請輸入網址: hintRecycleBin: description: Hint for recycle bin. - message: 移除的腳本會列在這裡,保留七天。 + message: 本區域會顯示所有被移除的腳本,這些腳本將會被保留 7 天。 hintUseDownloadURL: description: Shown as a place holder for @updateURL when it is not assigned - message: 使用@downloadURL + message: 使用 @downloadURL hintVacuum: description: Hint for vacuuming data. - message: 丟棄多餘的數據,並嘗試重新獲取缺失的資源。 + message: 捨棄冗餘資料,並嘗試重新載入快取中遺失的資源。 +install: + description: Label for button to install a script. + message: 安裝 installFrom: description: Label for button to install script from a userscript site. - message: 從$1安裝 + message: 從 $1 安裝 installOptionClose: description: Option to close confirm window after installation. message: 安裝完成後關閉 -installOptionTrack: - description: Option to track the loading local file before window is closed. - message: 本視窗關閉前跟蹤開啟的本地檔案同步更新 + touched: false installOptionTrackTooltip: description: >- Tooltip in Firefox 68+ for the option to track the loading local file before window is closed. - message: 在 Firefox 68 與更新版本,程式碼檔案分頁應該保持開啟。 + message: 在 Firefox 68 和更新版本中,原始碼檔案的分頁應保持開啟。 labelAbout: description: Label shown on top of the about page. - message: 關於暴力猴 + message: 關於 Violentmonkey touched: false labelAdvanced: description: Label for button to show advanced settings. @@ -246,18 +356,29 @@ labelAuthor: message: 作者: labelAutoReloadCurrentTab: description: Option to reload current tab after a script is switched on or off from menu. - message: 從選單開啟或關閉腳本後自動重新整理當前頁面 + message: 從選單開啟或關閉腳本後自動重新整理目前頁面 labelAutoReloadCurrentTabDisabled: description: >- Tooltip in menu after Violentmonkey is switched off if auto-reload is disabled. - message: 目前的腳本會繼續執行,直到您重新載入分頁 + message: 目前執行的腳本會持續運作,直到您重新載入分頁為止 + touched: false labelAutoUpdate: description: Option to allow automatically checking scripts for updates - message: 每 $1 天檢查腳本更新,用 0 停用 + message: 每 $1 天檢查腳本更新,設定為 0 則停用 +labelBackup: + description: Label of the import/export section in settings. + message: 備份 + touched: false +labelBackupMaintenance: + description: Label of the import/export/maintenance section in settings. + message: 備份與維護 labelBadge: description: Label for option to show number on badge. - message: 在徽章上顯示: + message: 徽章顯示: +labelBadgeColors: + description: Label for option group to set badge colors. + message: 徽章顏色: labelBadgeNone: description: Option to display nothing on badge. message: 無 @@ -266,7 +387,7 @@ labelBadgeTotal: message: 正在執行的腳本數量 labelBadgeUnique: description: Option to display number of unique running scripts on badge. - message: 正在執行的不重複的腳本數量 + message: 正在執行的不重複腳本數量 labelBlacklist: description: Label for global blacklist settings in security section. message: 黑名單 @@ -275,31 +396,37 @@ labelContributors: message: 貢獻者 labelCurrentLang: description: Label of current language. - message: 當前語言: + message: 目前語言: labelCustomCSS: description: Label for custom CSS section. message: 自訂樣式 labelDataExport: description: Section title of data export. - message: 數據匯出 + message: 資料匯出 + touched: false labelDataImport: description: Section title of data import. - message: 數據匯入 + message: 資料匯入 + touched: false labelDonate: description: Label of link to donate page. - message: 捐助 + message: 贊助 + touched: false labelDownloadURL: description: Label of script @downloadURL in custom meta data. - message: 下載更新網址: + message: 下載網址: labelEditValue: description: Label shown in the panel to edit a script value. - message: 編輯腳本數值 + message: 編輯腳本值 labelEditValueAll: description: Label shown in the panel to edit the entire script value storage. - message: '' + message: 正在編輯腳本資料 labelEditor: description: Label for Editor settings message: 編輯器 +labelEnabledScriptsOnly: + description: Sub-option for autoupdate option, begins with a lowercase letter. + message: 僅限已啟用的腳本 labelExclude: description: Label of @exclude rules. message: '@exclude 規則' @@ -308,28 +435,37 @@ labelExcludeMatch: message: '@exclude-match 規則' labelExportScriptData: description: Option to export script data along with scripts. - message: 匯出腳本數據 + message: 匯出腳本資料 labelExposeStatus: description: Option in advanced settings. - message: 透露已安裝的版本給使用者腳本目錄網站:$1 + message: 在使用者腳本目錄網站上顯示已安裝版本:$1 +labelFastFirefoxInject: + description: (`$1` will be shown as `page`) Option in Advanced settings. + message: 在 Firefox 瀏覽器中使用的「替代 $1 方案」 +labelFastFirefoxInjectHint: + description: Tooltip hint for the option in Advanced settings. + message: >- + 自 Firefox 59 版本以來,這個選項提供一種替代性的腳本插入方法,專門用於 + ,速度上比預設的模式要來得快。然而,和「同步頁面模式」一樣, + 開啟此選項會增加系統記憶體的使用量。若您發現不開啟此選項也能讓腳本正常運作, 建議您考慮停用此選項以節省記憶體。 labelFeedback: description: Label of link to feedback page. - message: 反饋 -labelFilterSort: - description: Label for sort filter. - message: 排須依照 $1 + message: 回饋 labelGeneral: description: Label for general settings. - message: 通用 + message: 一般 labelHelpTranslate: description: Label for link to localization guide in about tab - message: 幫助翻譯 + message: 協助翻譯 labelHomepage: description: Label for home page in about tab. message: 首頁 labelHomepageURL: description: Label of script @homepageURL in custom meta data. message: 首頁網址: +labelIconURL: + description: Label for the input. + message: 圖示網址: labelImportScriptData: description: Option to import script data along with scripts. message: 匯入腳本資料 @@ -340,8 +476,10 @@ labelInclude: description: Label of @include rules. message: '@include 規則' labelInjectionMode: - description: Label for default option to inject scripts. - message: 預設注入模式: + description: >- + Label for option in advanced settings and in script settings. Don't forget + the space after ":" if the translated language separates words with spaces. + message: 插入模式: labelInstall: description: Shown in the title of the confirm page while trying to install a script. message: 安裝腳本 @@ -350,7 +488,7 @@ labelKeepOriginal: message: 保留原有規則 labelLastUpdatedAt: description: Label shown on last updated time. - message: '上次更新於 $1 ' + message: 上次更新時間:$1 labelLineNumber: description: Label for line number jumper. message: 行號: @@ -359,41 +497,45 @@ labelMatch: message: '@match 規則' labelName: description: Label of script name. - message: 名字: + message: 名稱: +labelNoFrames: + description: Label of script @noframes properties in custom meta data. + message: 在網頁的各個框架(iframe)中執行: labelNoName: description: Text as the name of a script when no @name is assigned. - message: 名字為空 - touched: false + message: 無名稱 labelNoSearchScripts: description: Message shown when no script is found in search results. - message: 沒有找到腳本! + message: 找不到任何腳本。 labelNotifyThisUpdated: description: >- A per-script option in editor to enable notification when this script is updated. The text follows "Allow update" checkbox option so it's like a continuation of the phrase. - message: ,然後通知: + message: ,並發出通知: labelNotifyUpdates: description: Option to show notification when script is updated. message: 腳本更新時通知 labelNotifyUpdatesGlobal: description: Option to prioritize global notification option over script's setting. - message: 忽略每個腳本的通知(編輯器中的「設定」分頁) -labelPopupSort: - description: Label in the VM settings tab for script list sort order in popup - message: 在彈出視窗依照 $1 排序腳本 + message: 忽略個別腳本的通知設定(編輯器中的「設定」分頁) labelPrivacyPolicy: description: Label of link to privacy policy - message: 隱私策略 + message: 隱私權政策 +labelReinstall: + description: >- + Shown in the title of the confirm page while trying to install a script + that's already installed. + message: 重新安裝腳本 labelRelated: description: Label of related links. message: 相關連結: labelRemovedAt: description: Label for the time when the script is removed. - message: 移除於 $1 + message: 移除時間:$1 labelReplace: description: Label for replace input in search box. - message: 取代: + message: 取代為: labelRunAt: description: Label of script @run-at properties in custom meta data. message: 執行時機: @@ -405,13 +547,19 @@ labelScriptTemplate: message: 自訂腳本範本 labelSearch: description: Label for search input in search box. - message: 尋找: + message: 搜尋: labelSearchScript: description: Placeholder for script search box. message: 搜尋腳本… labelSettings: description: Label shown on the top of settings page message: 設定 +labelShowOrder: + description: Label for option in dashboard -> script list + message: 顯示執行順序 +labelShowVisited: + description: Label for option in dashboard -> script list + message: '' labelSync: description: Label for sync options. message: 同步 @@ -424,6 +572,9 @@ labelSyncAuthorize: labelSyncAuthorizing: description: Label for button when authorization is in progress. message: 授權中 +labelSyncAutomatically: + description: Label for option to sync automatically. + message: '' labelSyncDisabled: description: Label for option to disable sync service. message: 無 @@ -432,11 +583,11 @@ labelSyncPassword: message: 密碼: labelSyncReauthorize: description: Option to reauthorize sync service when expired. - message: '' + message: 過期時自動重新授權 touched: false labelSyncRevoke: description: Label for button to revoke authorization for a service. - message: 取消授權 + message: 撤銷授權 labelSyncScriptStatus: description: Label for option to sync script status. message: 同步腳本狀態 @@ -449,21 +600,44 @@ labelSyncService: labelSyncUsername: description: Label for input to hold username. message: 使用者名稱: +labelTags: + description: Label for custom tags. + message: 標籤(以空格分隔): +labelTheme: + description: Label for the visual theme option. + message: 佈景主題: labelTranslator: description: Label of translator. message: 翻譯者: touched: false labelUpdateURL: description: Label of script @updateURL in custom meta data. - message: 檢查更新網址: + message: 更新網址: labelViewSingleColumn: description: >- Label for option in dashboard script list to show the scripts in single column. - message: '' + message: 單欄顯示 labelViewTable: description: Label for option in dashboard script list to show the scripts as a table. - message: '' + message: 表格檢視 +labelWidth: + description: Width. + message: 寬度: +labelXhrInject: + description: >- + (`$1` will be shown as `page`) Option in Advanced settings to enable + synchronous injection. + message: 同步 $1 模式 +labelXhrInjectHint: + description: Tooltip of the `Synchronous page mode` option in Advanced settings. + message: >- + 只有在您有需要在網頁開始載入前執行的腳本, 且目前執行時機過晚時才啟用此選項。如同 Tampermonkey + 的「立即插入模式」,並會使用目前已不推薦使用的同步 XHR 技術。 在 Chrome / Chromium 瀏覽器的開發者工具中,這會觸發警告訊息; + 但您無需擔心,因為這些警告在這特定情境下的影響非常小。 如果您想永久去除這些警告,可以透過右鍵點選警告來隱藏它們。 +labelXhrInjectNote: + description: Shown to the right of "synchronous page mode" in options. + message: (無痕模式和已停用 Cookie 的網站除外) lastSync: description: Label for last sync timestamp. message: 最近同步時間為 $1 @@ -472,78 +646,96 @@ learnBlacklist: message: 了解更多黑名單規則。 learnInjectionMode: description: Refers to a link to introduce injection modes. - message: 了解更多注入模式。 + message: 了解更多插入模式。 menuCommands: description: Menu item to list script commands. message: 腳本命令 touched: false menuDashboard: description: Label for menu item to open dashboard. - message: 打開控制台 + message: 開啟控制面板 menuExclude: description: >- Shown in popup menu after clicking script's "..." dropdown so try to keep the label short. - message: 排除... + message: 排除… menuExcludeHint: description: Shown in popup menu after clicking "Exclude..." in script's "..." dropdown. - message: '' + message: |- + 如果您在一般設定中啟用了這個選項,目前的分頁將會自動重新載入。 + + 若要將變更套用至所有其他分頁,請手動重新載入它們。 + + 使用編輯器中的「設定」分頁可獲得更多客製化選項。 +menuFeedback: + description: >- + Menu item in popup to open the selected script's feedback page. Please use a + short word in translation to keep the menu narrow. + message: 提供回饋 menuFindScripts: description: Menu item to find scripts for a site. - message: 為此站點查詢腳本 + message: 尋找適用於此網站的腳本 menuInjectionFailed: description: Injection error. - message: 無法注入一些腳本。 + message: 無法插入部分腳本。 menuInjectionFailedFix: - description: 'Injection error fix, shown in case the default mode is "page".' - message: 在「自動」模式重試 + description: Injection error fix, shown in case the default mode is "page". + message: 在「自動」模式中重試 +menuMatchedDisabledScripts: + description: >- + Label for menu listing matched disabled scripts when the option to group + disabled scripts is selected. + message: 符合條件但已停用的腳本 menuMatchedFrameScripts: description: Label for menu listing matching scripts in sub-frames. - message: '' + message: 僅限子框架的腳本 menuMatchedScripts: description: Label for menu listing matched scripts. - message: 匹配的腳本 + message: 符合的腳本 menuNewScript: description: Menu item to create a new script. - message: 建立新腳本 + message: 新增腳本 menuScriptDisabled: - description: 'Menu item showing the status of Violentmonkey, when disabled.' + description: Menu item showing the status of Violentmonkey, when disabled. message: 腳本已停用 menuScriptEnabled: - description: 'Menu item showing the status of Violentmonkey, when enabled.' + description: Menu item showing the status of Violentmonkey, when enabled. message: 腳本已啟用 msgCheckingForUpdate: description: Message shown when a script is being checked for updates by version numbers. message: 正在檢查更新… +msgDateFormatInfo: + description: Help text of the info icon in VM settings for the export file name. + message: 點選此處以瀏覽 MomentJS 相關文件。允許的格式:$1。 若需保留文字的原意,請將其置於 [方括號] 內。 msgErrorFetchingResource: description: >- Message shown when Violentmonkey fails fetching a resource/require/icon of the script. - message: 獲取資源發生錯誤! + message: 取得資源時發生錯誤! msgErrorFetchingScript: description: Message shown when Violentmonkey fails fetching a new version of the script. - message: 獲取腳本發生錯誤! + message: 取得腳本時發生錯誤! msgErrorFetchingUpdateInfo: description: Message shown when Violentmonkey fails fetching version data of the script. - message: 獲取更新資訊失敗。 + message: 取得更新資訊失敗。 msgErrorLoadingData: description: >- Message shown on confirm page when the script to be installed cannot be loaded. - message: 載入腳本數據發生錯誤。 + message: 載入腳本資料時發生錯誤。 msgErrorLoadingDependency: description: Message shown when not all requirements are loaded successfully. - message: 載入腳本依賴發生錯誤。 + message: 載入相依性時發生錯誤。 msgImported: description: >- Message shown after import. There is an argument referring to the count of scripts imported. - message: 已匯入$1個腳本。 + message: 已匯入 $1 個腳本。 msgIncognitoChanges: description: >- Message shown in popup and installation tab when opened in an incognito window. - message: 在隱私模式做的變更也會套用到主設定檔。 + message: 您在無痕模式中所做的變更也會套用至您的主設定檔。 msgInstalled: description: Message shown when a script is installed. message: 腳本已安裝。 @@ -554,7 +746,7 @@ msgLoadedData: description: >- Message shown in the confirm page when a javascript file to be installed is loaded. - message: 腳本數據已載入。 + message: 腳本資料已載入。 touched: false msgLoading: description: Message shown in the options page before script list is loaded. @@ -562,56 +754,97 @@ msgLoading: touched: false msgLoadingData: description: Message shown on confirm page when the script to be installed is loading. - message: 正在載入腳本數據… + message: 正在載入腳本資料… msgLoadingDependency: description: Message shown on confirm page when the requirements are being downloaded. - message: 正在載入腳本依賴…($1/$2) + message: 正在載入相依性… ($1/$2) +msgMissingResources: + description: >- + Notification title shown for a missing @require or other resource when + running such scripts. + message: 缺少必要的資源,請確保所有必要檔案都已經下載和安裝。 msgNamespaceConflict: description: >- Message shown when namespace of the new script conflicts with an existent one. - message: 腳本命名空間衝突,請修改@name和@namespace! + message: |- + 您已經安裝了一個相似的腳本。 + + 請更改這個腳本的 @name 或 @namespace,或是直接編輯已經安裝的那個腳本。 +msgNamespaceConflictRestore: + description: >- + Message shown when namespace of the script to restore from recycle bin + conflicts with an existent one. + message: 您已經安裝了一個具有相同的 @name 和 @namespace 的腳本,請確認是否重複。 msgNewVersion: description: >- Message shown when a new version of script is found by @updateURL, but no @downloadURL is provided. - message: 發現新版本。 + message: 找到新版本。 msgNoUpdate: description: Message shown when there is no new version of a script. - message: 未發現新版本。 + message: 沒有找到更新。 +msgOpenUpdateErrors: + description: Shown at the beginning of a notification (Chrome) or in its title (Firefox). + message: 腳本更新失敗。請點選以檢視詳細資訊。 +msgReinstallScripts: + description: >- + Notification body shown for a missing @require or other resource when + running such scripts. + message: 請重新儲存或重新安裝這些腳本: +msgSameCode: + description: Shown inside the title when re-installing a script that's already installed. + message: (程式碼未變更) msgSavedBlacklist: description: Message shown when blacklist are saved. message: 黑名單已更新。 + touched: false msgSavedCustomCSS: description: Message shown when custom CSS is saved. message: 自訂樣式已更新。 + touched: false msgSavedEditorOptions: description: Message shown when editor options are saved. message: 編輯器選項已更新。 + touched: false msgSavedScriptTemplate: description: Message shown when custom script template is saved. message: 自訂腳本範本已更新。 + touched: false msgScriptUpdated: description: Notification message for script updates. - message: 腳本【$1】已更新! + message: 腳本 [$1] 已更新! msgShowHide: description: Tooltip or text shown on a toggle that shows/hides stuff message: 顯示/隱藏 msgSyncError: description: Message shown when sync failed. - message: 同步出錯! + message: 同步失敗! msgSyncInit: description: Message shown when sync service is initializing. message: 正在初始化… msgSyncInitError: description: Message shown when sync fails in initialization. message: 初始化失敗! +msgSyncNoAuthYet: + description: Message shown when the selected sync provider hasn't been authorized yet. + message: 尚未授權。 msgSyncReady: description: Message shown when sync will start soon. message: 即將開始同步… + touched: false msgSyncing: description: Message shown when sync is in progress. message: 正在同步… +msgSyntaxError: + description: Shown in popup when a script didn't run and we don't know the exact reason. + message: 遇到語法錯誤?可能導致腳本無法執行。 +msgTardyMatch: + description: Shown in popup for scripts that missed their chance to run. + message: >- + 當頁面載入時,腳本不存在或與網址不符,這種情況通常發生在使用虛擬導覽的單頁應用程式網站中, 像是 Facebook 或 Instagram + 這類的網站。您可以重新載入分頁以執行腳本。 要修正腳本,請對整個網站使用 @match,然後使用 MutationObserver 或 + window.navigation API 來偵測變化。 msgUpdated: description: Message shown when a script is updated/reinstalled. message: 腳本已更新。 @@ -620,27 +853,96 @@ msgUpdating: message: 正在更新… noValues: description: Label shown when there is no value for current script. - message: 沒有儲存的值 + message: 沒有儲存任何值 optionEditorWindow: description: Label for the option in settings - message: 在新視窗從彈出視窗開啟編輯器 + message: 在新視窗中從彈出視窗開啟編輯器 optionEditorWindowHint: description: >- Tooltip for optionEditorWindow in case the browser doesn't support onBoundsChanged - message: 只有在調整大小或儲存時才會記住編輯器視窗位置 + message: 只有在調整大小或儲存時才會記住編輯器視窗的位置 optionEditorWindowSimple: description: Label for the editor window type message: 隱藏網址列 +optionPopup: + description: Label of the popup menu section in settings. + message: 彈出式選單和圖示 optionPopupEnabledFirst: description: Option to show enabled scripts first in popup. - message: 先顯示啟用的腳本 + message: 優先顯示已啟用的腳本 +optionPopupGroupDisabled: + description: Option to group disabled scripts in popup. + message: 將已停用的腳本分組 + touched: false +optionPopupGroupRunAt: + description: Option to group scripts in popup shown only when sorting by execution order. + message: 依據腳本的 @run-at 執行時機進行分組 optionPopupHideDisabled: description: Option to hide disabled scripts in popup. - message: 隱藏停用的腳本 + message: 隱已停用的腳本 + touched: false +optionPopupShowDisabled: + description: Option to show disabled scripts in popup. + message: 顯示停用的腳本 + touched: false optionShowEnabledFirst: description: Option to show enabled scripts first in alphabetical order. - message: 先顯示啟用的腳本 + message: 優先顯示啟用的腳本 +optionUiTheme: + description: Label for the theme selector in Advanced settings. + message: UI 佈景主題: +optionUiThemeAuto: + description: Name of a theme selector value in settings. + message: 自動 +optionUiThemeDark: + description: Name of a theme selector value in settings. + message: 深色 +optionUiThemeLight: + description: Name of a theme selector value in settings. + message: 淺色 +optionUpdate: + description: >- + Label of the update section in settings and notification title for script + updates. + message: 更新 +popupSettings: + description: Item inside the popup's "⋮" menu + message: 彈出式選單設定 +popupSettingsHint: + description: Hint for the config button in the popup + message: 右鍵點選:彈出式選單設定 +readonly: + description: Text in the editor's header for non-editable scripts. + message: 唯讀 +readonlyNote: + description: Warning when trying to type in the editor. + message: 除非您停用更新或允許編輯,否則自動更新的腳本為唯讀。 +readonlyOpt: + description: Option label in the editor, displayed along with readonlyOptWarn. + message: 允許編輯 +readonlyOptWarn: + description: Text next to readonlyOpt label in the editor. + message: (下次更新將會覆蓋您的修改) +reinstall: + description: Button to reinstall a script + message: 重新安裝 +reloadTab: + description: Label of action to reload the tab + message: 重新載入分頁 +reloadTabTrackHint: + description: Tooltip for "Reload tab" checkbox when installer is tracking a local file. + message: 如果您啟用此選項,當系統偵測到與目前分頁網址相符的腳本有所變更時,該分頁將自動重新載入。 +removeAllScripts: + description: Button to remove all scripts + message: 移除所有腳本 + touched: false +removeAllScriptsConfirm: + description: Confirmation shown after clicking the `Remove all scripts` button. + message: |- + 將所有腳本移至「資源回收桶」? + 一段時間後會自動清空。您也可以開啟它並手動清空。 + touched: false searchCaseSensitive: description: Option to perform a case-sensitive search message: 區分大小寫 @@ -656,23 +958,92 @@ sideMenuInstalled: sideMenuSettings: description: 'Side menu: Settings' message: 設定 -titleScriptUpdated: - description: Notification title for script updates. - message: 更新 +skipScripts: + description: Title of the hotkey in the browser UI for customizing the hotkeys. + message: 不載入使用者腳本並重新載入頁面 +skipScriptsMsg: + description: Message shown in the popup if the tab was reloaded without scripts. + message: 您已停用此頁面的使用者腳本。若想重新啟用,請重新整理頁面或者轉到其他分頁再返回。 +sortOrder: + description: Label for the script sort order in dashboard, settings, popup settings. + message: 排序方式: +stopTracking: + description: Button in a script installation dialog when actually tracking local file. + message: 停止追蹤 +titleBadgeColor: + description: Tooltip for option to set badge color. + message: 一般徽章顏色 +titleBadgeColorBlocked: + description: Tooltip for option to set badge color of non-injectable tabs. + message: 頁面無法插入腳本(已列入黑名單或不支援)時的徽章顏色 titleSearchHint: description: Hover title for search icon in dashboard. message: |- - * 按 鍵會將文字加到自動完成記錄中 + * 按 鍵會將文字加入自動完成紀錄 * 支援正規表示式語法:/re/ 和 /re/flags + touched: false +titleSearchHintV2: + description: >- + Hover title for search icon in dashboard. Do not translate content between + .... + message: >- + * 按 Enter 鍵會將文字加入自動完成紀錄 + + * 所有條件不區分大小寫 + + * 可以使用空格分隔多個條件 + + * 透過中繼資料搜尋:"Awesome Script" "Description" + + * 透過標籤搜尋:#tag1 #tag2 + + * 透過腳本名稱搜尋:name:"awesome name" + + * 透過腳本程式碼搜尋:code:"awesome code" + + * 反向搜尋:!#tag2 !name:"unwanted" + + * 正規表示式:/\w+?/, /\w+?/gi, + name:/\w+?/, name+re:"\w+? with space" +toggleInjection: + description: Label for a keyboard shortcut. + message: 切換使用者腳本插入 +trackEdits: + description: Button in a script installation dialog. + message: 追蹤外部編輯 +trackEditsNote: + description: Text shown in installer when starting to track a local file. + message: 保持此頁面開啟以追蹤您在本機檔案中的編輯 +updateListedCmd: + description: >- + Command to update scripts e.g. in the extension popup, number of scripts in + parentheses. + message: 更新腳本 ($1) +updateScript: + description: Button to update one script. + message: 更新 +updateScriptsAll: + description: Command/button to update all scripts. + message: 更新所有腳本 +updateScriptsInTab: + description: Command/button to update scripts that run in current tab. + message: 更新目前分頁的腳本 valueLabelKey: description: Label for key of a script value. - message: 鍵(字串) + message: 鍵值(字串) valueLabelValue: description: Label for value of a script value. - message: 值(作為 JSON 序列化) + message: 值(序列化為 JSON) valueLabelValueAll: description: Label for input of entire script value storage. - message: 所有值(作為 JSON 序列化) + message: 所有值(序列化為 JSON) +valueLabelValueOr: + description: $1 is "[x] string" + message: '' +valueLabelValueString: + description: '$1 is the checkbox: "Value (serialized as JSON or [x] string)"' + message: '' visitWebsite: description: Label for link to open Violentmonkey website. - message: 訪問網站 + message: 瀏覽網站 + touched: false diff --git a/src/background/index.js b/src/background/index.js index 4c8ec7bb46..c9d83830b9 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -1,182 +1,73 @@ -import { getActiveTab, makePause, sendCmd } from '#/common'; -import { TIMEOUT_24HOURS, TIMEOUT_MAX } from '#/common/consts'; -import { deepCopy, forEachEntry, objectSet } from '#/common/object'; -import * as tld from '#/common/tld'; -import ua from '#/common/ua'; -import * as sync from './sync'; -import { commands } from './utils'; -import cache from './utils/cache'; -import { getData, checkRemove } from './utils/db'; -import { setBadge } from './utils/icon'; -import { initialize } from './utils/init'; -import { getOption, hookOptions } from './utils/options'; -import { getInjectedScripts } from './utils/preinject'; -import { SCRIPT_TEMPLATE, resetScriptTemplate } from './utils/template-hook'; -import { resetValueOpener, addValueOpener } from './utils/values'; -import { clearRequestsByTabId } from './utils/requests'; +import '@/common/browser'; +import { getActiveTab, makePause } from '@/common'; +import { deepCopy } from '@/common/object'; +import { handleHotkeyOrMenu } from './utils/icon'; +import { addPublicCommands, commands, init } from './utils'; +import './sync'; import './utils/clipboard'; -import './utils/hotkeys'; import './utils/notifications'; +import './utils/preinject'; import './utils/script'; -import './utils/tabs'; +import './utils/storage-fetch'; +import './utils/tab-redirector'; import './utils/tester'; import './utils/update'; -let isApplied; -const expose = {}; - -const optionHandlers = { - autoUpdate, - expose(val) { - val::forEachEntry(([site, isExposed]) => { - expose[decodeURIComponent(site)] = isExposed; - }); - }, - isApplied(val) { - isApplied = val; - }, - [SCRIPT_TEMPLATE](val, changes) { - resetScriptTemplate(changes); - }, -}; - -hookOptions((changes) => { - changes::forEachEntry(function processChange([key, value]) { - const handler = optionHandlers[key]; - if (handler) { - handler(value, changes); - } else if (key.includes('.')) { - objectSet({}, key, value)::forEachEntry(processChange); - } - }); - sendCmd('UpdateOptions', changes); -}); - -Object.assign(commands, { - /** @return {Promise<{ scripts: VMScript[], cache: Object, sync: Object }>} */ - async GetData(ids) { - const data = await getData(ids); - data.sync = sync.getStates(); - return data; - }, - /** @return {Promise} */ - async GetInjected(_, src) { - const { frameId, tab, url } = src; - if (!frameId) { - resetValueOpener(tab.id); - clearRequestsByTabId(tab.id); - } - const res = { - expose: !frameId && url.startsWith('https://') && expose[url.split('/', 3)[2]], - }; - if (isApplied) { - const data = await getInjectedScripts(url, tab.id, frameId); - addValueOpener(tab.id, frameId, data.withValueIds); - const badgeData = [data.enabledIds, src]; - setBadge(...badgeData); - // FF bug: the badge is reset because sometimes tabs get their real/internal url later - if (ua.isFirefox) cache.put(`badge:${tab.id}${url}`, badgeData); - Object.assign(res, data.inject); - // Injecting known content mode scripts without waiting for InjectionFeedback - const inContent = res.scripts.map(s => !s.code && [s.dataKey, true]).filter(Boolean); - if (inContent.length) { - // executeScript is slow (in FF at least) so this will run after the response is sent - Promise.resolve().then(() => commands.InjectionFeedback(inContent, src)); - } - } - return res; - }, - /** @return {Promise} */ - async GetTabDomain() { - const tab = await getActiveTab() || {}; - const url = tab.pendingUrl || tab.url || ''; - const host = url.match(/^https?:\/\/([^/]+)|$/)[1]; - return { - tab, - domain: host && tld.getDomain(host) || host, - }; - }, +addPublicCommands({ /** * Timers in content scripts are shared with the web page so it can clear them. * await sendCmd('SetTimeout', 100) in injected/content - * await bridge.send('SetTimeout', 100) in injected/web + * bridge.call('SetTimeout', 100, cb) in injected/web */ SetTimeout(ms) { return ms > 0 && makePause(ms); }, }); -// commands to sync unconditionally regardless of the returned value from the handler -const commandsToSync = [ - 'MarkRemoved', - 'Move', - 'ParseScript', - 'RemoveScript', - 'UpdateScriptInfo', -]; -// commands to sync only if the handler returns a truthy value -const commandsToSyncIfTruthy = [ - 'CheckRemove', - 'CheckUpdate', - 'CheckUpdateAll', -]; - -async function handleCommandMessage(req, src) { - const { cmd } = req; - const res = await commands[cmd]?.(req.data, src); - if (commandsToSync.includes(cmd) - || res && commandsToSyncIfTruthy.includes(cmd)) { - sync.sync(); +function handleCommandMessage({ cmd, data, url, [kTop]: mode } = {}, src) { + if (init) { + return init.then(handleCommandMessage.bind(this, ...arguments)); } - // `undefined` is not transferable, but `null` is - return res ?? null; + const func = hasOwnProperty(commands, cmd) && commands[cmd]; + if (!func) return; // not responding to commands for popup/options + // The `src` is omitted when invoked via sendCmdDirectly unless fakeSrc is set. + // The `origin` is Chrome-only, it can't be spoofed by a compromised tab unlike `url`. + if (src) { + let me = src.origin; + if (url) src.url = url; // MessageSender.url doesn't change on soft navigation + me = me ? me === extensionOrigin : `${url || src.url}`.startsWith(extensionRoot); + if (!me && func.isOwn && !src.fake) { + throw new SafeError(`Command is only allowed in extension context: ${cmd}`); + } + // TODO: revisit when link-preview is shipped in Chrome to fix tabId-dependent functionality + if (!src.tab) { + if (!me && (IS_FIREFOX ? !func.isOwn : !mode)) { + if (process.env.DEBUG) console.log('No src.tab, ignoring:', ...arguments); + return; + } + src.tab = false; // allowing access to props + } + if (mode) src[kTop] = mode; + } + return handleCommandMessageAsync(func, data, src); } -function autoUpdate() { - const interval = (+getOption('autoUpdate') || 0) * TIMEOUT_24HOURS; - if (!interval) return; - let elapsed = Date.now() - getOption('lastUpdate'); - if (elapsed >= interval) { - handleCommandMessage({ cmd: 'CheckUpdateAll' }); - elapsed = 0; +async function handleCommandMessageAsync(func, data, src) { + try { + // `await` is necessary to catch the error here + return await func(data, src); + } catch (err) { + if (process.env.DEBUG) console.error(err); + // Adding `stack` info + in FF a rejected Promise value is transferred only for an Error object + throw err instanceof SafeError ? err + : new SafeError(isObject(err) ? JSON.stringify(err) : err); } - clearTimeout(autoUpdate.timer); - autoUpdate.timer = setTimeout(autoUpdate, Math.min(TIMEOUT_MAX, interval - elapsed)); } -initialize(() => { - global.handleCommandMessage = handleCommandMessage; - global.deepCopy = deepCopy; - browser.runtime.onMessage.addListener( - ua.isFirefox // in FF a rejected Promise value is transferred only if it's an Error object - ? (...args) => ( - handleCommandMessage(...args).catch(e => { throw e instanceof Error ? e : new Error(e); })) - : handleCommandMessage, - ); - ['expose', 'isApplied'].forEach(key => optionHandlers[key](getOption(key))); - setTimeout(autoUpdate, 2e4); - sync.initialize(); - checkRemove(); - setInterval(checkRemove, TIMEOUT_24HOURS); - if (ua.isChrome) { - // Using declarativeContent to run content scripts earlier than document_start - const api = global.chrome.declarativeContent; - api.onPageChanged.getRules(['inject'], rules => { - if (rules.length) return; - api.onPageChanged.addRules([{ - id: 'inject', - conditions: [ - new api.PageStateMatcher({ - pageUrl: { urlContains: '://' }, // essentially like - }), - ], - actions: [ - new api.RequestContentScript({ - js: browser.runtime.getManifest().content_scripts[0].js, - // Not using `allFrames:true` as there's no improvement in frames - }), - ], - }]); - }); - } +window._bg = 1; +global['handle' + 'CommandMessage' /* hiding the global from IDE */] = handleCommandMessage; +global['deep' + 'Copy' /* hiding the global from IDE */] = deepCopy; +browser.runtime.onMessage.addListener(handleCommandMessage); +browser.commands?.onCommand.addListener(async cmd => { + handleHotkeyOrMenu(cmd, await getActiveTab()); }); diff --git a/src/background/plugin/events.js b/src/background/plugin/events.js index 9e79a5ec0a..f2c95d1a9f 100644 --- a/src/background/plugin/events.js +++ b/src/background/plugin/events.js @@ -1,4 +1,4 @@ -import EventEmitter from '#/common/events'; +import EventEmitter from '@/common/events'; export default new EventEmitter([ 'scriptEdit', diff --git a/src/background/plugin/index.js b/src/background/plugin/index.js index bd636c3c96..5c674836b7 100644 --- a/src/background/plugin/index.js +++ b/src/background/plugin/index.js @@ -1,5 +1,5 @@ -import { commands } from '../utils/message'; -import { getScripts } from '../utils/db'; +import { commands } from '../utils'; +import { getScripts, parseScript } from '../utils/db'; export const script = { /** @@ -7,7 +7,7 @@ export const script = { * @param {{ id, code, message, isNew, config, custom, props, update }} data * @return {Promise<{ isNew?, update, where }>} */ - update: commands.ParseScript, + update: parseScript, /** * List all available scripts, without script code * @return {Promise} diff --git a/src/background/sync/base.js b/src/background/sync/base.js index 7bf8b4d2e9..4d031ea7ae 100644 --- a/src/background/sync/base.js +++ b/src/background/sync/base.js @@ -1,25 +1,71 @@ import { - debounce, normalizeKeys, request, noop, makePause, ensureArray, sendCmd, -} from '#/common'; -import { TIMEOUT_HOUR } from '#/common/consts'; + debounce, + normalizeKeys, + request, + noop, + makePause, + sendCmd, + buffer2string, + getRandomString, +} from '@/common'; +import { TIMEOUT_HOUR, NO_CACHE } from '@/common/consts'; import { - forEachEntry, objectSet, objectPick, objectPurify, -} from '#/common/object'; + SYNC_MERGE, + SYNC_PULL, + SYNC_PUSH, + USER_CONFIG, +} from '@/common/consts-sync'; import { - getEventEmitter, getOption, setOption, hookOptions, -} from '../utils'; -import { - sortScripts, - updateScriptInfo, -} from '../utils/db'; + forEachEntry, + objectSet, + objectPick, + objectGet, +} from '@/common/object'; +import { getOption, setOption } from '../utils'; +import { sortScripts, updateScriptInfo } from '../utils/db'; import { script as pluginScript } from '../plugin'; +import { + events, + getSyncState, + resetSyncState, + setSyncState, + SYNC_AUTHORIZED, + SYNC_AUTHORIZING, + SYNC_ERROR, + SYNC_ERROR_AUTH, + SYNC_ERROR_INIT, + SYNC_IN_PROGRESS, + SYNC_INITIALIZING, + SYNC_UNAUTHORIZED, +} from './state-machine'; +import { formatDate } from '@/common/date'; + +export const INIT_SUCCESS = 0; +export const INIT_UNAUTHORIZED = 1; +export const INIT_RETRY = 2; +export const INIT_ERROR = 2; const serviceNames = []; const serviceClasses = []; const services = {}; -const autoSync = debounce(sync, TIMEOUT_HOUR); -let working = Promise.resolve(); +const syncLater = debounce(autoSync, TIMEOUT_HOUR); let syncConfig; +let syncMode = SYNC_MERGE; + +function getDateString() { + return formatDate('YYYY-MM-DD HH:mm:ss'); +} + +function log(type, ...args) { + console[type](`[${getDateString()}][sync]`, ...args); +} + +const logInfo = log.bind(null, 'info'); +const logError = log.bind(null, 'warn'); + +export function setSyncOnceMode(mode) { + syncMode = mode; +} export function getItemFilename({ name, uri }) { // When get or remove, current name should be prefered @@ -49,7 +95,7 @@ function initConfig() { function get(key, def) { const keys = normalizeKeys(key); keys.unshift('sync'); - return getOption(keys, def); + return getOption(keys) ?? def; } function set(key, value) { const keys = normalizeKeys(key); @@ -78,7 +124,7 @@ function serviceConfig(name) { return syncConfig.get(getKeys(key), def); } function set(key, val) { - if (typeof key === 'object') { + if (isObject(key)) { key::forEachEntry(([k, v]) => { syncConfig.set(getKeys(k), v); }); @@ -91,37 +137,20 @@ function serviceConfig(name) { } return { get, set, clear }; } -function serviceState(validStates, initialState, onChange) { - let state = initialState || validStates[0]; - function get() { - return state; - } - function set(newState) { - if (validStates.includes(newState)) { - state = newState; - if (onChange) onChange(); - } else { - console.warn('Invalid state:', newState); - } - return get(); - } - function is(states) { - return ensureArray(states).includes(state); - } - return { get, set, is }; -} export function getStates() { return serviceNames.map((name) => { const service = services[name]; + const { error } = service; return { name: service.name, displayName: service.displayName, - authState: service.authState.get(), - syncState: service.syncState.get(), + error: isObject(error) ? error.message || JSON.stringify(error) : error, + state: getSyncState(), lastSync: service.config.get('meta', {}).lastSync, progress: service.progress, properties: service.properties, - userConfig: service.getUserConfig(), + hasAuth: service.hasAuth(), + [USER_CONFIG]: service.getUserConfig(), }; }); } @@ -175,6 +204,19 @@ function parseScriptData(raw) { return data; } +function objectPurify(obj) { + // Remove keys with undefined values + if (Array.isArray(obj)) { + obj.forEach(objectPurify); + } else if (isObject(obj)) { + obj::forEachEntry(([key, value]) => { + if (typeof value === 'undefined') delete obj[key]; + else objectPurify(value); + }); + } + return obj; +} + function serviceFactory(base) { const Service = function constructor() { this.initialize(); @@ -190,310 +232,390 @@ function extendService(options) { const onStateChange = debounce(() => { sendCmd('UpdateSync', getStates()); }); +events.on('change', (state) => { + logInfo('status:', state.status); + onStateChange(); +}); export const BaseService = serviceFactory({ name: 'base', displayName: 'BaseService', - delayTime: 1000, urlPrefix: '', - metaFile: 'Violentmonkey', + metaFile: VIOLENTMONKEY, properties: { authType: 'oauth', }, + + // Methods to be implemented or overridden getUserConfig: noop, setUserConfig: noop, + requestAuth: noop, + authorize: noop, + revoke: noop, + authorized: noop, + matchAuth: noop, + finishAuth: noop, + metaError: noop, + hasAuth() { + const token = this.config.get('token'); + return !!token; + }, + initToken() { + const token = this.config.get('token'); + this.headers = token + ? { + authorization: `Bearer ${token}`, + } + : null; + return !!token; + }, + getSyncData() { + return Promise.all([this.getMeta(), this.list(), this.getLocalData()]); + }, + initialize() { this.progress = { finished: 0, total: 0, }; this.config = serviceConfig(this.name); - this.authState = serviceState([ - 'idle', - 'initializing', - 'authorizing', // in case some services require asynchronous requests to get access_tokens - 'authorized', - 'unauthorized', - 'error', - ], null, onStateChange); - this.syncState = serviceState([ - 'idle', - 'ready', - 'syncing', - 'error', - ], null, onStateChange); this.lastFetch = Promise.resolve(); - this.startSync = this.syncFactory(); - const events = getEventEmitter(); - ['on', 'off', 'fire'] - .forEach((key) => { - this[key] = (...args) => { events[key](...args); }; - }); - }, - log(...args) { - console.log(...args); // eslint-disable-line no-console }, - syncFactory() { - let promise; - let debouncedResolve; - const shouldSync = () => this.authState.is('authorized') && getCurrent() === this.name; - const getReady = () => { - if (!shouldSync()) return Promise.resolve(); - this.log('Ready to sync:', this.displayName); - this.syncState.set('ready'); - working = working.then(() => new Promise((resolve) => { - debouncedResolve = debounce(resolve, 10 * 1000); - debouncedResolve(); - })) - .then(() => { - if (shouldSync()) return this.sync(); - this.syncState.set('idle'); - }) - .catch((err) => { console.error(err); }) - .then(() => { - promise = null; - debouncedResolve = null; - }); - promise = working; - }; - function startSync() { - if (!promise) getReady(); - if (debouncedResolve) debouncedResolve(); - return promise; - } - return startSync; + logError(err) { + logError(err); + this.error = err; }, - prepareHeaders() { - this.headers = {}; + async _refreshToken() { + const refreshToken = this.config.get('refresh_token'); + if (!refreshToken) throw new Error('Invalid refresh token'); + await this.authorized({ + grant_type: 'refresh_token', + refresh_token: refreshToken, + }); + this.initToken(); }, - prepare() { - this.authState.set('initializing'); - return (this.initToken() ? Promise.resolve(this.user()) : Promise.reject({ - type: 'unauthorized', - })) - .then(() => { - this.authState.set('authorized'); - }, (err) => { - if (err && err.type === 'unauthorized') { - this.authState.set('unauthorized'); - } else { - console.error(err); - this.authState.set('error'); + async _refresh() { + setSyncState({ status: SYNC_INITIALIZING }); + let result; + try { + result = await this.requestAuth(); + if (result?.code === INIT_RETRY) { + await this._refreshToken(); + result = await this.requestAuth(); + } + if (result && ![INIT_SUCCESS, INIT_UNAUTHORIZED].includes(result.code)) { + throw result.error || { message: 'unknown refresh error' }; } - this.syncState.set('idle'); + } catch (err) { + setSyncState({ status: SYNC_ERROR_INIT }); + this.logError(err); throw err; + } + setSyncState({ + status: + result?.code === INIT_UNAUTHORIZED + ? SYNC_UNAUTHORIZED + : SYNC_AUTHORIZED, + }); + }, + prepare() { + if (this.initToken()) return this._refresh(); + setSyncState({ + status: SYNC_UNAUTHORIZED, }); }, - checkSync() { - return this.prepare() - .then(() => this.startSync()); + async handleAuth(payload) { + setSyncState({ status: SYNC_AUTHORIZING }); + try { + await this.finishAuth(payload); + } catch (err) { + setSyncState({ status: SYNC_ERROR_AUTH }); + throw err; + } + setSyncState({ status: SYNC_AUTHORIZED }); + autoSync(); }, - user: noop, - acquireLock: noop, - releaseLock: noop, - handleMetaError(err) { - throw err; + checkAuth(url) { + const payload = this.matchAuth(url); + if (payload) { + this.handleAuth(payload); + return true; + } }, getMeta() { return this.get({ name: this.metaFile }) - .then(data => JSON.parse(data)) - .catch(err => this.handleMetaError(err)) - .then(data => ({ - name: this.metaFile, - data, - })); - }, - initToken() { - this.prepareHeaders(); - const token = this.config.get('token'); - this.headers.Authorization = token ? `Bearer ${token}` : null; - return !!token; + .then((data) => JSON.parse(data)) + .catch((err) => this.metaError(err)) + .then((data) => ({ + name: this.metaFile, + data, + })); }, - loadData(options) { - const { progress } = this; - const { delay = this.delayTime } = options; - let lastFetch = Promise.resolve(); - if (delay) { - lastFetch = this.lastFetch - .then(ts => makePause(delay - (Date.now() - ts))) - .then(() => Date.now()); - this.lastFetch = lastFetch; + _requestDelay: 0, + async _request(options) { + options = Object.assign({}, NO_CACHE, options); + options.headers = Object.assign({}, this.headers, options.headers); + let { url } = options; + if (url.startsWith('/')) url = (options.prefix ?? this.urlPrefix) + url; + let delay = this._requestDelay; + let attempts = 5; + while (attempts > 0) { + attempts -= 1; + if (delay >= 200) await makePause(delay); + try { + const res = await request(url, options); + this._requestDelay >>= 1; + return res.data; + } catch (err) { + if (err?.status !== 429 || attempts <= 0) throw err; + const retryAfter = err.headers?.get('retry-after'); + const serverDelay = + retryAfter && + (isNaN(+retryAfter) + ? new Date(retryAfter).getTime() - Date.now() + : +retryAfter * 1000); + if (serverDelay) { + delay = serverDelay; + } else { + delay = Math.max(1000, delay * 2); + this._requestDelay = delay; + } + } } - progress.total += 1; - onStateChange(); - return lastFetch.then(() => { - options = Object.assign({}, options); - options.headers = Object.assign({}, this.headers, options.headers); - let { url } = options; - if (url.startsWith('/')) url = (options.prefix ?? this.urlPrefix) + url; - return request(url, options); - }) - .then(({ data }) => ({ data }), error => ({ error })) - .then(({ data, error }) => { - progress.finished += 1; + }, + _requestProcessing: false, + async _handleRequests() { + if (this._requestProcessing) return; + this._requestProcessing = true; + while (this._requestQueue.length) { + const task = this._requestQueue.shift(); + task.resolve(this._request(task.options)); + await task.promise.catch(noop); + this.progress.finished += 1; onStateChange(); - if (error) return Promise.reject(error); - return data; + } + this._requestProcessing = false; + }, + loadData(options) { + const task = { options }; + task.promise = new Promise((resolve, reject) => { + task.resolve = resolve; + task.reject = reject; }); + (this._requestQueue ||= []).push(task); + this.progress.total += 1; + onStateChange(); + this._handleRequests(); + return task.promise; }, getLocalData() { return pluginScript.list(); }, - getSyncData() { - return this.getMeta() - .then(remoteMeta => Promise.all([ - remoteMeta, - this.list(), - this.getLocalData(), - ])); + async sync() { + try { + await this.prepare(); + } catch { + // Sync in progress, ignore + } + if (getSyncState().status !== SYNC_AUTHORIZED || getCurrent() !== this.name) + return; + setSyncState({ status: SYNC_IN_PROGRESS }); + try { + await this._sync(); + logInfo('Sync finished:', this.displayName); + } catch (err) { + setSyncState({ status: SYNC_ERROR }); + logInfo('Failed syncing:', this.displayName); + this.logError(err); + throw err; + } + setSyncState({ status: SYNC_AUTHORIZED }); }, - sync() { + async _sync() { + const currentSyncMode = syncMode; + syncMode = SYNC_MERGE; this.progress = { finished: 0, total: 0, }; - this.syncState.set('syncing'); - // Avoid simultaneous requests - return this.prepare() - .then(() => this.getSyncData()) - .then(data => Promise.resolve(this.acquireLock()).then(() => data)) - .then(([remoteMeta, remoteData, localData]) => { - const remoteMetaData = remoteMeta.data || {}; - const remoteMetaInfo = remoteMetaData.info || {}; - const remoteTimestamp = remoteMetaData.timestamp || 0; - let remoteChanged = !remoteTimestamp - || Object.keys(remoteMetaInfo).length !== remoteData.length; - const now = Date.now(); - const globalLastModified = getOption('lastModified'); - const remoteItemMap = {}; - const localMeta = this.config.get('meta', {}); - const firstSync = !localMeta.timestamp; - const outdated = firstSync || remoteTimestamp > localMeta.timestamp; - this.log('First sync:', firstSync); - this.log('Outdated:', outdated, '(', 'local:', localMeta.timestamp, 'remote:', remoteTimestamp, ')'); - const putLocal = []; - const putRemote = []; - const delRemote = []; - const delLocal = []; - const updateLocal = []; - remoteMetaData.info = remoteData.reduce((info, item) => { - remoteItemMap[item.uri] = item; - let itemInfo = remoteMetaInfo[item.uri]; - if (!itemInfo) { - itemInfo = {}; - remoteChanged = true; - } - info[item.uri] = itemInfo; - if (!itemInfo.modified) { - itemInfo.modified = now; - remoteChanged = true; - } - return info; - }, {}); - localData.forEach((item) => { - const { props: { uri, position, lastModified } } = item; - const remoteInfo = remoteMetaData.info[uri]; - const remoteItem = remoteItemMap[uri]; - if (remoteInfo && remoteItem) { - if (firstSync || !lastModified || remoteInfo.modified > lastModified) { - putLocal.push({ local: item, remote: remoteItem, info: remoteInfo }); - } else { - if (remoteInfo.modified < lastModified) { - putRemote.push({ local: item, remote: remoteItem }); - remoteInfo.modified = lastModified; - remoteChanged = true; - } - if (remoteInfo.position !== position) { - if (remoteInfo.position && globalLastModified <= remoteTimestamp) { - updateLocal.push({ local: item, remote: remoteItem, info: remoteInfo }); - } else { - remoteInfo.position = position; - remoteChanged = true; - } - } - } - delete remoteItemMap[uri]; - } else if (firstSync || !outdated || lastModified > remoteTimestamp) { - putRemote.push({ local: item }); + const [remoteMeta, remoteData, localData] = await this.getSyncData(); + const remoteMetaData = remoteMeta.data || {}; + const remoteMetaInfo = remoteMetaData.info || {}; + const remoteTimestamp = remoteMetaData.timestamp || 0; + let remoteChanged = + !remoteTimestamp || + Object.keys(remoteMetaInfo).length !== remoteData.length; + const now = Date.now(); + const globalLastModified = getOption('lastModified'); + const remoteItemMap = {}; + const localMeta = this.config.get('meta', {}); + const firstSync = !localMeta.timestamp; + const outdated = firstSync || remoteTimestamp > localMeta.timestamp; + logInfo('First sync:', firstSync); + logInfo('Sync mode:', currentSyncMode); + logInfo( + 'Outdated:', + outdated, + '(', + 'local:', + localMeta.timestamp, + 'remote:', + remoteTimestamp, + ')', + ); + const putLocal = []; + const putRemote = []; + const delRemote = []; + const delLocal = []; + const updateLocal = []; + const compareItems = (localItem, remoteItem, remoteInfo) => { + if (currentSyncMode === SYNC_PUSH) return 1; + if (currentSyncMode === SYNC_PULL) return -1; + const localModified = objectGet(localItem, 'props.lastModified'); + if (localItem && remoteItem && remoteInfo) { + const remoteModified = remoteInfo.modified; + if (firstSync || !localModified || remoteModified > localModified) + return -1; + if (remoteModified < localModified) return 1; + return 0; + } + if (localItem) { + if (firstSync || !outdated || localModified > remoteTimestamp) return 1; + return -1; + } + if (remoteItem) { + if (outdated) return -1; + return 1; + } + }; + remoteMetaData.info = remoteData.reduce((info, item) => { + remoteItemMap[item.uri] = item; + let itemInfo = remoteMetaInfo[item.uri]; + if (!itemInfo) { + itemInfo = {}; + remoteChanged = true; + } + info[item.uri] = itemInfo; + if (!itemInfo.modified) { + itemInfo.modified = now; + remoteChanged = true; + } + return info; + }, {}); + localData.forEach((item) => { + const { + props: { uri, position, lastModified }, + } = item; + const remoteInfo = remoteMetaData.info[uri]; + const remoteItem = remoteItemMap[uri]; + delete remoteItemMap[uri]; + const result = compareItems(item, remoteItem, remoteInfo); + if (result < 0) { + if (remoteItem) { + putLocal.push({ + local: item, + remote: remoteItem, + info: remoteInfo, + }); } else { delLocal.push({ local: item }); } - }); - remoteItemMap::forEachEntry(([uri, item]) => { - const info = remoteMetaData.info[uri]; - if (outdated) { - putLocal.push({ remote: item, info }); - } else { - delRemote.push({ remote: item }); - } - }); - const promiseQueue = [ - ...putLocal.map(({ remote, info }) => { - this.log('Download script:', remote.uri); - return this.get(remote) - .then((raw) => { - const data = parseScriptData(raw); - // Invalid data - if (!data.code) return; - if (info.modified) objectSet(data, 'props.lastModified', info.modified); - const position = +info.position; - if (position) data.position = position; - if (!getOption('syncScriptStatus') && data.config) { - delete data.config.enabled; - } - return pluginScript.update(data); - }); - }), - ...putRemote.map(({ local, remote }) => { - this.log('Upload script:', local.props.uri); - return pluginScript.get(local.props.id) - .then((code) => { - // XXX use version 1 to be compatible with Violentmonkey on other platforms - const data = getScriptData(local, 1, { code }); - remoteMetaData.info[local.props.uri] = { - modified: local.props.lastModified, - position: local.props.position, - }; - remoteChanged = true; - return this.put( - Object.assign({}, remote, { - uri: local.props.uri, - name: null, // prefer using uri on PUT - }), - JSON.stringify(data), - ); + } else if (result > 0) { + putRemote.push({ local: item, remote: remoteItem }); + if (remoteInfo) remoteInfo.modified = lastModified; + remoteChanged = true; + } else if (remoteInfo && remoteInfo.position !== position) { + if (globalLastModified <= remoteTimestamp) { + updateLocal.push({ + local: item, + remote: remoteItem, + info: remoteInfo, }); - }), - ...delRemote.map(({ remote }) => { - this.log('Remove remote script:', remote.uri); - delete remoteMetaData.info[remote.uri]; + } else { + remoteInfo.position = position; remoteChanged = true; - return this.remove(remote); - }), - ...delLocal.map(({ local }) => { - this.log('Remove local script:', local.props.uri); - return pluginScript.remove(local.props.id); - }), - ...updateLocal.map(({ local, info }) => { - const updates = {}; - if (info.position) { - updates.props = { position: info.position }; + } + } + }); + remoteItemMap::forEachEntry(([uri, item]) => { + const info = remoteMetaData.info[uri]; + const result = compareItems(null, item, info); + if (result < 0) { + putLocal.push({ remote: item, info }); + } else { + delRemote.push({ remote: item }); + } + }); + const promiseQueue = [ + ...putLocal.map(({ remote, info }) => { + logInfo('Download script:', remote.uri); + return this.get(remote).then((raw) => { + const data = parseScriptData(raw); + // Invalid data + if (!data.code) return; + if (info.modified) + objectSet(data, 'props.lastModified', info.modified); + const position = +info.position; + if (position) data.position = position; + if (!getOption('syncScriptStatus') && data.config) { + delete data.config.enabled; } - return updateScriptInfo(local.props.id, updates); - }), - ]; - promiseQueue.push(Promise.all(promiseQueue).then(() => sortScripts()).then((changed) => { - if (!changed) return; + return pluginScript.update(data); + }); + }), + ...putRemote.map(({ local, remote }) => { + logInfo('Upload script:', local.props.uri); + return pluginScript.get(local.props.id).then((code) => { + // XXX use version 1 to be compatible with Violentmonkey on other platforms + const data = getScriptData(local, 1, { code }); + remoteMetaData.info[local.props.uri] = { + modified: local.props.lastModified, + position: local.props.position, + }; + remoteChanged = true; + return this.put( + Object.assign({}, remote, { + uri: local.props.uri, + name: null, // prefer using uri on PUT + }), + JSON.stringify(data), + ); + }); + }), + ...delRemote.map(({ remote }) => { + logInfo('Remove remote script:', remote.uri); + delete remoteMetaData.info[remote.uri]; remoteChanged = true; - return pluginScript.list() - .then((scripts) => { - scripts.forEach((script) => { - const remoteInfo = remoteMetaData.info[script.props.uri]; - if (remoteInfo) remoteInfo.position = script.props.position; + return this.remove(remote); + }), + ...delLocal.map(({ local }) => { + logInfo('Remove local script:', local.props.uri); + return pluginScript.remove(local.props.id); + }), + ...updateLocal.map(({ local, info }) => { + const updates = {}; + if (info.position) { + updates.props = { position: info.position }; + } + return updateScriptInfo(local.props.id, updates); + }), + ]; + promiseQueue.push( + Promise.all(promiseQueue) + .then(() => sortScripts()) + .then((changed) => { + if (!changed) return; + remoteChanged = true; + return pluginScript.list().then((scripts) => { + scripts.forEach((script) => { + const remoteInfo = remoteMetaData.info[script.props.uri]; + if (remoteInfo) remoteInfo.position = script.props.position; + }); }); - }); - })); - promiseQueue.push(Promise.all(promiseQueue).then(() => { + }), + ); + promiseQueue.push( + Promise.all(promiseQueue).then(() => { const promises = []; if (remoteChanged) { remoteMetaData.timestamp = Date.now(); @@ -503,21 +625,16 @@ export const BaseService = serviceFactory({ localMeta.lastSync = Date.now(); this.config.set('meta', localMeta); return Promise.all(promises); - })); - // ignore errors to ensure all promises are fulfilled - return Promise.all(promiseQueue.map(promise => promise.then(noop, err => err || true))) - .then(errors => errors.filter(Boolean)) - .then((errors) => { if (errors.length) throw errors; }); - }) - .then(() => { - this.syncState.set('idle'); - this.log('Sync finished:', this.displayName); - }, (err) => { - this.syncState.set('error'); - this.log('Failed syncing:', this.displayName); - this.log(err); - }) - .then(() => Promise.resolve(this.releaseLock()).catch(noop)); + }), + ); + // ignore errors to ensure all promises are fulfilled + return Promise.all( + promiseQueue.map((promise) => promise.then(noop, (err) => err || true)), + ) + .then((errors) => errors.filter(Boolean)) + .then((errors) => { + if (errors.length) throw errors; + }); }, }); @@ -540,31 +657,30 @@ export function initialize() { services[name] = service; }); } - sync(); + resetSyncState(); + return autoSync(); } -function syncOne(service) { - if (service.syncState.is(['ready', 'syncing'])) return; - if (service.authState.is(['idle', 'error'])) return service.checkSync(); - if (service.authState.is('authorized')) return service.startSync(); -} export function sync() { const service = getService(); - return service && Promise.resolve(syncOne(service)).then(autoSync); + return service && Promise.resolve(service.sync()).then(syncLater); } -export function checkAuthUrl(url) { - return serviceNames.some((name) => { - const service = services[name]; - const authorized = service.checkAuth && service.checkAuth(url); - return authorized; - }); +export function autoSync() { + if (!getOption('syncAutomatically')) { + console.info('[sync] auto-sync disabled, check later'); + const service = getService(); + service.prepare(); + return syncLater(); + } + sync(); } export function authorize() { const service = getService(); if (service) service.authorize(); } + export function revoke() { const service = getService(); if (service) service.revoke(); @@ -574,11 +690,74 @@ export function setConfig(config) { const service = getService(); if (service) { service.setUserConfig(config); - return service.checkSync(); + return service.sync(); } } -hookOptions((data) => { - const value = data?.['sync.current']; - if (value) initialize(); -}); +let unregister; + +export async function openAuthPage(url, redirectUri) { + unregister?.(); // otherwise our new tabId will be ignored + const tabId = (await browser.tabs.create({ url })).id; + /** + * @param {chrome.webRequest.WebResponseDetails} info + * @returns {chrome.webRequest.BlockingResponse} + */ + const handler = (info) => { + if (getService().checkAuth?.(info.url)) { + // When onBeforeRequest occurs for initial requests intercepted by service worker, + // info.tabId will be -1 on Chromium based browsers, use tabId instead. + // tested on Chrome / Edge / Brave + browser.tabs.remove(tabId); + // If we unregister without setTimeout, API will ignore { cancel: true } + setTimeout(unregister, 0); + return { cancel: true }; + } + }; + unregister = () => { + browser.webRequest.onBeforeRequest.removeListener(handler); + }; + // Note: match pattern does not support port number + // - In Chrome, the port number is ignored and the pattern still works + // - In Firefox, the pattern is ignored and won't match any URL + redirectUri = redirectUri.replace(/:\d+/, ''); + browser.webRequest.onBeforeRequest.addListener( + handler, + { + // Do not filter by tabId here, see above + urls: [`${redirectUri}*`], + types: ['main_frame', 'xmlhttprequest'], // fetch request in service worker + }, + ['blocking'], + ); +} + +const base64urlMapping = { + '+': '-', + '/': '_', +}; + +async function sha256b64url(code) { + const bin = new TextEncoder().encode(code); + const buffer = await crypto.subtle.digest('SHA-256', bin); + const b64 = btoa(buffer2string(buffer)); + return b64.replace(/[+/=]/g, (m) => base64urlMapping[m] || ''); +} + +/** + * Create a unique string between 43 and 128 characters long. + * + * Ref: RFC 7636 + */ +export function getCodeVerifier() { + return getRandomString(43, 128); +} + +export async function getCodeChallenge(codeVerifier) { + const method = 'S256'; + const challenge = await sha256b64url(codeVerifier); + return { + code_challenge: challenge, + code_challenge_method: method, + }; +} diff --git a/src/background/sync/dropbox.js b/src/background/sync/dropbox.js index 03fddb39c2..6009bf14b7 100644 --- a/src/background/sync/dropbox.js +++ b/src/background/sync/dropbox.js @@ -1,15 +1,26 @@ -import { loadQuery, dumpQuery } from '../utils'; +import { dumpQuery, getUniqId, loadQuery } from '@/common'; +import { FORM_URLENCODED, VM_HOME } from '@/common/consts'; import { - getURI, getItemFilename, BaseService, isScriptFile, register, + BaseService, + getCodeChallenge, + getCodeVerifier, + getItemFilename, + getURI, + INIT_ERROR, + INIT_RETRY, + isScriptFile, + openAuthPage, + register, } from './base'; const config = { - client_id: 'f0q12zup2uys5w8', - redirect_uri: 'https://violentmonkey.github.io/auth_dropbox.html', + client_id: process.env.SYNC_DROPBOX_CLIENT_ID, + redirect_uri: VM_HOME + 'auth_dropbox.html', }; const escRE = /[\u007f-\uffff]/g; // eslint-disable-line no-control-regex -const escFunc = m => `\\u${(m.charCodeAt(0) + 0x10000).toString(16).slice(1)}`; +const escFunc = (m) => + `\\u${(m.charCodeAt(0) + 0x10000).toString(16).slice(1)}`; function jsonStringifySafe(obj) { const string = JSON.stringify(obj); @@ -19,38 +30,56 @@ function jsonStringifySafe(obj) { const Dropbox = BaseService.extend({ name: 'dropbox', displayName: 'Dropbox', - user() { - return this.loadData({ - method: 'POST', - url: 'https://api.dropboxapi.com/2/users/get_current_account', - }) - .catch((err) => { + async requestAuth() { + try { + await this.loadData({ + method: 'POST', + url: 'https://api.dropboxapi.com/2/users/get_current_account', + }); + } catch (err) { + let code = INIT_ERROR; if (err.status === 401) { - return Promise.reject({ - type: 'unauthorized', - }); + code = INIT_RETRY; } - return Promise.reject({ - type: 'error', - data: err, - }); - }); + return { code, error: err }; + } }, - handleMetaError(res) { + metaError(res) { if (res.status !== 409) throw res; }, - list() { - return this.loadData({ + async list() { + let files = []; + let data = await this.loadData({ method: 'POST', url: 'https://api.dropboxapi.com/2/files/list_folder', body: { path: '', }, responseType: 'json', - }) - .then(data => ( - data.entries.filter(item => item['.tag'] === 'file' && isScriptFile(item.name)).map(normalize) - )); + }); + files = [ + ...files, + ...data.entries + .filter((item) => item['.tag'] === 'file' && isScriptFile(item.name)) + .map(normalize), + ]; + while (data.has_more) { + data = await this.loadData({ + method: 'POST', + url: 'https://api.dropboxapi.com/2/files/list_folder/continue', + body: { + cursor: data.cursor, + }, + responseType: 'json', + }); + files = [ + ...files, + ...data.entries + .filter((item) => item['.tag'] === 'file' && isScriptFile(item.name)) + .map(normalize), + ]; + } + return files; }, get(item) { const name = getItemFilename(item); @@ -78,8 +107,7 @@ const Dropbox = BaseService.extend({ }, body: data, responseType: 'json', - }) - .then(normalize); + }).then(normalize); }, remove(item) { const name = getItemFilename(item); @@ -90,44 +118,73 @@ const Dropbox = BaseService.extend({ path: `/${name}`, }, responseType: 'json', - }) - .then(normalize); + }).then(normalize); }, - authorize() { + async authorize() { + this.session = { + state: getUniqId(), + codeVerifier: getCodeVerifier(), + }; const params = { - response_type: 'token', + response_type: 'code', + token_access_type: 'offline', client_id: config.client_id, redirect_uri: config.redirect_uri, + state: this.session.state, + ...(await getCodeChallenge(this.session.codeVerifier)), }; const url = `https://www.dropbox.com/oauth2/authorize?${dumpQuery(params)}`; - browser.tabs.create({ url }); + openAuthPage(url, config.redirect_uri); }, - authorized(raw) { - const data = loadQuery(raw); - if (data.access_token) { - this.config.set({ - uid: data.uid, - token: data.access_token, - }); - } + async authorized(params) { + const data = await this.loadData({ + method: 'POST', + url: 'https://api.dropbox.com/oauth2/token', + headers: { + 'Content-Type': FORM_URLENCODED, + }, + body: dumpQuery({ + client_id: config.client_id, + ...params, + }), + responseType: 'json', + }); + if (!data.access_token) throw data; + this.config.set({ + token: data.access_token, + refresh_token: data.refresh_token || params.refresh_token, + }); }, - checkAuth(url) { - const redirectUri = `${config.redirect_uri}#`; - if (url.startsWith(redirectUri)) { - this.authorized(url.slice(redirectUri.length)); - this.checkSync(); - return true; - } + matchAuth(url) { + const redirectUri = `${config.redirect_uri}?`; + if (!url.startsWith(redirectUri)) return; + const query = loadQuery(url.slice(redirectUri.length)); + const { state, codeVerifier } = this.session || {}; + this.session = null; + if (query.state !== state || !query.code) return; + return { + code: query.code, + code_verifier: codeVerifier, + }; + }, + async finishAuth(payload) { + await this.authorized({ + code: payload.code, + code_verifier: payload.code_verifier, + grant_type: 'authorization_code', + redirect_uri: config.redirect_uri, + }); }, revoke() { this.config.set({ uid: null, token: null, + refresh_token: null, }); return this.prepare(); }, }); -register(Dropbox); +if (config.client_id) register(Dropbox); function normalize(item) { return { diff --git a/src/background/sync/googledrive.js b/src/background/sync/googledrive.js index 541d1c9848..6cdd301a15 100644 --- a/src/background/sync/googledrive.js +++ b/src/background/sync/googledrive.js @@ -1,85 +1,82 @@ // Reference: +// - https://developers.google.com/identity/protocols/oauth2/native-app // - https://developers.google.com/drive/v3/reference/files -// - https://github.com/google/google-api-nodejs-client -import { getUniqId, noop } from '#/common'; -import { objectGet } from '#/common/object'; -import { dumpQuery, notify } from '../utils'; +// +// Note: +// - Use a native app approach for longer authorization periods, +// - Web app refresh tokens have short expiration and require frequent user reauthorization. +import { dumpQuery, getUniqId, loadQuery } from '@/common'; +import { CHARSET_UTF8, FORM_URLENCODED } from '@/common/consts'; +import { objectGet } from '@/common/object'; import { - getURI, getItemFilename, BaseService, register, isScriptFile, + BaseService, + getCodeChallenge, + getCodeVerifier, + getItemFilename, + getURI, + INIT_ERROR, + INIT_RETRY, + INIT_SUCCESS, + isScriptFile, + openAuthPage, + register, } from './base'; -const SECRET_KEY = JSON.parse(window.atob('eyJjbGllbnRfc2VjcmV0IjoiTjBEbTZJOEV3bkJaeE1xMUpuMHN3UER0In0=')); -const config = Object.assign({ - client_id: '590447512361-05hjbhnf8ua3iha55e5pgqg15om0cpef.apps.googleusercontent.com', - redirect_uri: 'https://violentmonkey.github.io/auth_googledrive.html', +const config = { + client_id: process.env.SYNC_GOOGLE_DESKTOP_ID, + client_secret: process.env.SYNC_GOOGLE_DESKTOP_SECRET, + // Google OAuth for native app only allows loopback IP address for callback URL. + // The URL will be intercepted and blocked so the port doesn't matter. + redirect_uri: 'http://127.0.0.1:45678/', + // redirect_uri: VM_HOME + 'auth_googledrive.html', scope: 'https://www.googleapis.com/auth/drive.appdata', -}, SECRET_KEY); -const UNAUTHORIZED = { status: 'UNAUTHORIZED' }; +}; const GoogleDrive = BaseService.extend({ name: 'googledrive', displayName: 'Google Drive', urlPrefix: 'https://www.googleapis.com/drive/v3', - refreshToken() { - const refreshToken = this.config.get('refresh_token'); - if (!refreshToken) return Promise.reject({ type: 'unauthorized' }); - return this.authorized({ - refresh_token: refreshToken, - grant_type: 'refresh_token', - }) - .then(() => this.prepare()); - }, - user() { - const requestUser = () => this.loadData({ - url: `https://www.googleapis.com/oauth2/v3/tokeninfo?${dumpQuery({ - access_token: this.config.get('token'), - })}`, - responseType: 'json', - }); - return requestUser() - .then((info) => { - // If access was granted with access_type=online, revoke it. - if (info.access_type === 'online') { - return this.loadData({ - method: 'POST', - url: `https://accounts.google.com/o/oauth2/revoke?token=${this.config.get('token')}`, - prefix: '', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - }) - .then(() => { - notify({ - title: 'Sync Upgraded', - body: 'Please reauthorize access to your Google Drive to complete the upgradation.', - }); - return Promise.reject('Online access revoked.'); - }); - } - if (info.scope !== config.scope) return Promise.reject(UNAUTHORIZED); - }) - .catch((res) => { - if (res === UNAUTHORIZED || res.status === 400 && objectGet(res, 'data.error_description') === 'Invalid Value') { - return this.refreshToken().then(requestUser); - } - return Promise.reject({ - type: 'error', - data: res, + async requestAuth() { + let code = INIT_SUCCESS; + let error; + try { + const res = await this.loadData({ + url: `https://www.googleapis.com/oauth2/v3/tokeninfo?${dumpQuery({ + access_token: this.config.get('token'), + })}`, + responseType: 'json', }); - }); + if (res.scope !== config.scope) code = INIT_RETRY; + } catch (err) { + error = err; + code = INIT_ERROR; + if ( + err.status === 400 && + objectGet(err, 'data.error_description') === 'Invalid Value' + ) { + code = INIT_RETRY; + } + } + return { code, error }; }, - getSyncData() { + async getSyncData() { const params = { spaces: 'appDataFolder', - fields: 'files(id,name,size)', + fields: 'files(id,name,size),nextPageToken', }; - return this.loadData({ - url: `/files?${dumpQuery(params)}`, - responseType: 'json', - }) - .then(({ files }) => { - let metaFile; - const remoteData = files.filter((item) => { + let files = []; + while (true) { + const result = await this.loadData({ + url: `/files?${dumpQuery(params)}`, + responseType: 'json', + }); + files = [...files, ...result.files]; + params.pageToken = result.nextPageToken; + if (!params.pageToken) break; + } + let metaFile; + const remoteData = files + .filter((item) => { if (isScriptFile(item.name)) return true; if (!metaFile && item.name === this.metaFile) { metaFile = item; @@ -96,40 +93,57 @@ const GoogleDrive = BaseService.extend({ } return true; }); - const metaItem = metaFile ? normalize(metaFile) : {}; - const gotMeta = this.get(metaItem) - .then(data => JSON.parse(data)) - .catch(err => this.handleMetaError(err)) - .then(data => Object.assign({}, metaItem, { - name: this.metaFile, - uri: null, - data, - })); - return Promise.all([gotMeta, remoteData, this.getLocalData()]); - }); + const metaItem = metaFile ? normalize(metaFile) : {}; + const gotMeta = this.get(metaItem) + .then((data) => JSON.parse(data)) + .catch((err) => this.metaError(err)) + .then((data) => + Object.assign({}, metaItem, { + name: this.metaFile, + uri: null, + data, + }), + ); + return Promise.all([gotMeta, remoteData, this.getLocalData()]); }, - authorize() { + async authorize() { + this.session = { + state: getUniqId(), + codeVerifier: getCodeVerifier(), + }; const params = { response_type: 'code', - access_type: 'offline', client_id: config.client_id, redirect_uri: config.redirect_uri, scope: config.scope, + state: this.session.state, + ...(await getCodeChallenge(this.session.codeVerifier)), }; if (!this.config.get('refresh_token')) params.prompt = 'consent'; - const url = `https://accounts.google.com/o/oauth2/v2/auth?${dumpQuery(params)}`; - browser.tabs.create({ url }); + const url = `https://accounts.google.com/o/oauth2/v2/auth?${dumpQuery( + params, + )}`; + openAuthPage(url, config.redirect_uri); }, - checkAuth(url) { - const redirectUri = `${config.redirect_uri}?code=`; - if (url.startsWith(redirectUri)) { - this.authState.set('authorizing'); - this.authorized({ - code: decodeURIComponent(url.split('#')[0].slice(redirectUri.length)), - }) - .then(() => this.checkSync()); - return true; - } + matchAuth(url) { + const redirectUri = `${config.redirect_uri}?`; + if (!url.startsWith(redirectUri)) return; + const query = loadQuery(url.slice(redirectUri.length)); + const { state, codeVerifier } = this.session || {}; + this.session = null; + if (query.state !== state || !query.code) return; + return { + code: query.code, + code_verifier: codeVerifier, + }; + }, + async finishAuth(payload) { + await this.authorized({ + code: payload.code, + code_verifier: payload.code_verifier, + grant_type: 'authorization_code', + redirect_uri: config.redirect_uri, + }); }, revoke() { this.config.set({ @@ -138,42 +152,37 @@ const GoogleDrive = BaseService.extend({ }); return this.prepare(); }, - authorized(params) { - return this.loadData({ + async authorized(params) { + const data = await this.loadData({ method: 'POST', url: 'https://www.googleapis.com/oauth2/v4/token', prefix: '', headers: { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': FORM_URLENCODED, }, - body: dumpQuery(Object.assign({}, { - client_id: config.client_id, - client_secret: config.client_secret, - redirect_uri: config.redirect_uri, - grant_type: 'authorization_code', - }, params)), + body: dumpQuery( + Object.assign( + {}, + { + client_id: config.client_id, + client_secret: config.client_secret, + }, + params, + ), + ), responseType: 'json', - }) - .then((data) => { - if (data.access_token) { - const update = { - token: data.access_token, - }; - if (data.refresh_token) { - update.refresh_token = data.refresh_token; - } - this.config.set(update); - } else { - throw data; - } + }); + if (!data.access_token) throw data; + this.config.set({ + token: data.access_token, + refresh_token: data.refresh_token || params.refresh_token, }); }, - handleMetaError: noop, list() { throw new Error('Not supported'); }, get({ id }) { - if (!id) return Promise.reject(); + if (!id) throw new Error('Invalid file ID'); return this.loadData({ url: `/files/${id}?alt=media`, }); @@ -185,15 +194,17 @@ const GoogleDrive = BaseService.extend({ const headers = { 'Content-Type': `multipart/related; boundary=${boundary}`, }; - const metadata = id ? { - name, - } : { - name, - parents: ['appDataFolder'], - }; + const metadata = id + ? { + name, + } + : { + name, + parents: ['appDataFolder'], + }; const body = [ `--${boundary}`, - 'Content-Type: application/json; charset=UTF-8', + 'Content-Type: application/json; ' + CHARSET_UTF8, '', JSON.stringify(metadata), `--${boundary}`, @@ -220,7 +231,7 @@ const GoogleDrive = BaseService.extend({ }); }, }); -register(GoogleDrive); +if (config.client_id && config.client_secret) register(GoogleDrive); function normalize(item) { return { diff --git a/src/background/sync/index.js b/src/background/sync/index.js index 4800b0493a..6709e5dbbc 100644 --- a/src/background/sync/index.js +++ b/src/background/sync/index.js @@ -1,33 +1,58 @@ +import { SYNC_MERGE } from '@/common/consts-sync'; +import { addOwnCommands, hookOptionsInit } from '../utils'; +import { S_CODE_PRE, S_SCRIPT_PRE } from '../utils/storage'; +import { onStorageChanged } from '../utils/storage-cache'; import { - checkAuthUrl, - initialize, - sync, - getStates, authorize, + autoSync, + getStates, + initialize, revoke, setConfig, + setSyncOnceMode, + sync, } from './base'; import './dropbox'; -import './onedrive'; import './googledrive'; +import './onedrive'; import './webdav'; -import { commands } from '../utils/message'; -Object.assign(commands, { +const keysToSyncRe = new RegExp(`^(?:${[S_SCRIPT_PRE, S_CODE_PRE].join('|')})`); +let unwatch; + +hookOptionsInit((changes, firstRun) => { + if (firstRun || 'sync.current' in changes) reconfigure(); +}); + +addOwnCommands({ SyncAuthorize: authorize, + SyncGetStates: getStates, SyncRevoke: revoke, - SyncStart: sync, SyncSetConfig: setConfig, + SyncStart(mode) { + setSyncOnceMode(mode || SYNC_MERGE); + sync(); + }, }); -browser.tabs.onUpdated.addListener((tabId, changes) => { - if (changes.url && checkAuthUrl(changes.url)) browser.tabs.remove(tabId); -}); +function reconfigure() { + if (initialize()) { + if (!unwatch) { + unwatch = onStorageChanged(dbSentry); + } + } else { + if (unwatch) { + unwatch(); + unwatch = null; + } + } +} -export { - initialize, - sync, - getStates, - authorize, - revoke, -}; +function dbSentry({ keys }) { + for (const k of keys) { + if (keysToSyncRe.test(k)) { + autoSync(); + break; + } + } +} diff --git a/src/background/sync/onedrive.js b/src/background/sync/onedrive.js index 09e2709e7b..865fdd0d79 100644 --- a/src/background/sync/onedrive.js +++ b/src/background/sync/onedrive.js @@ -1,80 +1,77 @@ -// Reference: https://dev.onedrive.com/README.htm -import { noop } from '#/common'; -import { objectGet } from '#/common/object'; -import { dumpQuery } from '../utils'; +// References +// - https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow +// +// Note: +// - SPA refresh tokens expire after 24h, but each refresh operation returns a new refresh_token, extending the expiration. +// - Browser extensions cannot use the native app authorization flow due to Microsoft's restrictions. +import { dumpQuery, getUniqId, loadQuery, noop } from '@/common'; +import { FORM_URLENCODED, VM_HOME } from '@/common/consts'; +import { objectGet } from '@/common/object'; import { - getURI, getItemFilename, BaseService, isScriptFile, register, + BaseService, + getCodeChallenge, + getCodeVerifier, + getItemFilename, + getURI, + INIT_ERROR, + INIT_RETRY, + INIT_UNAUTHORIZED, + isScriptFile, + openAuthPage, + register, } from './base'; -const SECRET_KEY = JSON.parse(window.atob('eyJjbGllbnRfc2VjcmV0Ijoiajl4M09WRXRIdmhpSEtEV09HcXV5TWZaS2s5NjA0MEgifQ==')); -const config = Object.assign({ - client_id: '000000004418358A', - redirect_uri: 'https://violentmonkey.github.io/auth_onedrive.html', -}, SECRET_KEY); +const config = { + client_id: process.env.SYNC_ONEDRIVE_CLIENT_ID, + redirect_uri: VM_HOME + 'auth_onedrive.html', +}; const OneDrive = BaseService.extend({ name: 'onedrive', displayName: 'OneDrive', - urlPrefix: 'https://api.onedrive.com/v1.0', - refreshToken() { - const refreshToken = this.config.get('refresh_token'); - return this.authorized({ - refresh_token: refreshToken, - grant_type: 'refresh_token', - }) - .then(() => this.prepare()); - }, - user() { - const requestUser = () => this.loadData({ - url: '/drive', - responseType: 'json', - }); - return requestUser() - .catch((res) => { - if (res.status === 401) { - return this.refreshToken().then(requestUser); - } - throw res; - }) - .catch((res) => { - if (res.status === 400 && objectGet(res, 'data.error') === 'invalid_grant') { - return Promise.reject({ - type: 'unauthorized', - }); - } - return Promise.reject({ - type: 'error', - data: res, + urlPrefix: 'https://graph.microsoft.com/v1.0', + async requestAuth() { + try { + await this.loadData({ + url: '/drive/special/approot', + responseType: 'json', }); - }); - }, - handleMetaError(res) { - if (res.status === 404) { - const header = res.headers.get('WWW-Authenticate')?.[0] || ''; - if (/^Bearer realm="OneDriveAPI"/.test(header)) { - return this.refreshToken().then(() => this.getMeta()); + } catch (err) { + let code = INIT_ERROR; + if (err.status === 401) { + code = INIT_RETRY; + } else if ( + err.status === 400 && + objectGet(err, 'data.error') === 'invalid_grant' + ) { + code = INIT_UNAUTHORIZED; } - return; + return { code, error: err }; } - throw res; }, - list() { - return this.loadData({ - url: '/drive/special/approot/children', - responseType: 'json', - }) - .then(data => data.value.filter(item => item.file && isScriptFile(item.name)).map(normalize)); + async list() { + let files = []; + let url = '/drive/special/approot/children'; + while (url) { + const data = await this.loadData({ + url, + responseType: 'json', + }); + url = data['@odata.nextLink'] || ''; + files = [ + ...files, + ...data.value + .filter((item) => item.file && isScriptFile(item.name)) + .map(normalize), + ]; + } + return files; }, get(item) { const name = getItemFilename(item); return this.loadData({ - url: `/drive/special/approot:/${encodeURIComponent(name)}`, - responseType: 'json', - }) - .then(data => this.loadData({ - url: data['@content.downloadUrl'], - delay: false, - })); + url: `/drive/special/approot:/${encodeURIComponent(name)}:/content`, + }); }, put(item, data) { const name = getItemFilename(item); @@ -86,8 +83,7 @@ const OneDrive = BaseService.extend({ }, body: data, responseType: 'json', - }) - .then(normalize); + }).then(normalize); }, remove(item) { // return 204 @@ -95,29 +91,45 @@ const OneDrive = BaseService.extend({ return this.loadData({ method: 'DELETE', url: `/drive/special/approot:/${encodeURIComponent(name)}`, - }) - .catch(noop); + }).catch(noop); }, - authorize() { + async authorize() { + this.session = { + state: getUniqId(), + codeVerifier: getCodeVerifier(), + }; const params = { client_id: config.client_id, - scope: 'onedrive.appfolder wl.offline_access', + scope: 'openid profile Files.ReadWrite.AppFolder offline_access', response_type: 'code', redirect_uri: config.redirect_uri, + state: this.session.state, + ...(await getCodeChallenge(this.session.codeVerifier)), }; - const url = `https://login.live.com/oauth20_authorize.srf?${dumpQuery(params)}`; - browser.tabs.create({ url }); + const url = `https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?${dumpQuery( + params, + )}`; + openAuthPage(url, config.redirect_uri); }, - checkAuth(url) { - const redirectUri = `${config.redirect_uri}?code=`; - if (url.startsWith(redirectUri)) { - this.authState.set('authorizing'); - this.authorized({ - code: url.slice(redirectUri.length), - }) - .then(() => this.checkSync()); - return true; - } + matchAuth(url) { + const redirectUri = `${config.redirect_uri}?`; + if (!url.startsWith(redirectUri)) return; + const query = loadQuery(url.slice(redirectUri.length)); + const { state, codeVerifier } = this.session || {}; + this.session = null; + if (query.state !== state || !query.code) return; + return { + code: query.code, + code_verifier: codeVerifier, + }; + }, + async finishAuth(payload) { + await this.authorized({ + code: payload.code, + code_verifier: payload.code_verifier, + grant_type: 'authorization_code', + redirect_uri: config.redirect_uri, + }); }, revoke() { this.config.set({ @@ -127,36 +139,32 @@ const OneDrive = BaseService.extend({ }); return this.prepare(); }, - authorized(params) { - return this.loadData({ + async authorized(params) { + const data = await this.loadData({ method: 'POST', - url: 'https://login.live.com/oauth20_token.srf', + url: 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token', prefix: '', headers: { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': FORM_URLENCODED, }, - body: dumpQuery(Object.assign({}, { - client_id: config.client_id, - client_secret: config.client_secret, - redirect_uri: config.redirect_uri, - grant_type: 'authorization_code', - }, params)), + body: dumpQuery( + Object.assign( + { + client_id: config.client_id, + }, + params, + ), + ), responseType: 'json', - }) - .then((data) => { - if (data.access_token) { - this.config.set({ - uid: data.user_id, - token: data.access_token, - refresh_token: data.refresh_token, - }); - } else { - throw data; - } + }); + if (!data.access_token) throw data; + this.config.set({ + token: data.access_token, + refresh_token: data.refresh_token || params.refresh_token, }); }, }); -register(OneDrive); +if (config.client_id) register(OneDrive); function normalize(item) { return { diff --git a/src/background/sync/state-machine.js b/src/background/sync/state-machine.js new file mode 100644 index 0000000000..b3c7150304 --- /dev/null +++ b/src/background/sync/state-machine.js @@ -0,0 +1,54 @@ +import { getEventEmitter } from '../utils'; + +export const SYNC_IDLE = 1; +export const SYNC_UNAUTHORIZED = 2; +export const SYNC_AUTHORIZING = 3; +export const SYNC_AUTHORIZED = 4; +export const SYNC_INITIALIZING = 5; +export const SYNC_IN_PROGRESS = 6; +export const SYNC_ERROR_INIT = 7; +export const SYNC_ERROR_AUTH = 8; +export const SYNC_ERROR = 9; + +const stateMap = { + [SYNC_IDLE]: [SYNC_INITIALIZING, SYNC_UNAUTHORIZED], + [SYNC_INITIALIZING]: [SYNC_AUTHORIZED, SYNC_ERROR_INIT, SYNC_UNAUTHORIZED], + [SYNC_UNAUTHORIZED]: [SYNC_AUTHORIZING], + [SYNC_AUTHORIZING]: [SYNC_ERROR_AUTH, SYNC_AUTHORIZED, SYNC_UNAUTHORIZED], + [SYNC_AUTHORIZED]: [SYNC_UNAUTHORIZED, SYNC_INITIALIZING, SYNC_IN_PROGRESS], + [SYNC_IN_PROGRESS]: [SYNC_AUTHORIZED, SYNC_ERROR], + [SYNC_ERROR]: [SYNC_UNAUTHORIZED, SYNC_AUTHORIZING, SYNC_INITIALIZING], + [SYNC_ERROR_INIT]: [SYNC_UNAUTHORIZED, SYNC_AUTHORIZING, SYNC_INITIALIZING], + [SYNC_ERROR_AUTH]: [SYNC_UNAUTHORIZED, SYNC_AUTHORIZING], +}; + +export const events = getEventEmitter(); + +let state = defaultSyncState(); + +function defaultSyncState() { + return { + status: SYNC_IDLE, + }; +} + +function onUpdated() { + events.fire('change', state); +} + +export function resetSyncState() { + state = defaultSyncState(); + onUpdated(); +} + +export function getSyncState() { + return state; +} + +export function setSyncState(update) { + if (update.status && !stateMap[state.status].includes(update.status)) { + throw new Error(`Cannot move from ${state.status} to ${update.status}`); + } + state = { ...state, ...update }; + onUpdated(); +} diff --git a/src/background/sync/webdav.js b/src/background/sync/webdav.js index b3f3c6bccc..1065c0cb2f 100644 --- a/src/background/sync/webdav.js +++ b/src/background/sync/webdav.js @@ -1,5 +1,17 @@ +import { tryUrl } from '@/common'; import { - getURI, getItemFilename, BaseService, isScriptFile, register, + ANONYMOUS, + PASSWORD, + SERVER_URL, + USER_CONFIG, + USERNAME, +} from '@/common/consts-sync'; +import { + BaseService, + getItemFilename, + getURI, + isScriptFile, + register, } from './base'; const KEY_CHILDREN = Symbol('children'); @@ -56,8 +68,9 @@ class XNode { children() { if (!this[KEY_CHILDREN]) { const { node, nsMap } = this; - this[KEY_CHILDREN] = [...node.children] - .map(child => new XNode(child, nsMap)); + this[KEY_CHILDREN] = [...node.children].map( + (child) => new XNode(child, nsMap), + ); } return this[KEY_CHILDREN]; } @@ -68,7 +81,10 @@ class XNode { getCallback(callback) { if (typeof callback === 'string') { - return (tagName => node => node.name === tagName)(callback); + return ( + (tagName) => (node) => + node.name === tagName + )(callback); } return callback; } @@ -87,191 +103,116 @@ class XNode { } const DEFAULT_CONFIG = { - serverUrl: '', - anonymous: false, - username: '', - password: '', + [SERVER_URL]: '', + [ANONYMOUS]: false, + [USERNAME]: '', + [PASSWORD]: '', }; const WebDAV = BaseService.extend({ name: 'webdav', displayName: 'WebDAV', properties: { - authType: 'password', - serverUrl: null, + authType: PASSWORD, + [SERVER_URL]: null, }, getUserConfig() { - if (!this.userConfig) { - this.userConfig = { - ...DEFAULT_CONFIG, - ...this.config.get('userConfig'), - }; - } - return this.userConfig; + return (this[USER_CONFIG] ||= { + ...DEFAULT_CONFIG, + ...this.config.get(USER_CONFIG), + }); }, setUserConfig(config) { - Object.assign(this.userConfig, config); - this.config.set('userConfig', this.userConfig); + Object.assign(this[USER_CONFIG], config); + this.config.set(USER_CONFIG, this[USER_CONFIG]); }, initToken() { - this.prepareHeaders(); const config = this.getUserConfig(); - let url = config.serverUrl?.trim() || ''; + let url = config[SERVER_URL]?.trim() || ''; if (!url.includes('://')) url = `http://${url}`; if (!url.endsWith('/')) url += '/'; - try { - new URL(url); // eslint-disable-line no-new - } catch (e) { - this.properties.serverUrl = null; + if (!tryUrl(url)) { + this.properties[SERVER_URL] = null; return false; } - this.properties.serverUrl = `${url}Violentmonkey/`; + this.properties[SERVER_URL] = `${url}${VIOLENTMONKEY}/`; const { anonymous, username, password } = config; if (anonymous) return true; if (!username || !password) return false; const auth = window.btoa(`${username}:${password}`); - this.headers.Authorization = `Basic ${auth}`; + this.headers = { Authorization: `Basic ${auth}` }; return true; }, + async requestAuth() { + await this.list(); + }, loadData(options) { // Bypassing login CSRF protection in Nextcloud / Owncloud by not sending cookies. // We are not using web UI and cookie authentication, so we don't have to worry about that. // See https://github.com/violentmonkey/violentmonkey/issues/976 - return BaseService.prototype.loadData.call(this, Object.assign({ - credentials: 'omit', - }, options)); + return BaseService.prototype.loadData.call( + this, + Object.assign( + { + credentials: 'omit', + }, + options, + ), + ); }, - handleMetaError(res) { - if (![ - 404, // File not exists - 409, // Directory not exists - ].includes(res.status)) throw res; + metaError(res) { + if ( + ![ + 404, // File not exists + 409, // Directory not exists + ].includes(res.status) + ) + throw res; }, - // Some WebDAV servers do not allow LOCK / UNLOCK - /* - acquireLock() { + list() { const { serverUrl } = this.properties; - const createLock = () => { - this.log('Acquire lock...'); - return this.loadData({ - method: 'LOCK', + const mkdir = () => + this.loadData({ + method: 'MKCOL', url: serverUrl, - headers: { - Timeout: `Second-${30 * 60}`, - }, - body: `\ - - - - -`, - }) - .then(xml => { - const doc = XNode.fromXML(xml); - const lock = doc.find('DAV:prop') - .find('DAV:lockdiscovery') - .find('DAV:activelock') - .find('DAV:locktoken') - .find('DAV:href') - .text(); - this.log('Acquired lock:', lock); - this.config.set({ - lock, - }); }); - }; - const lock = this.config.get('lock'); - if (lock) { - this.log('Refresh lock:', lock); - return this.loadData({ - method: 'LOCK', + const readdir = () => + this.loadData({ + method: 'PROPFIND', url: serverUrl, headers: { - If: `(<${lock}>)`, + depth: '1', }, - }) - .then(() => { - this.log('Refreshed lock:', lock); - }, err => { - if (err.status === 412) { - this.log('Refresh lock error'); - this.config.set({ lock: null }); - // Precondition Failed - return createLock(); - } - throw err; - }); - } - return createLock(); - }, - releaseLock() { - const lock = this.config.get('lock'); - if (lock) { - const { serverUrl } = this.properties; - this.log('Release lock:', lock); - return this.loadData({ - method: 'UNLOCK', - url: serverUrl, - headers: { - 'Lock-Token': `<${lock}>`, - }, - }) - .then(() => { - this.log('Released lock'); - }, () => { - this.log('Release lock error'); - }) - .then(() => { - this.config.set({ lock: null }); + }).then((xml) => { + const doc = XNode.fromXML(xml); + const items = doc + .children()[0] + .map((node) => { + const prop = node.find('DAV:propstat').find('DAV:prop'); + const type = prop.find('DAV:resourcetype').find('DAV:collection') + ? 'directory' + : 'file'; + if (type === 'file') { + const displayNameNode = prop.find('DAV:displayname'); + const displayName = decodeURIComponent( + displayNameNode + ? displayNameNode.text() // some servers also encode DAV:displayname + : node.find('DAV:href').text().split('/').pop(), // extracting file name + ); + if (isScriptFile(displayName)) { + const size = prop.find('DAV:getcontentlength'); + return normalize({ + name: displayName, + size: size ? +size.text() : 0, + }); + } + } + return null; + }) + .filter(Boolean); + return items; }); - } - }, - */ - list() { - const { serverUrl } = this.properties; - const mkdir = () => this.loadData({ - method: 'MKCOL', - url: serverUrl, - }); - const readdir = () => this.loadData({ - method: 'PROPFIND', - url: serverUrl, - headers: { - depth: '1', - }, - }) - .then((xml) => { - const doc = XNode.fromXML(xml); - const items = doc.children()[0] - .map((node) => { - const prop = node.find('DAV:propstat').find('DAV:prop'); - const type = prop.find('DAV:resourcetype').find('DAV:collection') ? 'directory' : 'file'; - if (type === 'file') { - let displayName; - const displayNameNode = prop.find('DAV:displayname'); - - if (displayNameNode !== undefined) { - displayName = displayNameNode.text(); - } else { - const href = node.find('DAV:href').text(); - displayName = decodeURIComponent(href.substring(href.lastIndexOf('/') + 1)); - } - - if (isScriptFile(displayName)) { - const size = prop.find('DAV:getcontentlength'); - return normalize({ - name: displayName, - size: size ? +size.text() : 0, - }); - } - } - return null; - }) - .filter(Boolean); - return items; - }); - return readdir() - .catch((err) => { + return readdir().catch((err) => { if (err.status === 404) { return mkdir().then(readdir); } diff --git a/src/background/utils/cache.js b/src/background/utils/cache.js index 93a6d58f40..da6834fe40 100644 --- a/src/background/utils/cache.js +++ b/src/background/utils/cache.js @@ -1,11 +1,11 @@ -import initCache from '#/common/cache'; -import { commands } from './message'; +import initCache from '@/common/cache'; +import { addOwnCommands } from './init'; const cache = initCache({ lifetime: 5 * 60 * 1000, }); -Object.assign(commands, { +addOwnCommands({ CacheLoad(data) { return cache.get(data) || null; }, diff --git a/src/background/utils/clipboard.js b/src/background/utils/clipboard.js index e36eb5b084..f5f6ad8b12 100644 --- a/src/background/utils/clipboard.js +++ b/src/background/utils/clipboard.js @@ -1,9 +1,9 @@ -import { commands } from './message'; +import { addPublicCommands } from './init'; const textarea = document.createElement('textarea'); let clipboardData; -Object.assign(commands, { +addPublicCommands({ SetClipboard(data) { clipboardData = data; textarea.focus(); @@ -16,7 +16,7 @@ Object.assign(commands, { document.body.appendChild(textarea); -document.addEventListener('copy', e => { +addEventListener('copy', e => { e.preventDefault(); const { type, data } = clipboardData; e.clipboardData.setData(type || 'text/plain', data); diff --git a/src/background/utils/db.js b/src/background/utils/db.js index 0c14f0a79a..5b3b621063 100644 --- a/src/background/utils/db.js +++ b/src/background/utils/db.js @@ -1,80 +1,107 @@ import { - compareVersion, i18n, getFullUrl, isRemote, sendCmd, trueJoin, -} from '#/common'; -import { - CMD_SCRIPT_ADD, CMD_SCRIPT_UPDATE, INJECT_PAGE, INJECT_AUTO, TIMEOUT_WEEK, -} from '#/common/consts'; -import { forEachEntry, forEachKey, forEachValue } from '#/common/object'; -import storage from '#/common/storage'; -import ua from '#/common/ua'; + dataUri2text, i18n, getScriptHome, isDataUri, + getScriptName, getScriptsTags, getScriptUpdateUrl, isRemote, sendCmd, trueJoin, + getScriptPrettyUrl, getScriptRunAt, makePause, isValidHttpUrl, normalizeTag, + ignoreChromeErrors, +} from '@/common'; +import { FETCH_OPTS, INFERRED, TIMEOUT_24HOURS, TIMEOUT_WEEK, TL_AWAIT } from '@/common/consts'; +import { deepSize, forEachEntry, forEachKey, forEachValue } from '@/common/object'; import pluginEvents from '../plugin/events'; -import { getNameURI, parseMeta, newScript, getDefaultCustom } from './script'; -import { testScript, testBlacklist } from './tester'; -import { preInitialize } from './init'; -import { commands } from './message'; +import { + aliveScripts, getDefaultCustom, getNameURI, inferScriptProps, newScript, parseMeta, + removedScripts, scriptMap, +} from './script'; +import { testBlacklist, testerBatch, testScript } from './tester'; +import { getImageData } from './icon'; +import { addOwnCommands, addPublicCommands, commands, resolveInit } from './init'; import patchDB from './patch-db'; -import { setOption } from './options'; -import './storage-fetch'; +import { initOptions, kVersion, setOption } from './options'; +import storage, { + S_CACHE, S_CODE, S_REQUIRE, S_SCRIPT, S_VALUE, + S_CACHE_PRE, S_CODE_PRE, S_MOD_PRE, S_REQUIRE_PRE, S_SCRIPT_PRE, S_VALUE_PRE, + getStorageKeys, +} from './storage'; +import { storageCacheHas } from './storage-cache'; +import { reloadTabForScript } from './tabs'; +import { vetUrl } from './url'; -const store = {}; +let maxScriptId = 0; +let maxScriptPosition = 0; +/** @type {Map} */ +export let dbKeys = new Map(); // 1: exists, 0: known to be absent +/** @type {{ [url:string]: number }} */ +export let scriptSizes = {}; +/** Ensuring slow icons don't prevent installation/update */ +const ICON_TIMEOUT = 1000; +export const kTryVacuuming = 'Try vacuuming database in options.'; +/** Same order as in SIZE_TITLES and getSizes */ +export const sizesPrefixRe = RegExp( + `^(${S_CODE_PRE}|${S_SCRIPT_PRE}|${S_VALUE_PRE}|${S_REQUIRE_PRE}|${S_CACHE_PRE}${S_MOD_PRE})`); +/** @type {{ [type: 'cache' | 'require']: { [url: string]: Promise } }} */ +const pendingDeps = { [S_CACHE]: {}, [S_REQUIRE]: {} }; +const depsPorts = {}; -storage.script.onDump = (item) => { - store.scriptMap[item.props.id] = item; -}; +addPublicCommands({ + GetScriptVer(opts) { + const script = getScript(opts); + return script + ? script.meta.version + : null; + }, +}); -Object.assign(commands, { +addOwnCommands({ CheckPosition: sortScripts, CheckRemove: checkRemove, - /** @return {?string} */ - CheckScript({ name, namespace }) { - const script = getScript({ meta: { name, namespace } }); - return script && !script.config.removed - ? script.meta.version - : null; + RemoveScripts: removeScripts, + GetData: getData, + GetMoreIds({ url, [kTop]: isTop, [IDS]: ids }) { + return getScriptsByURL(url, isTop, null, ids); }, + /** @return {VMScript} */ + GetScript: getScript, + GetSizes: getSizes, /** @return {Promise<{ items: VMScript[], values? }>} */ async ExportZip({ values }) { const scripts = getScripts(); const ids = scripts.map(getPropsId); - const codeMap = await storage.code.getMulti(ids); + const codeMap = await storage[S_CODE].getMulti(ids); return { items: scripts.map(script => ({ script, code: codeMap[script.props.id] })), - values: values ? await storage.value.getMulti(ids) : undefined, + values: values ? await storage[S_VALUE].getMulti(ids) : undefined, }; }, /** @return {Promise} */ GetScriptCode(id) { - return storage.code.getOne(id); + return storage[S_CODE][Array.isArray(id) ? 'getMulti' : 'getOne'](id); }, + GetTags: () => getScriptsTags(aliveScripts), /** @return {Promise} */ - MarkRemoved({ id, removed }) { - return updateScriptInfo(id, { + async MarkRemoved({ id, removed }) { + if (!removed) { + const script = getScriptById(id); + const conflict = getScript({ meta: script.meta }); + if (conflict) throw i18n('msgNamespaceConflictRestore'); + } + await updateScriptInfo(id, { config: { removed: removed ? 1 : 0 }, props: { lastModified: Date.now() }, }); + const list = removed ? aliveScripts : removedScripts; + const i = list.findIndex(script => script.props.id === id); + const [script] = list.splice(i, 1); + (removed ? removedScripts : aliveScripts).push(script); }, /** @return {Promise} */ Move({ id, offset }) { const script = getScriptById(id); - const index = store.scripts.indexOf(script); - store.scripts.splice(index, 1); - store.scripts.splice(index + offset, 0, script); + const index = aliveScripts.indexOf(script); + aliveScripts.splice(index, 1); + aliveScripts.splice(index + offset, 0, script); return normalizePosition(); }, - /** @return {Promise} */ - async RemoveScript(id) { - const i = store.scripts.indexOf(getScriptById(id)); - if (i >= 0) { - store.scripts.splice(i, 1); - await Promise.all([ - storage.script.remove(id), - storage.code.remove(id), - storage.value.remove(id), - ]); - } - return sendCmd('RemoveScript', id); - }, - ParseMeta: parseMeta, + ParseMeta: parseMetaWithErrors, + ParseMetaErrors: data => parseMetaWithErrors(data).errors, ParseScript: parseScript, /** @return {Promise} */ UpdateScriptInfo({ id, config, custom }) { @@ -84,95 +111,87 @@ Object.assign(commands, { props: { lastModified: Date.now() }, }); }, - /** @return {Promise} */ + /** @return {Promise} */ Vacuum: vacuum, }); -preInitialize.push(async () => { - const { version: lastVersion } = await browser.storage.local.get('version'); +(async () => { + /** @type {string[]} */ + let allKeys, keys; + if (getStorageKeys) { + allKeys = await getStorageKeys(); + // Filtering and creating Map in atomic native code operations instead of js loop + keys = allKeys.join('\n').replace(/^(?:(options|version|(?:scr|mod):\d+)|\S+)$/gm, '$1').trim(); + dbKeys = new Map(JSON.parse(`[${keys.replace(/\S+/g, '["$&",1],').slice(0, -1)}]`)); + keys = keys.split(/\n+/); + } + const lastVersion = (!getStorageKeys || dbKeys.has(kVersion)) + && await storage.base.getOne(kVersion); const version = process.env.VM_VER; + const versionChanged = version !== lastVersion; if (!lastVersion) await patchDB(); - if (version !== lastVersion) browser.storage.local.set({ version }); - const data = await browser.storage.local.get(); - const scripts = []; - const storeInfo = { - id: 0, - position: 0, - }; - const idMap = {}; + if (versionChanged) storage.api.set({ [kVersion]: version }); + const data = await storage.api.get(keys); const uriMap = {}; - const mods = []; - const resUrls = []; - /** @this VMScriptCustom.pathMap */ - const rememberUrl = function _(url) { resUrls.push(this[url] || url); }; + const defaultCustom = getDefaultCustom(); data::forEachEntry(([key, script]) => { - if (key.startsWith(storage.script.prefix)) { - // { - // meta, - // custom, - // props: { id, position, uri }, - // config: { enabled, shouldUpdate }, - // } - const id = getInt(key.slice(storage.script.prefix.length)); - if (!id || idMap[id]) { - // ID conflicts! - // Should not happen, discard duplicates. - return; - } - idMap[id] = script; + const id = +storage[S_SCRIPT].toId(key); + if (id && script) { const uri = getNameURI(script); - if (uriMap[uri]) { - // Namespace conflicts! - // Should not happen, discard duplicates. - return; + // Only check ID/namespace conflicts for scripts not removed + if (!script.config.removed) { + if (scriptMap[id] && scriptMap[id] !== script) { + // ID conflicts! + // Should not happen, discard duplicates. + return; + } + if (uriMap[uri]) { + // Namespace conflicts! + // Should not happen, discard duplicates. + return; + } + uriMap[uri] = script; } - uriMap[uri] = script; script.props = { ...script.props, id, uri, }; - script.custom = { - ...getDefaultCustom(), - ...script.custom, - }; - storeInfo.id = Math.max(storeInfo.id, id); - storeInfo.position = Math.max(storeInfo.position, getInt(script.props.position)); - scripts.push(script); + const {pathMap} = script.custom = Object.assign({}, defaultCustom, script.custom); + // Patching the bug in 2.27.0 where data: URI was saved as invalid in pathMap + if (pathMap) for (const url in pathMap) if (isDataUri(url)) delete pathMap[url]; + maxScriptId = Math.max(maxScriptId, id); + maxScriptPosition = Math.max(maxScriptPosition, getInt(script.props.position)); + (script.config.removed ? removedScripts : aliveScripts).push(script); // listing all known resource urls in order to remove unused mod keys const { - custom: { pathMap = {} } = {}, - meta = {}, + meta = script.meta = {}, } = script; + if (!meta.require) meta.require = []; + if (!meta.resources) meta.resources = {}; + if (TL_AWAIT in meta) meta[TL_AWAIT] = true; // a string if the script was saved in old VM meta.grant = [...new Set(meta.grant || [])]; // deduplicate - meta.require?.forEach(rememberUrl, pathMap); - Object.values(meta.resources || {}).forEach(rememberUrl, pathMap); - pathMap::rememberUrl(meta.icon); - } else if (key.startsWith(storage.mod.prefix)) { - mods.push(key.slice(storage.mod.prefix.length)); } }); - storage.mod.removeMulti(mods.filter(url => !resUrls.includes(url))); - Object.assign(store, { - scripts, - storeInfo, - scriptMap: scripts.reduce((map, item) => { - map[item.props.id] = item; - return map; - }, {}), - }); - // Switch defaultInjectInto from `page` to `auto` when upgrading VM2.12.7 or older - if (version !== lastVersion - && ua.isFirefox - && data.options?.defaultInjectInto === INJECT_PAGE - && compareVersion(lastVersion, '2.12.7') <= 0) { - setOption('defaultInjectInto', INJECT_AUTO); - } + initOptions(data, lastVersion, versionChanged); if (process.env.DEBUG) { - console.log('store:', store); // eslint-disable-line no-console + console.info('store:', { + aliveScripts, removedScripts, maxScriptId, maxScriptPosition, scriptMap, scriptSizes, + }); } - return sortScripts(); -}); + sortScripts(); + setTimeout(async () => { + if (allKeys?.length) { + const set = new Set(keys); // much faster lookup + const data2 = await storage.api.get(allKeys.filter(k => !set.has(k))); + Object.assign(data, data2); + } + vacuum(data); + }, 100); + checkRemove(); + setInterval(checkRemove, TIMEOUT_24HOURS); + resolveInit(); +})(); /** @return {number} */ function getInt(val) { @@ -189,25 +208,28 @@ function updateLastModified() { setOption('lastModified', Date.now()); } -/** @return {Promise} */ +/** @return {Promise} */ export async function normalizePosition() { - const updates = store.scripts.filter(({ props }, index) => { + const updates = aliveScripts.reduce((res, script, index) => { + const { props } = script; const position = index + 1; - const res = props.position !== position; - if (res) props.position = position; + if (props.position !== position) { + props.position = position; + (res || (res = {}))[props.id] = script; + } return res; - }); - store.storeInfo.position = store.scripts.length; - if (updates.length) { - await storage.script.dump(updates); + }, null); + maxScriptPosition = aliveScripts.length; + if (updates) { + await storage[S_SCRIPT].set(updates); updateLastModified(); } - return updates.length; + return !!updates; } -/** @return {Promise} */ +/** @return {Promise} */ export async function sortScripts() { - store.scripts.sort((a, b) => getInt(a.props.position) - getInt(b.props.position)); + aliveScripts.sort((a, b) => getInt(a.props.position) - getInt(b.props.position)); const changed = await normalizePosition(); sendCmd('ScriptsUpdated', null); return changed; @@ -215,154 +237,324 @@ export async function sortScripts() { /** @return {?VMScript} */ export function getScriptById(id) { - return store.scriptMap[id]; + return scriptMap[id]; +} + +export function getScriptsByIdsOrAll(ids) { + return ids?.map(getScriptById) ?? [...aliveScripts, ...removedScripts]; } /** @return {?VMScript} */ -export function getScript({ id, uri, meta }) { +export function getScript({ id, uri, meta, removed }) { let script; if (id) { script = getScriptById(id); } else { - if (!uri) uri = getNameURI({ meta, id: '@@should-have-name' }); - script = store.scripts.find(({ props }) => uri === props.uri); + if (!uri) uri = getNameURI({ meta, props: { id: '@@should-have-name' } }); + script = (removed ? removedScripts : aliveScripts).find(({ props }) => uri === props.uri); } return script; } /** @return {VMScript[]} */ export function getScripts() { - return store.scripts.filter(script => !script.config.removed); + return [...aliveScripts]; } -/** - * @desc Load values for batch updates. - * @param {number[]} ids - * @return {Promise} - */ -export function getValueStoresByIds(ids) { - return storage.value.getMulti(ids); -} +export const CACHE_KEYS = 'cacheKeys'; +export const REQ_KEYS = 'reqKeys'; +export const VALUE_IDS = 'valueIds'; +export const PROMISE = 'promise'; +const makeEnv = () => ({ + depsMap: {}, + [RUN_AT]: {}, + [SCRIPTS]: [], +}); +const GMVALUES_RE = /^GM[_.](listValues|([gs]et|delete)Values?)$/; +const STORAGE_ROUTES = { + [S_CACHE]: CACHE_KEYS, + [S_CODE]: IDS, + [S_REQUIRE]: REQ_KEYS, + [S_VALUE]: VALUE_IDS, +}; +const STORAGE_ROUTES_ENTRIES = Object.entries(STORAGE_ROUTES); +const notifiedBadScripts = new Set(); /** - * @desc Dump values for batch updates. - * @param {Object} valueDict { id1: value1, id2: value2, ... } - * @return {Promise} + * @desc Get scripts to be injected to page with specific URL. + * @param {string} url + * @param {boolean} isTop + * @param {Array} [errors] - omit to enable EnvDelayed mode + * @param {Object} [prevIds] - used by the popup to return an object with only new ids + * (disabled, newly installed, non-matching due to SPA navigation) + * @return {VMInjection.EnvStart | VMInjection.EnvDelayed | Object | void } */ -export async function dumpValueStores(valueDict) { - if (process.env.DEBUG) console.info('Update value stores', valueDict); - await storage.value.dump(valueDict); - return valueDict; +export function getScriptsByURL(url, isTop, errors, prevIds) { + if (testBlacklist(url)) return; + const allIds = {}; + const isDelayed = !errors; + /** @type {VMInjection.EnvStart} */ + let envStart; + /** @type {VMInjection.EnvDelayed} */ + let envDelayed; + let clipboardChecked = isDelayed || !IS_FIREFOX; + testerBatch(errors || true); + for (const script of aliveScripts) { + const { + config: { enabled }, + custom, + meta, + props: { id }, + } = script; + if ((prevIds ? id in prevIds : !enabled) + || !((isTop || !(custom.noframes ?? meta.noframes)) && testScript(url, script))) { + continue; + } + if (prevIds) { + allIds[id] = enabled ? MORE : 0; + continue; + } + allIds[id] = 1; + if (!envStart) { + envStart = makeEnv(); + envDelayed = makeEnv(); + for (const [areaName, listName] of STORAGE_ROUTES_ENTRIES) { + envStart[areaName] = {}; envDelayed[areaName] = {}; + envStart[listName] = []; envDelayed[listName] = []; + } + } + const { pathMap = buildPathMap(script) } = custom; + const runAt = getScriptRunAt(script); + const env = runAt === 'start' || runAt === 'body' ? envStart : envDelayed; + const { depsMap } = env; + env[IDS].push(id); + env[RUN_AT][id] = runAt; + if (meta.grant.some(GMVALUES_RE.test, GMVALUES_RE)) { + env[VALUE_IDS].push(id); + } + if (!clipboardChecked) { + for (const g of meta.grant) { + if (!clipboardChecked && (g === 'GM_setClipboard' || g === 'GM.setClipboard')) { + clipboardChecked = envStart.clipFF = true; + } + } + } + for (const [list, name, dataUriDecoder] of [ + [meta.require, S_REQUIRE, dataUri2text], + [Object.values(meta.resources), S_CACHE], + ]) { + const listName = STORAGE_ROUTES[name]; + const envCheck = name === S_CACHE ? envStart : env; // envStart cache is reused in injected + // eslint-disable-next-line no-shadow + for (let url of list) { + url = pathMap[url] || url; + if (url) { + if (isDataUri(url)) { + if (dataUriDecoder) { + env[name][url] = dataUriDecoder(url); + } + } else if (!envCheck[listName].includes(url)) { + env[listName].push(url); + (depsMap[url] || (depsMap[url] = [])).push(id); + } + } + } + } + env[SCRIPTS].push(script); + } + testerBatch(); + if (prevIds) { + return allIds; + } + if (!envStart) { + return; + } + if (isDelayed) { + envDelayed[PROMISE] = readEnvironmentData(envDelayed); + return envDelayed; + } + if (envStart[IDS].length) { + envStart[PROMISE] = readEnvironmentData(envStart); + } + if (envDelayed[IDS].length) { + envDelayed[PROMISE] = makePause().then(readEnvironmentData.bind(null, envDelayed)); + } + return Object.assign(envStart, { allIds, [MORE]: envDelayed }); } -const gmValues = [ - 'GM_getValue', 'GM.getValue', - 'GM_setValue', 'GM.setValue', - 'GM_listValues', 'GM.listValues', - 'GM_deleteValue', 'GM.deleteValue', -]; +async function readEnvironmentData(env) { + const keys = []; + for (const [area, listName] of STORAGE_ROUTES_ENTRIES) { + for (const id of env[listName]) { + keys.push(storage[area].toKey(id)); + } + } + const data = await storage.api.get(keys); + const badScripts = new Set(); + for (const [area, listName] of STORAGE_ROUTES_ENTRIES) { + for (const id of env[listName]) { + let val = data[storage[area].toKey(id)]; + if (!val && area === S_VALUE) val = {}; + // {} enables tracking in addValueOpener + env[area][id] = val; + if (val == null) { + if (area === S_CODE) { + badScripts.add(id); + } else { + env.depsMap[id]?.forEach(scriptId => badScripts.add(scriptId)); + } + } + } + } + if (badScripts.size) { + reportBadScripts(badScripts); + } + return env; +} -/** - * @desc Get scripts to be injected to page with specific URL. - * @return {Promise} - */ -export async function getScriptsByURL(url, isTop) { - const allScripts = testBlacklist(url) - ? [] - : store.scripts.filter(script => ( - !script.config.removed - && (isTop || !script.meta.noframes) - && testScript(url, script) - )); - const reqKeys = []; - const cacheKeys = []; - const scripts = allScripts.filter(script => script.config.enabled); - scripts.forEach((script) => { - const { meta, custom } = script; - const { pathMap = buildPathMap(script) } = custom; - meta.require.forEach((key) => { - pushUnique(reqKeys, pathMap[key] || key); - }); - meta.resources::forEachValue((key) => { - pushUnique(cacheKeys, pathMap[key] || key); - }); +/** @param {Set} ids */ +function reportBadScripts(ids) { + const unnotifiedIds = []; + const title = i18n('msgMissingResources'); + let toLog = i18n('msgReinstallScripts'); + let toNotify = toLog; + let str; + ids.forEach(id => { + str = `\n#${id}: ${getScriptName(getScriptById(id))}`; + toLog += str; + if (!notifiedBadScripts.has(id)) { + notifiedBadScripts.add(id); + unnotifiedIds.push(id); + toNotify += str; + } }); - const ids = allScripts.map(getPropsId); - const enabledIds = scripts.map(getPropsId); - const withValueIds = scripts - .filter(script => script.meta.grant?.some(gm => gmValues.includes(gm))) - .map(getPropsId); - const [require, cache, values, code] = await Promise.all([ - storage.require.getMulti(reqKeys), - storage.cache.getMulti(cacheKeys), - storage.value.getMulti(withValueIds, {}), - storage.code.getMulti(enabledIds), - ]); - return { - // these will be sent to injectScripts() - inject: { - cache, - ids, - scripts, - }, - // these will be used only by bg/* and to augment the data above - cacheKeys, - code, - enabledIds, - reqKeys, - require, - values, - withValueIds, - }; + console.error(`${title} ${toLog}`); + if (unnotifiedIds.length) { + notifyToOpenScripts(title, toNotify, unnotifiedIds); + } } -function pushUnique(arr, elem) { - if (!arr.includes(elem)) arr.push(elem); +export function notifyToOpenScripts(title, text, ids) { + // FF doesn't show notifications of type:'list' so we'll use `text` everywhere + commands.Notification({ + title, + text, + onclick() { + ids.forEach(id => commands.OpenEditor(id)); + }, + }); } /** * @desc Get data for dashboard. * @return {Promise<{ scripts: VMScript[], cache: Object }>} */ -export async function getData(ids) { - const scripts = ids ? ids.map(getScriptById) : store.scripts; - return { - scripts, - cache: await getIconCache(scripts), - }; +export async function getData({ id, ids, sizes }) { + if (id) ids = [id]; + const res = {}; + const scripts = ids + // Some ids shown in popup/editor may have been hard-deleted + ? getScriptsByIdsOrAll(ids).filter(Boolean) + : getScriptsByIdsOrAll(); + scripts.forEach(inferScriptProps); + res[SCRIPTS] = scripts; + if (sizes) res.sizes = getSizes(ids); + if (!id) res.cache = await getIconCache(scripts); + if (!id && sizes) res.sync = commands.SyncGetStates(); + return res; } -function getIconCache(scripts) { - const iconUrls = []; - scripts.forEach((script) => { - const { icon } = script.meta; - if (isRemote(icon)) { - iconUrls.push(script.custom.pathMap?.[icon] || icon); +/** + * Returns only own icon and the already cached icons. + * The rest are prefetched in background and will be used by loadScriptIcon. + * @param {VMScript[]} scripts + * @return {Promise<{}>} + */ +async function getIconCache(scripts) { + // data uri for own icon to load it instantly in Chrome when there are many images + const toGet = [`${ICON_PREFIX}38.png`]; + const toPrime = []; + const res = {}; + for (let { custom, meta } of scripts) { + let icon = custom.icon || meta.icon; + if (isValidHttpUrl(icon)) { + icon = custom.pathMap[icon] || icon; + toGet.push(icon); + if (!storageCacheHas(S_CACHE_PRE + icon)) toPrime.push(icon); } - }); - return iconUrls.length - ? storage.cache.getMulti(iconUrls, undefined, storage.cache.makeDataUri) - : {}; + } + if (toPrime.length) { + await storage[S_CACHE].getMulti(toPrime); + } + for (let i = 0, d, url; i < toGet.length; i++) { + url = toGet[i]; + d = getImageData(url); + if (!isObject(d) || !i && (d = await d)) { + res[url] = d; + } + } + return res; +} + +/** + * @param {number[]} [ids] + * @return {number[][]} + */ +export function getSizes(ids) { + const scripts = getScriptsByIdsOrAll(ids); + return scripts.map(({ + meta, + custom: { pathMap = {} }, + props: { id }, + }, i) => [ + // Same order as SIZE_TITLES and sizesPrefixRe + scriptSizes[S_CODE_PRE + id] || 0, + deepSize(scripts[i]), + scriptSizes[S_VALUE_PRE + id] || 0, + meta.require.reduce(getSizeForRequires, { len: 0, pathMap }).len, + Object.values(meta.resources).reduce(getSizeForResources, { len: 0, pathMap }).len, + ]); +} + +function getSizeForRequires(accum, url) { + accum.len += (scriptSizes[S_REQUIRE_PRE + (accum.pathMap[url] || url)] || 0) + url.length; + return accum; +} + +function getSizeForResources(accum, url) { + accum.len += (scriptSizes[S_CACHE_PRE + (accum.pathMap[url] || url)] || 0) + url.length; + return accum; +} + +export async function removeScripts(ids) { + const idsToRemove = []; + // Only those marked as removed can be removed permanently + const newLen = 1 + removedScripts.reduce((iAlive, script, i) => { + const id = getPropsId(script); + if (ids.includes(id)) { + idsToRemove.push(S_CODE_PRE + id, S_SCRIPT_PRE + id, S_VALUE_PRE + id); + delete scriptMap[id]; + } else if (++iAlive < i) removedScripts[iAlive] = script; + return iAlive; + }, -1); + if (removedScripts.length !== newLen) { + removedScripts.length = newLen; // live scripts were moved to the beginning + await storage.api.remove(idsToRemove); + return sendCmd('RemoveScripts', ids); + } } -/** @return {number} */ export function checkRemove({ force } = {}) { const now = Date.now(); - const toRemove = store.scripts.filter(script => script.config.removed && ( - force || now - getInt(script.props.lastModified) > TIMEOUT_WEEK - )); - if (toRemove.length) { - store.scripts = store.scripts.filter(script => !script.config.removed); - const ids = toRemove.map(getPropsId); - storage.script.removeMulti(ids); - storage.code.removeMulti(ids); - storage.value.removeMulti(ids); - } - return toRemove.length; + const ids = removedScripts.filter(script => { + const { lastModified } = script.props; + return script.config.removed && (force || now - getInt(lastModified) > TIMEOUT_WEEK); + }).map(script => script.props.id); + return removeScripts(ids); } /** @return {string} */ -function getUUID() { +const getUUID = crypto.randomUUID ? crypto.randomUUID.bind(crypto) : () => { const rnd = new Uint16Array(8); window.crypto.getRandomValues(rnd); // xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx @@ -371,113 +563,134 @@ function getUUID() { rnd[3] = rnd[3] & 0x0FFF | 0x4000; // eslint-disable-line no-bitwise rnd[4] = rnd[4] & 0x3FFF | 0x8000; // eslint-disable-line no-bitwise return '01-2-3-4-567'.replace(/\d/g, i => (rnd[i] + 0x1_0000).toString(16).slice(-4)); -} +}; /** - * @param {VMScript} script - * @param {string} code - * @return {Promise} + * @param {number} id + * @param {DeepPartial} data */ -async function saveScript(script, code) { - const config = script.config || {}; - config.enabled = getInt(config.enabled); - config.shouldUpdate = getInt(config.shouldUpdate); - const props = script.props || {}; - let oldScript; - if (!props.id) { - store.storeInfo.id += 1; - props.id = store.storeInfo.id; - } else { - oldScript = store.scriptMap[props.id]; - } - props.uri = getNameURI(script); - props.uuid = props.uuid || getUUID(); - // Do not allow script with same name and namespace - if (store.scripts.some(({ props: { id, uri } = {} }) => props.id !== id && props.uri === uri)) { - throw i18n('msgNamespaceConflict'); - } - if (oldScript) { - script.config = { ...oldScript.config, ...config }; - script.props = { ...oldScript.props, ...props }; - const index = store.scripts.indexOf(oldScript); - store.scripts[index] = script; - } else { - if (!props.position) { - store.storeInfo.position += 1; - props.position = store.storeInfo.position; - } else if (store.storeInfo.position < props.position) { - store.storeInfo.position = props.position; - } - script.config = config; - script.props = props; - store.scripts.push(script); +export async function updateScriptInfo(id, data) { + const script = scriptMap[id]; + for (const key in data) { // shallow merge + if (script[key]) Object.assign(script[key], data[key]); } - return Promise.all([ - storage.script.dump(script), - storage.code.set(props.id, code), + await Promise.all([ + storage.api.set({ [S_SCRIPT_PRE + id]: script }), + sendCmd('UpdateScript', { where: { id }, update: script }), ]); } -/** @return {Promise} */ -export async function updateScriptInfo(id, data) { - const script = store.scriptMap[id]; - if (!script) throw null; - script.props = { ...script.props, ...data.props }; - script.config = { ...script.config, ...data.config }; - script.custom = { ...script.custom, ...data.custom }; - await storage.script.dump(script); - return sendCmd(CMD_SCRIPT_UPDATE, { where: { id }, update: script }); +/** + * @param {string | {code:string, custom:VMScript['custom']}} src + * @return {{ meta: VMScript['meta'], errors: string[] }} + */ +function parseMetaWithErrors(src) { + const isObj = isObject(src); + const custom = isObj && src.custom || getDefaultCustom(); + const errors = []; + const meta = parseMeta(isObj ? src.code : src, { errors }); + if (meta) { + testerBatch(errors); + testScript('', { meta, custom }); + testerBatch(); + } else { + errors.push(i18n('labelNoName')); // used by confirm app + } + return { + meta, + errors: errors.length ? errors : null, + }; } -/** @return {Promise<{ isNew?, update, where }>} */ +/** + * @param {VMScriptSourceOptions} src + * @return {Promise<{ + * errors: string[], + * isNew: boolean, + * update: VMScript & { message: string }, + * where: { id: number }, + * }>} + */ export async function parseScript(src) { - const meta = parseMeta(src.code); - if (!meta.name) throw i18n('msgInvalidScript'); - const result = { - update: { - message: src.message == null ? i18n('msgUpdated') : src.message || '', - }, + const { meta, errors } = src.meta ? src : parseMetaWithErrors(src); + if (!meta.name) throw `${i18n('msgInvalidScript')}\n${i18n('labelNoName')}`; + const update = { + message: src.message == null ? i18n('msgUpdated') : src.message || '', }; - let cmd = CMD_SCRIPT_UPDATE; + const result = { errors, update }; + const { [S_CODE]: code, update: srcUpdate } = src; + const now = Date.now(); + let { id } = src; let script; - const oldScript = await getScript({ id: src.id, meta }); + let oldScript = getScript({ id, meta }); if (oldScript) { - if (src.isNew) throw i18n('msgNamespaceConflict'); - script = { ...oldScript }; + script = oldScript; + id = script.props.id; } else { ({ script } = newScript()); - cmd = CMD_SCRIPT_ADD; + maxScriptId++; + id = script.props.id = maxScriptId; result.isNew = true; - result.update.message = i18n('msgInstalled'); + update.message = i18n('msgInstalled'); + aliveScripts.push(script); } - script.config = { - ...script.config, - ...src.config, - removed: 0, // force reset `removed` since this is an installation - }; - script.custom = { - ...script.custom, - ...src.custom, - }; - script.props = { - ...script.props, - lastModified: Date.now(), - lastUpdated: Date.now(), - ...src.props, - }; + const { config, custom, props } = script; + const uri = getNameURI({ meta, props: {id} }); + if (oldScript) { + // Do not allow script with same name and namespace + if (src.isNew || id && aliveScripts.some(({ props: p }) => uri === p.uri && id !== p.id)) { + throw i18n('msgNamespaceConflict'); + } + delete script[INFERRED]; + } + props.lastModified = now; + props.uuid = props.uuid || getUUID(); + // Overwriting inner data by `src`, deleting keys for which `src` specifies `null` + for (const key of ['config', 'custom', 'props']) { + const dst = script[key]; + src[key]::forEachEntry(([srcKey, srcVal]) => { + if (srcVal == null) delete dst[srcKey]; + else dst[srcKey] = srcVal; + }); + } + const pos = +src.position; + if (pos) { + props.position = pos; + maxScriptPosition = Math.max(maxScriptPosition, pos); + } else if (!oldScript) { + maxScriptPosition++; + props.position = maxScriptPosition; + } + config.enabled = getInt(config.enabled); + config.removed = 0; // force-resetting `removed` since this is an installation + config.shouldUpdate = getInt(config.shouldUpdate); script.meta = meta; - if (!meta.homepageURL && !script.custom.homepageURL && isRemote(src.from)) { - script.custom.homepageURL = src.from; + props.uri = getNameURI(script); // DANGER! Must be after props.position and meta assignments. + delete custom.from; // remove the old installation URL if any + if (!getScriptHome(script) && isRemote(src.from)) { + custom.from = src.from; // to avoid overriding script's `meta` for homepage in a future version } - if (isRemote(src.url)) script.custom.lastInstallURL = src.url; - if (src.position) script.props.position = +src.position; + // Allowing any http url including localhost as the user may keep multiple scripts there + if (isValidHttpUrl(src.url)) custom.lastInstallURL = src.url; + custom.tags = custom.tags?.split(/\s+/).map(normalizeTag).filter(Boolean).join(' ').toLowerCase(); + if (!srcUpdate) storage.mod.remove(getScriptUpdateUrl(script, { all: true }) || []); buildPathMap(script, src.url); - await saveScript(script, src.code); - fetchResources(script, src); - Object.assign(result.update, script, src.update); - result.where = { id: script.props.id }; - sendCmd(cmd, result); + const depsPromise = fetchResources(script, src); + // DANGER! Awaiting here when all props are set to avoid modifications made by a "concurrent" call + const codeChanged = !oldScript || code !== await storage[S_CODE].getOne(id); + if (codeChanged && src.bumpDate) props.lastUpdated = now; + // Installer has all the deps, so we'll put them in storage first + if (src.cache) await depsPromise; + await storage.api.set({ + [S_SCRIPT_PRE + id]: script, + ...codeChanged && { [S_CODE_PRE + id]: code }, + }); + Object.assign(update, script, srcUpdate); + result.where = { id }; + result[S_CODE] = src[S_CODE]; + sendCmd('UpdateScript', result); pluginEvents.emit('scriptChanged', result); + if (src.reloadTab) reloadTabForScript(script); return result; } @@ -491,7 +704,7 @@ function buildPathMap(script, base) { meta.icon, ].reduce((map, key) => { if (key) { - const fullUrl = getFullUrl(key, baseUrl); + const fullUrl = vetUrl(key, baseUrl); if (fullUrl !== key) map[key] = fullUrl; } return map; @@ -500,176 +713,200 @@ function buildPathMap(script, base) { return pathMap; } -/** @return {Promise} resolves to error text if `resourceCache` is absent */ -export async function fetchResources(script, resourceCache, reqOptions) { - const { custom: { pathMap }, meta } = script; - const snatch = (url, type, validator) => { - url = pathMap[url] || url; - const contents = resourceCache?.[type]?.[url]; - return contents != null && !validator - ? storage[type].set(url, contents) && null - : storage[type].fetch(url, reqOptions, validator).catch(err => err); - }; - const errors = await Promise.all([ - ...meta.require.map(url => snatch(url, 'require')), - ...Object.values(meta.resources).map(url => snatch(url, 'cache')), - isRemote(meta.icon) && snatch(meta.icon, 'cache', validateImage), - ]); - if (!resourceCache) { - const error = errors.map(formatHttpError)::trueJoin('\n'); - if (error) { - const message = i18n('msgErrorFetchingResource'); - sendCmd(CMD_SCRIPT_UPDATE, { - update: { error, message }, - where: { id: script.props.id }, - }); - return `${message}\n${error}`; +/** + * @param {VMScript} script + * @param {VMScriptSourceOptions} src + * @return {Promise} error text + */ +export async function fetchResources(script, src) { + const { custom, meta } = script; + const { pathMap } = custom; + const { resources } = meta; + const icon = custom.icon || meta.icon; + const jobs = []; + for (const url of meta.require) { + jobs.push([S_REQUIRE, url]); + } + for (const key in resources) { + jobs.push([S_CACHE, resources[key]]); + } + if (isRemote(icon)) { + jobs.push([S_CACHE, icon, ICON_TIMEOUT]); + } + for (let i = 0, type, url, timeout, res; i < jobs.length; i++) { + [type, url, timeout] = jobs[i]; + if (!(res = pendingDeps[type][url])) { + if (url && !isDataUri(url)) { + url = pathMap[url] || url; + if ((res = src[type]) && (res = res[url]) != null) { + storage[type].setOne(url, res); + res = ''; + } else { + res = fetchResource(src, type, url); + if (timeout) res = Promise.race([res, makePause(timeout)]); + pendingDeps[type][url] = res; + } + } } + jobs[i] = res; + } + const errors = await Promise.all(jobs); + const error = errors.map(formatHttpError)::trueJoin('\n'); + if (error) { + const message = i18n('msgErrorFetchingResource'); + sendCmd('UpdateScript', { + update: { error, message }, + where: { id: getPropsId(script) }, + }); + return `${message}\n${error}`; } } -/** @return {Promise} resolves on success, rejects on error */ -function validateImage(url, buf, type) { - return new Promise((resolve, reject) => { - const blobUrl = URL.createObjectURL(new Blob([buf], { type })); - const onDone = (e) => { - URL.revokeObjectURL(blobUrl); - if (e.type === 'load') resolve(); - else reject({ type: 'IMAGE_ERROR', url }); - }; - const image = new Image(); - image.onload = onDone; - image.onerror = onDone; - image.src = blobUrl; - }); +/** + * @param {VMScriptSourceOptions} src + * @param {string} type + * @param {string} url + * @return {Promise} + */ +async function fetchResource(src, type, url) { + if (!src.reuseDeps && !isRemote(url) + || src.update + || await storage[type].getOne(url) == null) { + const { portId } = src; + if (portId) postToPort(depsPorts, portId, [url]); + try { + await storage[type].fetch(url, src[FETCH_OPTS]); + } catch (err) { + return err; + } finally { + if (portId) postToPort(depsPorts, portId, [url, true]); + delete pendingDeps[type][url]; + } + } +} + +function postToPort(ports, id, msg) { + let p = ports[id]; + if (!p) { + p = ports[id] = chrome.runtime.connect({ name: id }); + p.onDisconnect.addListener(() => { + ignoreChromeErrors(); + delete ports[id]; + }); + } + p.postMessage(msg); } function formatHttpError(e) { return e && [e.status && `HTTP${e.status}`, e.url]::trueJoin(' ') || e; } -/** @return {Promise} */ -export async function vacuum() { - const valueKeys = {}; - const cacheKeys = {}; - const requireKeys = {}; - const codeKeys = {}; - const mappings = [ - [storage.value, valueKeys], - [storage.cache, cacheKeys], - [storage.require, requireKeys], - [storage.code, codeKeys], +let _vacuuming; +/** + * @param {Object} [data] + * @return {Promise<{errors:string[], fixes:number}>} + */ +export async function vacuum(data) { + if (_vacuuming) return _vacuuming; + let resolveSelf; + _vacuuming = new Promise(r => { resolveSelf = r; }); + const noFetch = data && []; + const sizes = {}; + const result = {}; + const toFetch = []; + const keysToRemove = []; + /** -1=untouched, 1=touched, 2(+scriptId)=missing */ + const status = {}; + const prefixRe = RegExp(`^(${[ + S_VALUE_PRE, + S_CACHE_PRE, + S_REQUIRE_PRE, + S_CODE_PRE, + S_MOD_PRE, + ].join('|')})`); + const prefixIgnoreMissing = [ + S_VALUE_PRE, + S_MOD_PRE, ]; - const data = await browser.storage.local.get(); - data::forEachKey((key) => { - mappings.some(([substore, map]) => { - const { prefix } = substore; - if (key.startsWith(prefix)) { - // -1 for untouched, 1 for touched, 2 for missing - map[key.slice(prefix.length)] = -1; - return true; + const downloadUrls = {}; + const touch = (prefix, id, scriptId, pathMap) => { + if (!id || pathMap && isDataUri(id)) { + return 0; + } + const key = prefix + (pathMap?.[id] || id); + const val = status[key]; + if (val < 0) { + status[key] = 1; + if (id !== scriptId) { + status[S_MOD_PRE + id] = 1; } - return false; - }); - }); - const touch = (obj, key) => { - if (obj[key] < 0) { - obj[key] = 1; - } else if (!obj[key]) { - obj[key] = 2; + if (prefix === S_VALUE_PRE) { + if ((sizes[key] = deepSize(data[key])) === 2) { + // remove empty {} + sizes[key] = 0; + status[key] = -1; + } + } else if (prefix !== S_MOD_PRE) { + sizes[key] = deepSize(data[key]) + key.length; + } + } else if (!val && !prefixIgnoreMissing.includes(prefix)) { + status[key] = 2 + scriptId; } }; - store.scripts.forEach((script) => { - const { id } = script.props; - touch(codeKeys, id); - touch(valueKeys, id); - if (!script.custom.pathMap) buildPathMap(script); - const { pathMap } = script.custom; - script.meta.require.forEach((url) => { - touch(requireKeys, pathMap[url] || url); - }); - script.meta.resources::forEachValue((url) => { - touch(cacheKeys, pathMap[url] || url); - }); - const { icon } = script.meta; - if (isRemote(icon)) { - const fullUrl = pathMap[icon] || icon; - touch(cacheKeys, fullUrl); + if (!data) data = await storage.api.get(); + data::forEachKey((key) => { + if (prefixRe.test(key)) { + status[key] = -1; + } + }); + scriptSizes = sizes; + getScriptsByIdsOrAll().forEach((script) => { + const { meta, props } = script; + const icon = script.custom.icon || meta.icon; + const { id } = props; + const pathMap = script.custom.pathMap || buildPathMap(script); + const updUrls = getScriptUpdateUrl(script, { all: true }); + if (updUrls) { + updUrls.forEach(url => touch(S_MOD_PRE, url, id)); + downloadUrls[id] = updUrls[0]; } + touch(S_CODE_PRE, id, id); + touch(S_MOD_PRE, id, id); + touch(S_VALUE_PRE, id, id); + meta.require.forEach(url => touch(S_REQUIRE_PRE, url, id, pathMap)); + meta.resources::forEachValue(url => touch(S_CACHE_PRE, url, id, pathMap)); + if (isRemote(icon)) touch(S_CACHE_PRE, icon, id, pathMap); }); - mappings.forEach(([substore, map]) => { - map::forEachEntry(([key, value]) => { - if (value < 0) { - // redundant value - substore.remove(key); - } else if (value === 2 && substore.fetch) { - // missing resource - substore.fetch(key); + status::forEachEntry(([key, value]) => { + if (value < 0) { + // Removing redundant value + keysToRemove.push(key); + } else if (value >= 2) { + // Downloading the missing code or resource + const area = storage.forKey(key); + const id = area.toId(key); + const url = area.name === S_CODE ? downloadUrls[id] : id; + if (noFetch) { + noFetch.push(url || +id && getScriptPrettyUrl(getScriptById(id)) || key); + } else if (url && area.fetch) { + keysToRemove.push(S_MOD_PRE + url); + toFetch.push(area.fetch(url).catch(err => `${ + getScriptName(getScriptById(+id || value - 2)) + }: ${ + formatHttpError(err) + }`)); } - }); + } }); + if (keysToRemove.length) { + await storage.api.remove(keysToRemove); // Removing `mod` before fetching + result.errors = (await Promise.all(toFetch)).filter(Boolean); + } + if (noFetch && noFetch.length) { + console.warn('Missing required resources. ' + kTryVacuuming, noFetch); + } + _vacuuming = null; + result.fixes = toFetch.length + keysToRemove.length; + resolveSelf(result); + return result; } - -/** @typedef VMScript - * @property {VMScriptConfig} config - * @property {VMScriptCustom} custom - * @property {VMScriptMeta} meta - * @property {VMScriptProps} props - */ -/** @typedef VMScriptConfig * - * @property {Boolean} enabled - stored as 0 or 1 - * @property {Boolean} removed - stored as 0 or 1 - * @property {Boolean} shouldUpdate - stored as 0 or 1 - * @property {Boolean | null} notifyUpdates - stored as 0 or 1 or null (default) which means "use global setting" - */ -/** @typedef VMScriptCustom * - * @property {string} name - * @property {string} downloadURL - * @property {string} homepageURL - * @property {string} lastInstallURL - * @property {string} updateURL - * @property {'auto' | 'page' | 'content'} injectInto - * @property {string[]} exclude - * @property {string[]} excludeMatch - * @property {string[]} include - * @property {string[]} match - * @property {boolean} origExclude - * @property {boolean} origExcludeMatch - * @property {boolean} origInclude - * @property {boolean} origMatch - * @property {Object} pathMap - * @property {VMScriptRunAt} runAt - */ -/** @typedef VMScriptMeta * - * @property {string} description - * @property {string} downloadURL - * @property {string[]} exclude - * @property {string[]} excludeMatch - * @property {string[]} grant - * @property {string} homepageURL - * @property {string} icon - * @property {string[]} include - * @property {'auto' | 'page' | 'content'} injectInto - * @property {string[]} match - * @property {string} namespace - * @property {string} name - * @property {boolean} noframes - * @property {string[]} require - * @property {Object} resources - * @property {VMScriptRunAt} runAt - * @property {string} supportURL - * @property {string} version - */ -/** @typedef VMScriptProps * - * @property {number} id - * @property {number} lastModified - * @property {number} lastUpdated - * @property {number} position - * @property {string} uri - * @property {string} uuid - */ -/** - * @typedef { - 'document-start' | 'document-body' | 'document-end' | 'document-idle' - } VMScriptRunAt - */ diff --git a/src/background/utils/hotkeys.js b/src/background/utils/hotkeys.js deleted file mode 100644 index 010a8271fe..0000000000 --- a/src/background/utils/hotkeys.js +++ /dev/null @@ -1,13 +0,0 @@ -import { postInitialize } from './init'; -import { commands } from './message'; - -postInitialize.push(() => { - browser.commands.onCommand.addListener((cmd) => { - if (cmd === 'newScript') { - commands.OpenEditor(); - } else { - const route = cmd === 'settings' ? `#${cmd}` : ''; - commands.TabOpen({ url: `/options/index.html${route}` }); - } - }); -}); diff --git a/src/background/utils/icon.js b/src/background/utils/icon.js index 3bbe22d349..e7c81f3c1a 100644 --- a/src/background/utils/icon.js +++ b/src/background/utils/icon.js @@ -1,34 +1,35 @@ -import { i18n, noop } from '#/common'; -import ua from '#/common/ua'; -import { INJECTABLE_TAB_URL_RE } from '#/common/consts'; -import { objectPick } from '#/common/object'; -import cache from './cache'; -import { postInitialize } from './init'; -import { commands, forEachTab } from './message'; -import { getOption, hookOptions } from './options'; +import { i18n, ignoreChromeErrors, makeDataUri, noop } from '@/common'; +import { BLACKLIST } from '@/common/consts'; +import { nest, objectPick } from '@/common/object'; +import { addOwnCommands, commands, init } from './init'; +import { getOption, hookOptions, setOption } from './options'; +import { popupTabs } from './popup-tracker'; +import storage, { S_CACHE } from './storage'; +import { forEachTab, getTabUrl, injectableRe, openDashboard, tabsOnRemoved, tabsOnUpdated } from './tabs'; import { testBlacklist } from './tester'; +import { FIREFOX, ua } from './ua'; -// storing in `cache` only for the duration of page load in case there are 2+ identical urls -const CACHE_DURATION = 1000; - -Object.assign(commands, { - async GetImageData(url) { - const key = `GetImageData:${url}`; - return cache.get(key) - || cache.put(key, loadImageData(url, { base64: true }).catch(noop), CACHE_DURATION); - }, -}); - +/** 1x + HiDPI 1.5x, 2x */ +const SIZES = !FIREFOX + ? [16, 32] + : ua.mobile + ? [32, 38, 48] // 1x, 1.5x, 2x + : [16, 32, 48, 64]; // 16+32: toolbar, 32+48+64: extensions panel +/** Caching own icon to improve dashboard loading speed, as well as browserAction API + * (e.g. Chrome wastes 40ms in our extension's process to read 4 icons for every tab). */ +const iconCache = {}; +const iconDataCache = {}; +/** @return {string | Promise} */ +export const getImageData = url => iconCache[url] || (iconCache[url] = loadIcon(url)); // Firefox Android does not support such APIs, use noop - const browserAction = (() => { - const api = browser.browserAction; - // Suppress the "no tab id" error when setting an icon/badge as it cannot be reliably prevented - const ignoreErrors = () => global.chrome.runtime.lastError; + // Using `chrome` namespace in order to skip our browser.js polyfill in Chrome + const api = chrome.browserAction; // Some methods like setBadgeText added callbacks only in Chrome 67+. const makeMethod = fn => (...args) => { try { - api::fn(...args, ignoreErrors); + // Suppress the "no tab id" error when setting an icon/badge as it cannot be reliably prevented + api::fn(...args, ignoreChromeErrors); } catch (e) { api::fn(...args); } @@ -40,146 +41,265 @@ const browserAction = (() => { 'setTitle', ], fn => (fn ? makeMethod(fn) : noop)); })(); +// Promisifying explicitly because this API returns an id in Firefox and not a Promise +const contextMenus = chrome.contextMenus; -const badges = {}; +/** @type {{ [tabId: string]: VMBadgeData }}*/ +export const badges = {}; +const KEY_SHOW_BADGE = 'showBadge'; +const KEY_BADGE_COLOR = 'badgeColor'; +const KEY_BADGE_COLOR_BLOCKED = 'badgeColorBlocked'; +const titleBlacklisted = i18n('failureReasonBlacklisted'); +const titleDefault = extensionManifest[BROWSER_ACTION].default_title; +const iconDefault = extensionManifest[BROWSER_ACTION].default_icon[16].match(/\d+(\w*)\./)[1]; +const titleDisabled = i18n('menuScriptDisabled'); +const titleNoninjectable = i18n('failureReasonNoninjectable'); +const titleSkipped = i18n('skipScriptsMsg'); let isApplied; +/** @type {VMBadgeMode} */ let showBadge; -let titleBlacklisted; -let titleNoninjectable; +let badgeColor; +let badgeColorBlocked; -// We'll cache the icon data in Chrome as it doesn't cache the data and takes up to 40ms -// in our background page context to set the 4 icon sizes for each new tab opened -const iconCache = ua.isChrome && {}; +addOwnCommands({ + GetImageData: getImageData, +}); hookOptions((changes) => { - if ('isApplied' in changes) { - isApplied = changes.isApplied; + let v; + const jobs = []; + if ((v = changes[IS_APPLIED]) != null) { + isApplied = v; setIcon(); // change the default icon - forEachTab(setIcon); // change the current tabs' icons + jobs.push(setIcon); // change the current tabs' icons } - if ('showBadge' in changes) { - showBadge = changes.showBadge; - forEachTab(updateBadge); + if ((v = changes[KEY_SHOW_BADGE]) != null) { + showBadge = v; + jobs.push(updateBadge); + contextMenus?.update(KEY_SHOW_BADGE + ':' + showBadge, {checked: true}); } - if ('blacklist' in changes) { - forEachTab(updateState); + if ((v = changes[KEY_BADGE_COLOR]) && (badgeColor = v) + || (v = changes[KEY_BADGE_COLOR_BLOCKED]) && (badgeColorBlocked = v)) { + jobs.push(updateBadgeColor); + } + if (BLACKLIST in changes) { + jobs.push(updateState); + } + if (jobs.length) { + forEachTab(tab => jobs.forEach(fn => fn(tab))); } }); -postInitialize.push(() => { - isApplied = getOption('isApplied'); - showBadge = getOption('showBadge'); - titleBlacklisted = i18n('failureReasonBlacklisted'); - titleNoninjectable = i18n('failureReasonNoninjectable'); +init.then(async () => { + isApplied = getOption(IS_APPLIED); + showBadge = getOption(KEY_SHOW_BADGE); + badgeColor = getOption(KEY_BADGE_COLOR); + badgeColorBlocked = getOption(KEY_BADGE_COLOR_BLOCKED); forEachTab(updateState); if (!isApplied) setIcon(); // sets the dimmed icon as default + if (contextMenus) { + const addToIcon = (id, title, opts) => ( + new Promise(resolve => ( + contextMenus.create({ + contexts: [BROWSER_ACTION], + id, + title, + ...opts, + }, resolve) + )) + ).then(ignoreChromeErrors); + const badgeChild = { parentId: KEY_SHOW_BADGE, type: 'radio' }; + await addToIcon(SKIP_SCRIPTS, i18n('skipScripts')); + for (const args of [ + [KEY_SHOW_BADGE, i18n('labelBadge')], + [`${KEY_SHOW_BADGE}:`, i18n('labelBadgeNone'), badgeChild], + [`${KEY_SHOW_BADGE}:unique`, i18n('labelBadgeUnique'), badgeChild], + [`${KEY_SHOW_BADGE}:total`, i18n('labelBadgeTotal'), badgeChild], + ]) { + await addToIcon(...args); + } + contextMenus.update(KEY_SHOW_BADGE + ':' + showBadge, { checked: true }); + // Chrome already adds a built-in "Options" item + if (IS_FIREFOX) await addToIcon(TAB_SETTINGS, i18n('labelSettings')); + } }); -browser.tabs.onRemoved.addListener((id) => { - delete badges[id]; +contextMenus?.onClicked.addListener(({ menuItemId: id }, tab) => { + handleHotkeyOrMenu(id, tab); }); - -browser.tabs.onUpdated.addListener((tabId, info, tab) => { - const { url } = info; - if (url && ua.isFirefox && isApplied) { - const badgeData = cache.pop(`badge:${tabId}${url}`); - if (badgeData) setBadge(...badgeData); +tabsOnRemoved.addListener(id => delete badges[id]); +tabsOnUpdated.addListener((tabId, { url }, tab) => { + if (url) { + const [title] = getFailureReason(url); + if (title) updateState(tab, resetBadgeData(tabId, null), title); } - if (info.status === 'loading' - // at least about:newtab in Firefox may open without 'loading' status - || info.favIconUrl && tab.url.startsWith('about:')) { - updateState(tab, url); - } -}); +}, FIREFOX && { properties: ['status'] }); -export function setBadge(ids, { tab, frameId }) { +function resetBadgeData(tabId, isInjected) { + // 'total' and 'unique' must match showBadge in options-defaults.js + /** @type {VMBadgeData} */ + const data = nest(badges, tabId); + data.icon = iconDefault; + data.total = 0; + data.unique = 0; + data[IDS] = new Set(); + data[kFrameId] = undefined; + data[INJECT] = isInjected; + // Notify popup about non-injectable tab + if (!isInjected) popupTabs[tabId]?.postMessage(null); + return data; +} + +/** + * @param {number[] | string} ids + * @param {boolean} reset + * @param {VMMessageSender} src + */ +export function setBadge(ids, reset, { tab, [kFrameId]: frameId, [kTop]: isTop }) { const tabId = tab.id; - const data = badges[tabId] || {}; - if (!data.idMap || frameId === 0) { - // 1) keeping data object to preserve data.blocked - // 2) 'total' and 'unique' must match showBadge in options-defaults.js + const injectable = ids === SKIP_SCRIPTS || ids === 'off' ? ids : !!ids; + /** @type {VMBadgeData} */ + const data = !(reset && isTop) && badges[tabId] || resetBadgeData(tabId, injectable); + if (Array.isArray(ids)) { + const { + [IDS]: idMap, + [kFrameId]: totalMap = data[kFrameId] = {}, + } = data; + // uniques + ids.forEach(idMap.add, idMap); + data.unique = idMap.size; + // totals data.total = 0; - data.unique = 0; - data.idMap = {}; - badges[tabId] = data; - } - data.total += ids.length; - if (ids) { - ids.forEach((id) => { - data.idMap[id] = 1; - }); - data.unique = Object.keys(data.idMap).length; + totalMap[frameId] = ids.length; + for (const id in totalMap) data.total += totalMap[id]; } - browserAction.setBadgeBackgroundColor({ - color: data.blocked ? '#888' : '#808', - tabId, - }); - updateBadge(tab, data); + if (isTop) { + data[INJECT] = injectable; + } + updateBadgeColor(tab, data); + updateState(tab, data); } -function updateBadge(tab, data = badges[tab.id]) { +function updateBadge({ id: tabId }, data = badges[tabId]) { if (data) { browserAction.setBadgeText({ text: `${data[showBadge] || ''}`, - tabId: tab.id, + tabId, }); } } -// Chrome 79+ uses pendingUrl while the tab connects to the newly navigated URL -// https://groups.google.com/a/chromium.org/forum/#!topic/chromium-extensions/5zu_PT0arls -function updateState(tab, url = tab.pendingUrl || tab.url) { - const tabId = tab.id; - const injectable = INJECTABLE_TAB_URL_RE.test(url); - const blacklisted = injectable ? testBlacklist(url) : undefined; - const title = blacklisted && titleBlacklisted || !injectable && titleNoninjectable || ''; - // if the user unblacklisted this previously blocked tab in settings, - // but didn't reload the tab yet, we need to restore the icon and the title - if (title || (badges[tabId] || {}).blocked) { - browserAction.setTitle({ title, tabId }); - const data = title ? { blocked: true } : {}; - badges[tabId] = data; - setIcon(tab, data); - updateBadge(tab, data); +function updateBadgeColor({ id: tabId }, data = badges[tabId]) { + if (data) { + browserAction.setBadgeBackgroundColor({ + color: data[INJECT] ? badgeColor : badgeColorBlocked, + tabId, + }); } } -async function setIcon(tab = {}, data = {}) { - // modern Chrome and Firefox use 16/32, other browsers may still use 19/38 (e.g. Vivaldi) - const mod = data.blocked && 'b' || !isApplied && 'w' || ''; +function updateState(tab, data, title) { + const tabId = tab.id; + if (!data) data = badges[tabId] || resetBadgeData(tabId); + if (!title) [title] = getFailureReason(getTabUrl(tab), data); + browserAction.setTitle({ tabId, title }); + setIcon(tab, data); + updateBadge(tab, data); +} + +async function setIcon({ id: tabId } = {}, data = badges[tabId] || {}) { + const mod = !isApplied ? 'w' + : data[INJECT] !== true ? 'b' + : ''; + if (data.icon === mod) return; + data.icon = mod; + const pathData = {}; const iconData = {}; - for (const n of [16, 19, 32, 38]) { - const path = `/public/images/icon${n}${mod}.png`; - let icon = iconCache ? iconCache[path] : path; - if (!icon) { - icon = await loadImageData(path); - iconCache[path] = icon; - } - iconData[n] = icon; + for (const n of SIZES) { + const url = `${ICON_PREFIX}${n}${mod}.png`; + pathData[n] = url; + iconData[n] = iconDataCache[url] + || await (iconCache[url] || (iconCache[url] = loadIcon(url))) && iconDataCache[url]; } + // imageData doesn't work in Firefox Android, so we also set path here browserAction.setIcon({ - tabId: tab.id, - [iconCache ? 'imageData' : 'path']: iconData, + tabId, + path: pathData, + imageData: iconData, }); } -function loadImageData(path, { base64 } = {}) { - return new Promise((resolve, reject) => { - const img = new Image(); - img.src = path; - img.onload = () => { - const { width, height } = img; - if (!width) { // FF reports 0 for SVG - resolve(path); - return; - } - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - canvas.width = width; - canvas.height = height; - ctx.drawImage(img, 0, 0, width, height); - resolve(base64 ? canvas.toDataURL() : ctx.getImageData(0, 0, width, height)); - }; - img.onerror = reject; +/** Omitting `data` = check whether injection is allowed for `url` */ +export function getFailureReason(url, data, def = titleDefault) { + return !injectableRe.test(url) ? [titleNoninjectable, INJECT_INTO] + : ((url = testBlacklist(url))) ? [titleBlacklisted, 'blacklisted', url] + : !isApplied || data?.[INJECT] === 'off' ? [titleDisabled, IS_APPLIED] + : !data ? [] + : data[INJECT] === SKIP_SCRIPTS + ? [titleSkipped, SKIP_SCRIPTS] + : [def]; +} + +export function handleHotkeyOrMenu(id, tab) { + if (id === SKIP_SCRIPTS) { + commands[SKIP_SCRIPTS](tab); + } else if (id === TAB_SETTINGS) { + openDashboard(id); + } else if (id === 'dashboard') { + openDashboard(''); + } else if (id === 'newScript') { + commands.OpenEditor(); + } else if (id === 'toggleInjection') { + setOption(IS_APPLIED, !isApplied); + } else if (id === 'updateScripts') { + commands.CheckUpdate(); + } else if (id === 'updateScriptsInTab') { + id = badges[tab.id]?.[IDS]; + if (id) commands.CheckUpdate([...id]); + } else if (id.startsWith(KEY_SHOW_BADGE)) { + setOption(KEY_SHOW_BADGE, id.slice(KEY_SHOW_BADGE.length + 1)); + } +} + +async function loadIcon(url) { + const img = new Image(); + const isOwn = url.startsWith(ICON_PREFIX); + img.src = isOwn ? url.slice(extensionOrigin.length) // must be a relative path in Firefox Android + : url.startsWith('data:') ? url + : makeDataUri(url[0] === 'i' ? url : await loadStorageCache(url)) + || url; + await new Promise((resolve) => { + img.onload = resolve; + img.onerror = resolve; }); + let res; + let maxSize = !isOwn && (2 * 38); // dashboard icon size for 2xDPI + let { width, height } = img; + if (!width || !height) { // FF reports 0 for SVG + iconCache[url] = url; + return url; + } + if (maxSize && (width > maxSize || height > maxSize)) { + maxSize /= width > height ? width : height; + width = Math.round(width * maxSize); + height = Math.round(height * maxSize); + } + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + canvas.width = width; + canvas.height = height; + ctx.drawImage(img, 0, 0, width, height); + try { + res = canvas.toDataURL(); + if (isOwn) iconDataCache[url] = ctx.getImageData(0, 0, width, height); + } catch (err) { + res = url; + } + iconCache[url] = res; + return res; +} + +async function loadStorageCache(url) { + return await storage[S_CACHE].getOne(url) + ?? await storage[S_CACHE].fetch(url, 'res').catch(console.warn); } diff --git a/src/background/utils/index.js b/src/background/utils/index.js index 61a9c05dcc..16e7b618a1 100644 --- a/src/background/utils/index.js +++ b/src/background/utils/index.js @@ -1,5 +1,4 @@ export { default as cache } from './cache'; export { default as getEventEmitter } from './events'; -export * from './message'; +export * from './init'; export * from './options'; -export * from './search'; diff --git a/src/background/utils/init.js b/src/background/utils/init.js index 2ad916727b..fea5c5e8af 100644 --- a/src/background/utils/init.js +++ b/src/background/utils/init.js @@ -1,13 +1,15 @@ -export const extensionRoot = browser.runtime.getURL('/'); +export const commands = {}; +export const addPublicCommands = obj => Object.assign(commands, obj); +/** Commands that can be used only by an extension page i.e. not by a content script */ +export const addOwnCommands = obj => { + for (const key in obj) { + (commands[key] = obj[key]).isOwn = true; + } +}; -export const preInitialize = []; -export const postInitialize = []; - -export async function initialize(main) { - const run = init => (typeof init === 'function' ? init() : init); - await Promise.all(preInitialize.map(run)); - await run(main); - await Promise.all(postInitialize.map(run)); - preInitialize.length = 0; - postInitialize.length = 0; -} +export let resolveInit; +export let init = new Promise(r => { + resolveInit = () => Promise.all(init.deps).then(r); +}); +init.deps = []; +init.then(() => (init = null)); diff --git a/src/background/utils/message.js b/src/background/utils/message.js deleted file mode 100644 index 12108b4587..0000000000 --- a/src/background/utils/message.js +++ /dev/null @@ -1,37 +0,0 @@ -import { defaultImage, i18n, noop } from '#/common'; - -export const commands = {}; - -export function notify(options) { - browser.notifications.create(options.id || 'ViolentMonkey', { - type: 'basic', - iconUrl: defaultImage, - title: `${options.title} - ${i18n('extName')}`, - message: options.body, - isClickable: options.isClickable, - }); -} - -export function broadcast(data) { - forEachTab((tab) => { - browser.tabs.sendMessage(tab.id, data) - .catch(noop); - }); -} - -export function sendMessageOrIgnore(...args) { - return browser.runtime.sendMessage(...args).catch(noop); -} - -export async function forEachTab(callback) { - const tabs = await browser.tabs.query({}); - let i = 0; - for (const tab of tabs) { - callback(tab); - i += 1; - // we'll run at most this many tabs in one event loop cycle - // because hundreds of tabs would make our extension process unresponsive, - // the same process used by our own pages like the background page, dashboard, or popups - if (i % 20 === 0) await new Promise(setTimeout); - } -} diff --git a/src/background/utils/notifications.js b/src/background/utils/notifications.js index 9da92d6a79..7a0ccd3ce0 100644 --- a/src/background/utils/notifications.js +++ b/src/background/utils/notifications.js @@ -1,40 +1,105 @@ -import { i18n, defaultImage, sendTabCmd } from '#/common'; -import ua from '#/common/ua'; -import { commands } from './message'; +import { i18n, defaultImage, sendTabCmd, trueJoin } from '@/common'; +import { addPublicCommands, commands } from './init'; +import { CHROME } from './ua'; +import { vetUrl } from './url'; +/** @type {{ [nid: string]: browser.runtime.MessageSender | function | number }} */ const openers = {}; +const kZombie = 'zombie'; +const kZombieTimeout = 'zombieTimeout'; +const kZombieUrl = 'zombieUrl'; -Object.assign(commands, { +addPublicCommands({ /** @return {Promise} */ - async Notification(data, src, bgExtras) { - const notificationId = await browser.notifications.create({ + async Notification({ + image, + text, + tag, + title, + silent, + onclick, + [kZombieUrl]: zombieUrl, + [kZombieTimeout]: zombieTimeout, + }, src) { + if (tag) clearZombieTimer(openers[tag]); + const notificationId = await browser.notifications.create(tag, { type: 'basic', - title: data.title || (ua.isFirefox ? i18n('extName') : ''), // Chrome already shows the name - message: data.text, - iconUrl: data.image || defaultImage, + title: [title, IS_FIREFOX && i18n('extName')]::trueJoin(' - '), // Chrome already shows the name + message: text, + iconUrl: image || defaultImage, + ...!IS_FIREFOX && { + requireInteraction: !!onclick, + }, + ...CHROME >= 70 && { + silent, + } }); - openers[notificationId] = bgExtras?.onClick || src.tab.id; + if (isFunction(onclick)) { + openers[notificationId] = onclick; + } else if (src) { + openers[notificationId] = src; + if (+zombieTimeout > 0) src[kZombieTimeout] = +zombieTimeout; + if (zombieUrl != null) src[kZombieUrl] = vetUrl(zombieUrl, src.url); + } return notificationId; }, - RemoveNotification(notificationId) { - return browser.notifications.clear(notificationId); + RemoveNotification(nid) { + clearZombieTimer(openers[nid]); + removeNotification(nid); }, }); browser.notifications.onClicked.addListener((id) => { - const openerId = openers[id]; - if (openerId >= 0) { - sendTabCmd(openerId, 'NotificationClick', id); - } - if (typeof openerId === 'function') { - openerId(); - } + notifyOpener(id, true); }); browser.notifications.onClosed.addListener((id) => { - const openerId = openers[id]; + notifyOpener(id, false); delete openers[id]; - if (openerId >= 0) { - sendTabCmd(openerId, 'NotificationClose', id); - } }); + +function notifyOpener(id, isClick) { + const op = openers[id]; + if (!op) return; + if (isFunction(op)) { + if (isClick) op(); + } else if (op > 0) { + if (isClick) clearZombieTimer(op); + } else if (op[kZombie]) { + if (isClick) { + commands.TabOpen({ url: op[kZombieUrl] }, op); + removeNotification(id); // Chrome doesn't auto-remove it on click + } + } else { + sendTabCmd(op.tab.id, isClick ? 'NotificationClick' : 'NotificationClose', id, { + [kFrameId]: op[kFrameId], + }); + } +} + +export function clearNotifications(tabId, frameId, tabRemoved) { + for (const nid in openers) { + const op = openers[nid]; + if (isObject(op) + && op.tab.id === tabId + && (!frameId || op[kFrameId] === frameId) + && !op[kZombie]) { + if (op[kZombieTimeout]) { + op[kZombie] = setTimeout(removeNotification, op[kZombieTimeout], nid); + if (!op[kZombieUrl]) openers[nid] = op[kZombie]; + if (tabRemoved) op._removed = true; + } else { + removeNotification(nid); + } + } + } +} + +function clearZombieTimer(op) { + if (op > 0) clearTimeout(op); +} + +function removeNotification(nid) { + delete openers[nid]; + return browser.notifications.clear(nid); +} diff --git a/src/background/utils/options.js b/src/background/utils/options.js index 57430688a9..c3999198bd 100644 --- a/src/background/utils/options.js +++ b/src/background/utils/options.js @@ -1,93 +1,135 @@ -import { - debounce, ensureArray, initHooks, normalizeKeys, -} from '#/common'; -import { mapEntry, objectGet, objectSet } from '#/common/object'; -import defaults from '#/common/options-defaults'; -import { preInitialize } from './init'; -import { commands } from './message'; +import { compareVersion, debounce, initHooks, normalizeKeys, sendCmd } from '@/common'; +import { deepCopy, deepEqual, objectGet, objectSet } from '@/common/object'; +import defaults, { kScriptTemplate } from '@/common/options-defaults'; +import { addOwnCommands, init } from './init'; +import storage from './storage'; -Object.assign(commands, { +let changes; + +addOwnCommands({ /** @return {Object} */ GetAllOptions() { - return commands.GetOptions(defaults); - }, - /** @return {Object} */ - GetOptions(data) { - return data::mapEntry(([key]) => getOption(key)); + return Object.assign({}, defaults, options); // eslint-disable-line no-use-before-define }, - /** @return {void} */ + /** + * @param {{ [key:string]: PlainJSONValue }} data + * @return {void} + * @throws {?} hooks can throw after the option was set */ SetOptions(data) { - ensureArray(data).forEach(item => setOption(item.key, item.value)); + for (const key in data) setOption(key, data[key], true); + callHooks(); // exceptions will be sent to the caller }, }); -let changes = {}; +const options = {}; +export const kOptions = 'options'; +export const kVersion = 'version'; +const TPL_OLD_VAL = `\ +// ==UserScript== +// @name New Script +// @namespace Violentmonkey Scripts +// @match {{url}} +// @grant none +// ==/UserScript== +`; +const DELAY = 100; const hooks = initHooks(); -const callHooksLater = debounce(callHooks, 100); +const callHooksLater = debounce(callHooks, DELAY); +const writeOptionsLater = debounce(writeOptions, DELAY); +const optProxy = new Proxy(defaults, { get: (_, key) => getOption(key) }); +export const hookOptions = hooks.hook; +hookOptions(data => sendCmd('UpdateOptions', data)); -let options = {}; -let initPending = browser.storage.local.get('options') -.then(({ options: data }) => { - if (data && typeof data === 'object') options = data; - if (process.env.DEBUG) { - console.log('options:', options); // eslint-disable-line no-console +export function initOptions(data, lastVersion, versionChanged) { + data = data[kOptions] || {}; + Object.assign(options, data); + if (process.env.DEBUG) console.info('options:', options); + if (!options[kVersion]) { + setOption(kVersion, 1); } - if (!objectGet(options, 'version')) { - setOption('version', 1); + if (options[kScriptTemplate] === TPL_OLD_VAL) { + options[kScriptTemplate] = defaults[kScriptTemplate]; // will be detected by omitDefaultValue below } - initPending = null; -}); -preInitialize.push(initPending); + if (Object.keys(options).map(omitDefaultValue).some(Boolean)) { + delete options[`${kScriptTemplate}Edited`]; // TODO: remove this in 2023 + writeOptionsLater(); + } + if (versionChanged) { + let key, val; + if (IS_FIREFOX && options[key = 'defaultInjectInto'] === PAGE + && compareVersion(lastVersion, '2.12.7') <= 0) { + options[key] = AUTO; + } + if ((val = options.filters) && val[key = 'sort'] === 'exec' + && compareVersion(lastVersion, '2.31.2') <= 0) { + val[key] += '-'; // Until reverse sort was implemented, 'size' was reversed by design + } + } +} -function fireChange(keys, value) { - // Flattening key path so the subscribers can update nested values without overwriting the parent - const key = keys.join('.'); - // Ensuring the correct order when updates were mixed like this: foo.bar=1; foo={bar:2}; foo.bar=3 - delete changes[key]; +/** + * @param {!string} key - must be "a.b.c" to allow clients easily set inside existing object trees + * @param {PlainJSONValue} [value] + * @param {boolean} [silent] - in case you callHooks() directly yourself afterwards + */ +function addChange(key, value, silent) { + if (!changes) changes = {}; + else delete changes[key]; // Deleting first to place the new value at the end changes[key] = value; - callHooksLater(); + if (!silent) callHooksLater(); } +/** @throws in option handlers */ function callHooks() { - hooks.fire(changes); - changes = {}; + if (!changes) return; // may happen in callHooksLater if callHooks was called earlier + const tmp = changes; + changes = null; + hooks.fire(tmp); } -export function getOption(key, def) { - const keys = normalizeKeys(key); - const mainKey = keys[0]; - let value = options[mainKey]; - if (value == null) value = defaults[mainKey]; - if (value == null) value = def; - return keys.length > 1 ? objectGet(value, keys.slice(1), def) : value; +/** Hooks and calls the callback with a copy of all options when init is resolved */ +export function hookOptionsInit(cb) { + if (init) init.then(() => cb(optProxy, true)); + else cb(optProxy, true); + return hookOptions(cb); } -export function getDefaultOption(key) { - return objectGet(defaults, key); +export function getOption(key) { + let res = options[key]; + if (res != null) return res; + const keys = normalizeKeys(key); + const mainKey = keys[0]; + const value = options[mainKey] ?? defaults[mainKey]; + return deepCopy(keys.length > 1 ? objectGet(value, keys.slice(1)) : value); } -export function setOption(key, value) { - if (initPending) { - initPending.then(() => { - setOption(key, value); - }); - return; - } +export function setOption(key, value, silent) { + if (init) return init.then(setOption.bind(null, ...arguments)); const keys = normalizeKeys(key); - const optionKey = keys.join('.'); - let optionValue = value; const mainKey = keys[0]; - if (mainKey in defaults) { - if (keys.length > 1) { - optionValue = objectSet(getOption(mainKey), keys.slice(1), value); - } - options[mainKey] = optionValue; - browser.storage.local.set({ options }); - fireChange(keys, value); - if (process.env.DEBUG) { - console.log('Options updated:', optionKey, value, options); // eslint-disable-line no-console - } + key = keys.join('.'); // must be a string for addChange() + if (!hasOwnProperty(defaults, mainKey)) { + if (process.env.DEBUG) console.info('Unknown option:', key, value, options); + return; } + const subKey = keys.length > 1 && keys.slice(1); + const mainVal = getOption([mainKey]); + if (deepEqual(value, subKey ? objectGet(mainVal, subKey) : mainVal)) { + if (process.env.DEBUG) console.info('Option unchanged:', key, value, options); + return; + } + options[mainKey] = subKey ? objectSet(mainVal, subKey, value) : value; + omitDefaultValue(mainKey); + writeOptionsLater(); + addChange(key, value, silent); + if (process.env.DEBUG) console.info('Options updated:', key, value, options); } -export const hookOptions = hooks.hook; +function writeOptions() { + return storage.base.setOne(kOptions, options); +} + +function omitDefaultValue(key) { + return deepEqual(options[key], defaults[key]) + && delete options[key]; +} diff --git a/src/background/utils/patch-db.js b/src/background/utils/patch-db.js index 41f7063cb6..6d573b4413 100644 --- a/src/background/utils/patch-db.js +++ b/src/background/utils/patch-db.js @@ -1,11 +1,12 @@ -import { parseMeta } from './script'; -import storage from '#/common/storage'; +import { getDefaultCustom, parseMeta } from './script'; +import storage, { S_CACHE, S_CODE, S_REQUIRE, S_SCRIPT, S_VALUE } from './storage'; export default () => new Promise((resolve, reject) => { + const defaultCustom = getDefaultCustom(); console.info('Upgrade database...'); init(); function init() { - const req = indexedDB.open('Violentmonkey', 1); + const req = indexedDB.open(VIOLENTMONKEY, 1); req.onsuccess = () => { try { transform(req.result); @@ -21,43 +22,43 @@ export default () => new Promise((resolve, reject) => { }; } function transform(db) { - const tx = db.transaction(['scripts', 'require', 'cache', 'values']); + const tx = db.transaction([SCRIPTS, S_REQUIRE, S_CACHE, VALUES]); const updates = {}; let processing = 3; const done = () => { processing -= 1; - if (!processing) resolve(browser.storage.local.set(updates)); + if (!processing) resolve(storage.api.set(updates)); }; const getAll = (storeName, callback) => { const req = tx.objectStore(storeName).getAll(); req.onsuccess = () => callback(req.result); req.onerror = reject; }; - getAll('scripts', (allScripts) => { + getAll(SCRIPTS, (allScripts) => { const uriMap = {}; allScripts.forEach((script) => { const { code, id, uri } = script; - updates[`${storage.script.prefix}${id}`] = transformScript(script); - updates[`${storage.code.prefix}${id}`] = code; + updates[storage[S_SCRIPT].toKey(id)] = transformScript(script); + updates[storage[S_CODE].toKey(id)] = code; uriMap[uri] = id; }); - getAll('values', (allValues) => { - allValues.forEach(({ uri, values }) => { + getAll(VALUES, (allValues) => { + allValues.forEach(({ uri, [VALUES]: values }) => { const id = uriMap[uri]; - if (id) updates[`${storage.value.prefix}${id}`] = values; + if (id) updates[storage[S_VALUE].toKey(id)] = values; }); done(); }); }); - getAll('cache', (allCache) => { + getAll(S_CACHE, (allCache) => { allCache.forEach(({ uri, data }) => { - updates[`${storage.cache.prefix}${uri}`] = data; + updates[storage[S_CACHE].toKey(uri)] = data; }); done(); }); - getAll('require', (allRequire) => { + getAll(S_REQUIRE, (allRequire) => { allRequire.forEach(({ uri, code }) => { - updates[`${storage.require.prefix}${uri}`] = code; + updates[storage[S_REQUIRE].toKey(uri)] = code; }); done(); }); @@ -65,12 +66,7 @@ export default () => new Promise((resolve, reject) => { function transformScript(script) { return { meta: parseMeta(script.code), - custom: Object.assign({ - origInclude: true, - origExclude: true, - origMatch: true, - origExcludeMatch: true, - }, script.custom), + custom: Object.assign({}, defaultCustom, script.custom), props: { id: script.id, uri: script.uri, diff --git a/src/background/utils/popup-tracker.js b/src/background/utils/popup-tracker.js index 913575a58a..1155f8eaca 100644 --- a/src/background/utils/popup-tracker.js +++ b/src/background/utils/popup-tracker.js @@ -1,37 +1,99 @@ -import { getActiveTab, sendTabCmd } from '#/common'; +import { getActiveTab, i18n, sendTabCmd } from '@/common'; import cache from './cache'; -import { getData } from './db'; -import { postInitialize } from './init'; -import { commands } from './message'; +import { getData, getScriptsByURL } from './db'; +import { badges, getFailureReason } from './icon'; +import { addOwnCommands, addPublicCommands, commands } from './init'; -export const popupTabs = {}; // { tabId: 1 } +/** @type {{[tabId: string]: chrome.runtime.Port}} */ +export const popupTabs = {}; +const getCacheKey = tabId => 'SetPopup' + tabId; -postInitialize.push(() => { - browser.runtime.onConnect.addListener(onPopupOpened); - browser.webRequest.onBeforeRequest.addListener(prefetchSetPopup, { - urls: [browser.runtime.getURL(browser.runtime.getManifest().browser_action.default_popup)], - types: ['main_frame'], - }); +addOwnCommands({ + async InitPopup() { + const tab = await getActiveTab() || {}; + const { url = '', id: tabId } = tab; + const data = commands.GetTabDomain(url); + const badgeData = badges[tabId] || {}; + let failure = getFailureReason(url, badgeData, ''); + // FF injects content scripts after update/install/reload + let reset = !IS_FIREFOX && !failure[0] && badgeData[INJECT] === undefined; + let cachedSetPopup = cache.pop(getCacheKey(tabId)); + if (reset && (cachedSetPopup ? !cachedSetPopup[0] : cachedSetPopup = {})) { + cachedSetPopup[0] = await augmentSetPopup( + { [IDS]: {}, menus: {} }, + { tab, url, [kFrameId]: 0, [kTop]: 1 }, + ); + } + if (!failure[0] && badgeData[INJECT] == null) { + if (!await isInjectable(tabId, badgeData)) { + failure = getFailureReason(''); + } else if (reset && (reset = cachedSetPopup[0][0])[SCRIPTS].length) { + /* We also show this after the background script is reloaded inside devtools, which keeps + the content script connected, but breaks GM_xxxValue, GM_xhr, and so on. */ + failure = [i18n('failureReasonRestarted'), IS_APPLIED]; + reset[INJECT_INTO] = 'off'; + } + } + data.tab = tab; + return [cachedSetPopup, data, failure]; + }, +}); + +addPublicCommands({ + /** Must be synchronous for proper handling of `return;` inside */ + SetPopup(data, src) { + const tabId = src.tab.id; + const key = getCacheKey(tabId); + if (popupTabs[tabId]) { + return; // allowing the visible popup's onMessage to handle this message + } + augmentSetPopup(data, src, key); + } +}); + +browser.runtime.onConnect.addListener(onPopupOpened); +browser.webRequest.onBeforeRequest.addListener(prefetchSetPopup, { + urls: [chrome.runtime.getURL(extensionManifest[BROWSER_ACTION].default_popup)], + types: ['main_frame'], }); +async function augmentSetPopup(data, src, key) { + data[MORE] = true; + const ids = data[IDS]; + const moreIds = getScriptsByURL(src.url, src[kTop], null, ids); + Object.assign(ids, moreIds); + Object.assign(data, await getData({ [IDS]: Object.keys(ids) })); + data = [data, src]; + if (!key) return data; + (cache.get(key) || cache.put(key, {}))[src[kFrameId]] = data; +} + +async function isInjectable(tabId, badgeData) { + return badgeData[INJECT] + && await sendTabCmd(tabId, VIOLENTMONKEY, null, { [kFrameId]: 0 }) + || ( + await browser.tabs.executeScript(tabId, { code: '1', [RUN_AT]: 'document_start' }) + .catch(() => []) + )[0]; +} + function onPopupOpened(port) { - const tabId = +port.name; - popupTabs[tabId] = 1; - sendTabCmd(tabId, 'PopupShown', true); - port.onDisconnect.addListener(onPopupClosed); - delete commands.SetPopup; + const [cmd, cached, tabId] = port.name.split(':'); + if (cmd !== 'Popup') return; + if (!cached) notifyTab(+tabId, true); + popupTabs[tabId] = port; + port.onDisconnect.addListener(() => { + delete popupTabs[tabId]; + notifyTab(+tabId, false); + }); } -function onPopupClosed({ name }) { - delete popupTabs[name]; - sendTabCmd(+name, 'PopupShown', false); +function prefetchSetPopup() { + getActiveTab().then(t => t && notifyTab(t.id, true)); } -async function prefetchSetPopup() { - const tabId = (await getActiveTab()).id; - sendTabCmd(tabId, 'PopupShown', true); - commands.SetPopup = async (data, src) => { - Object.assign(data, await getData(data.ids)); - cache.put('SetPopup', Object.assign({ [src.frameId]: [data, src] }, cache.get('SetPopup'))); - }; +function notifyTab(tabId, data) { + if (badges[tabId]) { + sendTabCmd(tabId, 'PopupShown', data); + } } diff --git a/src/background/utils/preinject.js b/src/background/utils/preinject.js index 9ea53dd33d..6866273313 100644 --- a/src/background/utils/preinject.js +++ b/src/background/utils/preinject.js @@ -1,204 +1,710 @@ -import { getScriptName, getUniqId } from '#/common'; -import { INJECT_CONTENT, INJECTABLE_TAB_URL_RE, METABLOCK_RE } from '#/common/consts'; -import initCache from '#/common/cache'; -import storage from '#/common/storage'; -import ua from '#/common/ua'; -import { getScriptsByURL } from './db'; -import { extensionRoot, postInitialize } from './init'; -import { commands } from './message'; -import { getOption, hookOptions } from './options'; +import { + getActiveTab, getScriptName, getScriptPrettyUrl, getUniqId, sendTabCmd, +} from '@/common'; +import { + __CODE, TL_AWAIT, UNWRAP, XHR_COOKIE_RE, + BLACKLIST, HOMEPAGE_URL, KNOWN_INJECT_INTO, META_STR, METABLOCK_RE, NEWLINE_END_RE, +} from '@/common/consts'; +import initCache from '@/common/cache'; +import { + deepCopy, forEachEntry, forEachValue, mapEntry, objectPick, objectSet, +} from '@/common/object'; +import { CACHE_KEYS, getScriptsByURL, kTryVacuuming, PROMISE, REQ_KEYS, VALUE_IDS } from './db'; +import { setBadge } from './icon'; +import { addOwnCommands, addPublicCommands } from './init'; +import { clearNotifications } from './notifications'; +import { hookOptionsInit } from './options'; import { popupTabs } from './popup-tracker'; +import { clearRequestsByTabId, reifyRequests } from './requests'; +import { kSetCookie } from './requests-core'; +import { updateVisitedTime } from './script'; +import { + S_CACHE, S_CACHE_PRE, S_CODE, S_CODE_PRE, S_REQUIRE, S_REQUIRE_PRE, S_SCRIPT_PRE, S_VALUE, + S_VALUE_PRE, +} from './storage'; +import { clearStorageCache, onStorageChanged } from './storage-cache'; +import { getFrameDocId, getFrameDocIdAsObj, tabsOnRemoved } from './tabs'; +import { addValueOpener, clearValueOpener, reifyValueOpener } from './values'; +import { ua } from './ua'; +let isApplied; +let injectInto; +let ffInject; +let xhrInject = false; // must be initialized for proper comparison when toggling +let xhrInjectKey; + +const sessionId = getUniqId(); +const API_HEADERS_RECEIVED = browser.webRequest.onHeadersReceived; const API_CONFIG = { urls: ['*://*/*'], // `*` scheme matches only http and https types: ['main_frame', 'sub_frame'], }; -const TIME_AFTER_SEND = 10e3; // longer as establishing connection to sites may take time -const TIME_AFTER_RECEIVE = 1e3; // shorter as response body will be coming very soon -const TIME_KEEP_DATA = 60e3; // 100ms should be enough but the tab may hang or get paused in debugger -const cacheCode = initCache({ lifetime: TIME_KEEP_DATA }); +const API_EXTRA = [ + 'blocking', // used for xhrInject and to make Firefox fire the event before GetInjected + kResponseHeaders, + browser.webRequest.OnHeadersReceivedOptions.EXTRA_HEADERS, +].filter(Boolean); +const findCspHeader = h => h.name.toLowerCase() === 'content-security-policy'; +const CSP_RE = /(?:^|[;,])\s*(?:script-src(-elem)?|(d)efault-src)(\s+[^;,]+)/g; +const NONCE_RE = /'nonce-([-+/=\w]+)'/; +const SKIP_COMMENTS_RE = /^\s*(?:\/\*[\s\S]*?\*\/|\/\/.*[\r\n]+|\s+)*/u; +/** Not using a combined regex to check for the chars to avoid catastrophic backtracking */ +const isUnsafeConcat = s => (s = s.charCodeAt(s.match(SKIP_COMMENTS_RE)[0].length)) === 45/*"-"*/ + || s === 43/*"+"*/ + || s === 91/*"["*/ + || s === 40/*"("*/; +const UNSAFE_INLINE = "'unsafe-inline'"; +/** These bags are reused in cache to reduce memory usage, + * CACHE_KEYS is for removeStaleCacheEntry */ +const BAG_NOOP = { [INJECT]: {}, [CACHE_KEYS]: [] }; +const BAG_NOOP_EXPOSE = { ...BAG_NOOP, [INJECT]: { [EXPOSE]: true, [kSessionId]: sessionId } }; +const CSAPI_REG = 'csReg'; +const contentScriptsAPI = browser.contentScripts; const cache = initCache({ - lifetime: TIME_KEEP_DATA, - async onDispose(promise) { - const data = await promise; - data.unregister?.(); + lifetime: 5 * 60e3, + onDispose(val) { + if (IS_FIREFOX) unregisterScriptFF(val); + cache.del(val[MORE]); }, }); -let injectInto; -hookOptions(changes => { - injectInto = changes.defaultInjectInto ?? injectInto; - if ('isApplied' in changes) togglePreinject(changes.isApplied); -}); -postInitialize.push(() => { - injectInto = getOption('defaultInjectInto'); - togglePreinject(getOption('isApplied')); -}); +// KEY_XXX for hooked options +const GRANT_NONE_VARS = '{GM,GM_info}'; +const META_KEYS_TO_ENSURE = [ + 'description', + 'name', + 'namespace', + [RUN_AT], + 'version', +]; +const META_KEYS_TO_ENSURE_FROM = [ + [HOMEPAGE_URL, 'homepage'], +]; +const META_KEYS_TO_PLURALIZE_RE = /^(?:(m|excludeM)atch|(ex|in)clude)$/; +const pluralizeMetaKey = (s, consonant) => s + (consonant ? 'es' : 's'); +const pluralizeMeta = key => key.replace(META_KEYS_TO_PLURALIZE_RE, pluralizeMetaKey); +const propsToClear = { + [S_CACHE_PRE]: CACHE_KEYS, + [S_CODE_PRE]: true, + [S_REQUIRE_PRE]: REQ_KEYS, + [S_SCRIPT_PRE]: true, + [S_VALUE_PRE]: VALUE_IDS, +}; +const expose = {}; +const resolveDataCodeStr = `(${(global, data) => { + if (global.vmResolve) global.vmResolve(data); // `window` is a const which is inaccessible here + else global.vmData = data; // Ran earlier than the main content script so just drop the payload +}})`; +const getKey = (url, isTop) => ( + isTop ? url : `-${url}` +); +const normalizeRealm = val => ( + KNOWN_INJECT_INTO[val] ? val : injectInto || AUTO +); +const normalizeScriptRealm = (custom, meta) => ( + normalizeRealm(custom[INJECT_INTO] || meta[INJECT_INTO]) +); +const isContentRealm = (val, force) => ( + val === CONTENT || val === AUTO && force +); +/** @param {chrome.webRequest.WebRequestHeadersDetails | chrome.webRequest.WebResponseHeadersDetails} info */ +const isTopFrame = info => info.frameType === 'outermost_frame' || !info[kFrameId]; -Object.assign(commands, { - InjectionFeedback(feedback, { tab, frameId }) { - feedback.forEach(([key, needsInjection]) => { - const code = cacheCode.pop(key); - // see TIME_KEEP_DATA comment - if (needsInjection && code) { - browser.tabs.executeScript(tab.id, { - code, - frameId, - runAt: 'document_start', - }); +const skippedTabs = {}; +export const reloadAndSkipScripts = async tab => { + if (!tab) tab = await getActiveTab(); + const tabId = tab.id; + const bag = cache.get(getKey(tab.url, true)); + const reg = IS_FIREFOX && bag && unregisterScriptFF(bag); + skippedTabs[tabId] = 1; + if (reg) await reg; + clearFrameData(tabId); + await browser.tabs.reload(tabId); +}; + +const OPT_HANDLERS = { + [BLACKLIST]: cache.destroy, + defaultInjectInto(value) { + value = normalizeRealm(value); + cache.destroy(); + if (injectInto) { // already initialized, so we should update the listener + if (value === CONTENT) { + API_HEADERS_RECEIVED.removeListener(onHeadersReceived); + } else if (isApplied && IS_FIREFOX && !xhrInject) { + API_HEADERS_RECEIVED.addListener(onHeadersReceived, API_CONFIG, API_EXTRA); } + } + injectInto = value; + }, + /** WARNING! toggleXhrInject should precede togglePreinject as it sets xhrInject variable */ + xhrInject: toggleXhrInject, + [IS_APPLIED]: togglePreinject, + [EXPOSE](value) { + value::forEachEntry(([site, isExposed]) => { + expose[decodeURIComponent(site)] = isExposed; }); }, +}; +if (contentScriptsAPI) OPT_HANDLERS.ffInject = toggleFastFirefoxInject; + +addOwnCommands({ + [SKIP_SCRIPTS]: reloadAndSkipScripts, }); -// Keys of the object returned by getScriptsByURL() -const propsToClear = { - [storage.cache.prefix]: 'cacheKeys', - [storage.code.prefix]: true, - [storage.require.prefix]: 'reqKeys', - [storage.script.prefix]: true, - [storage.value.prefix]: 'withValueIds', -}; +addPublicCommands({ + /** @return {Promise} */ + async GetInjected({ url, [FORCE_CONTENT]: forceContent, done }, src) { + const { tab, [kFrameId]: frameId, [kTop]: isTop } = src; + const frameDoc = getFrameDocId(isTop, src[kDocumentId], frameId); + const tabId = tab.id; + if (!url) url = src.url || tab.url; + clearFrameData(tabId, frameDoc); + let skip = skippedTabs[tabId]; + if (skip > 0) { // first time loading the tab after skipScripts was invoked + if (isTop) skippedTabs[tabId] = -1; // keeping a phantom for future iframes in this page + if (popupTabs[tabId]) sendPopupShown(tabId, frameDoc); + return { [INJECT_INTO]: SKIP_SCRIPTS }; + } + if (skip) delete skippedTabs[tabId]; // deleting the phantom as we're in a new page + const bagKey = getKey(url, isTop); + const bagP = cache.get(bagKey) || prepare(bagKey, url, isTop); + const bag = bagP[INJECT] ? bagP : await bagP[PROMISE]; + /** @type {VMInjection} */ + const inject = bag[INJECT]; + const scripts = inject[SCRIPTS]; + if (scripts) { + triageRealms(scripts, bag[FORCE_CONTENT] || forceContent, tabId, frameId, bag); + addValueOpener(scripts, tabId, frameDoc); + if (isTop < 2/* skip prerendered pages*/ && scripts.length) { + updateVisitedTime(scripts); + } + } + if (popupTabs[tabId]) { + sendPopupShown(tabId, frameDoc); + } + return isApplied + ? !done && inject + : { [INJECT_INTO]: 'off', ...inject }; + }, + async InjectionFeedback({ + [FORCE_CONTENT]: forceContent, + [CONTENT]: items, + [MORE]: moreKey, + url, + }, src) { + if (!isApplied) return; // the user disabled injection right after page started loading + const { tab, [kFrameId]: frameId } = src; + const isTop = src[kTop]; + const tabId = tab.id; + injectContentRealm(items, tabId, frameId); + if (!moreKey) return; + if (!url) url = src.url || tab.url; + const env = cache.get(moreKey) + || cache.put(moreKey, getScriptsByURL(url, isTop)) + || { [SCRIPTS]: [] }; // scripts got removed or the url got blacklisted after GetInjected + const envCache = (env[PROMISE] ? await env[PROMISE] : env)[S_CACHE]; + const scripts = prepareScripts(env); + triageRealms(scripts, forceContent, tabId, frameId); + addValueOpener(scripts, tabId, getFrameDocId(isTop, src[kDocumentId], frameId)); + if (isTop < 2/* skip prerendered pages*/ && scripts.length) { + updateVisitedTime(scripts); + } + return { + [SCRIPTS]: scripts, + [S_CACHE]: envCache, + }; + }, + Run({ [IDS]: ids, reset }, src) { + const { + [kDocumentId]: docId, + [kTop]: isTop, + tab: { id: tabId }, + } = src; + const hasIds = +ids?.[0]; + setBadge(ids, reset, src); + if (isTop === 3) { + if (hasIds) { + reifyValueOpener(ids, docId); + updateVisitedTime(ids, true); + } + reifyRequests(tabId, docId); + clearNotifications(tabId); + } + if (reset === 'bfcache' && hasIds) { + addValueOpener(ids, tabId, getFrameDocId(isTop, docId, src[kFrameId])); + } + }, +}); + +hookOptionsInit(onOptionChanged); -browser.storage.onChanged.addListener(async changes => { - const dbKeys = Object.keys(changes); - const cacheValues = await Promise.all(cache.getValues()); - const dirty = cacheValues.some(data => data.inject - && dbKeys.some((key) => { - const prefix = key.slice(0, key.indexOf(':') + 1); - const prop = propsToClear[prefix]; - key = key.slice(prefix.length); - return prop === true - || data[prop]?.includes(prefix === storage.value.prefix ? +key : key); - })); - if (dirty) { - clearCache(); +onStorageChanged((keys, data) => { + const toClear = []; + for (const key of keys) { + const i = key.indexOf(':') + 1; + const prefix = key.slice(0, i); + const id = key.slice(i); + /* TODO: only delete the script's entry if no impactful @key is changed in metablock? + Might be beneficial for local file tracking. */ + if (propsToClear[prefix] === true) { + cache.destroy(); + return; + } + let script, values; + // Patching cached script's values + if (prefix === S_VALUE_PRE) { + values = data?.[key]; + if ((script = cache.get(S_SCRIPT_PRE + id))) { + script[VALUES] = values = deepCopy(values) || script[VALUES] && {}; + // {} enables tracking in addValueOpener + } + } + // Removing values/require/resource in injection bags + if (prefix) { + toClear.push([prefix, id, values]); + } } + if (toClear.length) cache.some(removeStaleCacheEntry, toClear); }); -function clearCache() { - cacheCode.destroy(); - cache.destroy(); +/** @this {string[][]} changed storage keys, already split as [prefix,id] */ +function removeStaleCacheEntry(val, key) { + if (!val[CACHE_KEYS]) return; + for (const [prefix, id, newData] of this) { + const prop = propsToClear[prefix]; + if (val[prop]?.includes(+id || id)) { + if (prefix === S_REQUIRE_PRE) { + val.depsMap[id].forEach(scriptId => cache.del(S_SCRIPT_PRE + scriptId)); + } else if (prefix === S_VALUE_PRE) { + if (val[S_VALUE]) val[S_VALUE][id] = newData; // envDelayed + setBaggedScriptValues(val, +id, newData); + if (!val[CSAPI_REG]) continue; + } + cache.del(key); // TODO: try to patch the cache in-place? + } + } } -/** @return {Promise} */ -export function getInjectedScripts(url, tabId, frameId) { - return cache.pop(getKey(url, !frameId)) || prepare(url, tabId, frameId, true); +function setBaggedScriptValues(bag, id, val) { + for (const /** @type {VMInjection.Script} */scr of (bag[INJECT] || bag)[SCRIPTS]) { + if (scr.id === id) { + scr[VALUES] = val || scr[VALUES] && {}; + // {} enables tracking in addValueOpener + return true; + } + } } -function getKey(url, isTop) { - return isTop ? url : `-${url}`; +function onOptionChanged(changes) { + // DANGER! Must initialize in the specified order + for (const key in OPT_HANDLERS) { + if (key in changes) OPT_HANDLERS[key](changes[key]); + } + for (const key in changes) { + if (key.includes('.')) { // used by `expose.url` + onOptionChanged(objectSet({}, key, changes[key])); + } + } +} + +function toggleXhrInject(enable) { + if (enable) enable = injectInto !== CONTENT; + if (xhrInject === enable) return; + xhrInject = enable; + xhrInjectKey ??= extensionRoot.match(XHR_COOKIE_RE)[1]; + cache.destroy(); + API_HEADERS_RECEIVED.removeListener(onHeadersReceived); + if (enable) { + API_HEADERS_RECEIVED.addListener(onHeadersReceived, API_CONFIG, API_EXTRA); + } } function togglePreinject(enable) { + isApplied = enable; // Using onSendHeaders because onHeadersReceived in Firefox fires *after* content scripts. // And even in Chrome a site may be so fast that preinject on onHeadersReceived won't be useful. const onOff = `${enable ? 'add' : 'remove'}Listener`; const config = enable ? API_CONFIG : undefined; - browser.webRequest.onSendHeaders[onOff](preinject, config); - browser.webRequest.onHeadersReceived[onOff](prolong, config); - clearCache(); + browser.webRequest.onSendHeaders[onOff](onSendHeaders, config); + if (!isApplied /* remove the listener */ + || IS_FIREFOX && !xhrInject && injectInto !== CONTENT /* add 'nonce' detector */) { + API_HEADERS_RECEIVED[onOff](onHeadersReceived, config, config && API_EXTRA); + } + tabsOnRemoved[onOff](onTabRemoved); + browser.tabs.onReplaced[onOff](onTabReplaced); + if (!enable) { + cache.destroy(); + clearFrameData(); + clearStorageCache(); + } +} + +function toggleFastFirefoxInject(enable) { + ffInject = enable; + if (!enable) { + cache.some(v => { unregisterScriptFF(v); /* must return falsy! */ }); + } else if (!xhrInject) { + cache.destroy(); // nuking the cache so that CSAPI_REG is created for subsequent injections + } } -function preinject({ url, tabId, frameId }) { - if (!INJECTABLE_TAB_URL_RE.test(url)) return; - const isTop = !frameId; +/** @param {chrome.webRequest.WebRequestHeadersDetails} info */ +function onSendHeaders(info) { + const { url, tabId } = info; + const isTop = isTopFrame(info); const key = getKey(url, isTop); - if (!cache.has(key)) { - // GetInjected message will be sent soon by the content script - // and it may easily happen while getScriptsByURL is still waiting for browser.storage - // so we'll let GetInjected await this pending data by storing Promise in the cache - cache.put(key, prepare(url, tabId, frameId), TIME_AFTER_SEND); + if (!cache.has(key) && !skippedTabs[tabId]) { + prepare(key, url, isTop); } } -function prolong({ url, frameId }) { - cache.hit(getKey(url, !frameId), TIME_AFTER_RECEIVE); +/** @param {chrome.webRequest.WebResponseHeadersDetails} info */ +function onHeadersReceived(info) { + const key = getKey(info.url, isTopFrame(info)); + const bag = cache.get(key); + // The INJECT data is normally already in cache if code and values aren't huge + if (bag && !bag[FORCE_CONTENT] && bag[INJECT]?.[SCRIPTS] && !skippedTabs[info.tabId]) { + const ffReg = IS_FIREFOX && info.url.startsWith('https:') + && detectStrictCsp(info, bag); + const res = xhrInject && prepareXhrBlob(info, bag); + return ffReg ? ffReg.then(res && (() => res)) : res; + } } -async function prepare(url, tabId, frameId, isLate) { - const data = await getScriptsByURL(url, !frameId); - const { inject } = data; - inject.scripts.forEach(prepareScript, data); - inject.injectInto = injectInto; - inject.ua = ua; - inject.isFirefox = ua.isFirefox; - inject.isPopupShown = popupTabs[tabId]; - if (!isLate && browser.contentScripts) { - registerScriptDataFF(data, url, !!frameId); +/** + * @param {chrome.webRequest.WebResponseHeadersDetails} info + * @param {VMInjection.Bag} bag + */ +function prepareXhrBlob({ [kResponseHeaders]: responseHeaders, [kFrameId]: frameId, tabId }, bag) { + triageRealms(bag[INJECT][SCRIPTS], bag[FORCE_CONTENT], tabId, frameId, bag); + const blobUrl = URL.createObjectURL(new Blob([ + JSON.stringify(bag[INJECT]), + ])); + responseHeaders.push({ + name: kSetCookie, + value: `${xhrInjectKey}=${blobUrl.split('/').pop()}; SameSite=Lax`, + }); + setTimeout(URL.revokeObjectURL, 60e3, blobUrl); + return { [kResponseHeaders]: responseHeaders }; +} + +function prepare(cacheKey, url, isTop) { + const shouldExpose = isTop && url.startsWith('https://') + ? expose[url.split('/', 3)[2]] + : null; + const bagNoOp = shouldExpose != null ? BAG_NOOP_EXPOSE : BAG_NOOP; + BAG_NOOP_EXPOSE[INJECT][EXPOSE] = shouldExpose; + if (!isApplied) { + return bagNoOp; } - return data; + const errors = []; + // TODO: teach `getScriptEnv` to skip prepared scripts in cache + const env = getScriptsByURL(url, isTop, errors); + const res = env || bagNoOp; + cache.put(cacheKey, res); // must be called before prepareBag overwrites it synchronously + if (env) { + env[PROMISE] = prepareBag(cacheKey, url, isTop, + env, shouldExpose != null ? { [EXPOSE]: shouldExpose } : {}, errors); + } + return res; } -/** @this data */ -function prepareScript(script, index, scripts) { +async function prepareBag(cacheKey, url, isTop, env, inject, errors) { + if (env[PROMISE]) await env[PROMISE]; + if (!isApplied) return; // the user disabled injection while we awaited + cache.batch(true); + const bag = { [INJECT]: inject }; + const { allIds, [MORE]: envDelayed } = env; + const moreKey = envDelayed[IDS].length && getUniqId('more'); + Object.assign(inject, { + [SCRIPTS]: prepareScripts(env), + [INJECT_INTO]: injectInto, + [MORE]: moreKey, + [kSessionId]: sessionId, + [IDS]: allIds, + info: { ua }, + errors: errors.filter(err => allIds[err.split('#').pop()]).join('\n'), + }, objectPick(env, [ + S_CACHE, + 'clipFF', + 'xhr', + ])); + propsToClear::forEachValue(val => { + if (val !== true) bag[val] = env[val]; + }); + bag[MORE] = envDelayed; + if (ffInject && contentScriptsAPI && !xhrInject && isTop) { + inject[PAGE] = env[PAGE] || triagePageRealm(envDelayed); + bag[CSAPI_REG] = registerScriptDataFF(inject, url); + } + if (moreKey) { + cache.put(moreKey, envDelayed); + envDelayed[MORE] = cacheKey; + } + cache.put(cacheKey, bag); + cache.batch(false); + return bag; +} + +function prepareScripts(env) { + env[PROMISE] = null; // let GC have it + const scripts = env[SCRIPTS]; + for (let i = 0, script, key, id; i < scripts.length; i++) { + script = scripts[i]; + id = script.id; + if (!script[__CODE]) { + id = script.props.id; + key = S_SCRIPT_PRE + id; + script = cache.get(key) || cache.put(key, prepareScript(script, env)); + scripts[i] = script; + } + if (script[INJECT_INTO] !== CONTENT) { + env[PAGE] = true; // for registerScriptDataFF + } + script[VALUES] = env[S_VALUE][id] || null; + } + return scripts; +} + +/** + * @param {VMScript} script + * @param {VMInjection.EnvStart} env + * @return {VMInjection.Script} + */ +function prepareScript(script, env) { const { custom, meta, props } = script; const { id } = props; - const { require, values } = this; - const code = this.code[id]; - const dataKey = getUniqId('VMin'); + const { [S_REQUIRE]: require, [RUN_AT]: runAt } = env; + const code = env[S_CODE][id]; + const dataKey = getUniqId(); + const winKey = getUniqId(); + const plantKey = { data: dataKey, win: winKey }; const displayName = getScriptName(script); - const name = encodeURIComponent(displayName.replace(/[#&',/:;?@=]/g, replaceWithFullWidthForm)); - const isContent = (custom.injectInto || meta.injectInto || injectInto) === INJECT_CONTENT; const pathMap = custom.pathMap || {}; - const reqs = meta.require?.map(key => require[pathMap[key] || key]).filter(Boolean); - // trying to avoid progressive string concatenation of potentially huge code slices - // adding `;` on a new line in case some required script ends with a line comment - const reqsSlices = reqs ? [].concat(...reqs.map(req => [req, '\n;'])) : []; - const hasReqs = reqsSlices.length; - const injectedCode = [ - // hiding module interface from @require'd scripts so they don't mistakenly use it - `window.${dataKey}=function(${dataKey}){try{with(this)((define,module,exports)=>{`, - ...reqsSlices, - // adding a nested IIFE to support 'use strict' in the code when there are @requires - hasReqs ? '(()=>{' : '', - // TODO: move code above @require - code, - // adding a new line in case the code ends with a line comment - code.endsWith('\n') ? '' : '\n', - hasReqs ? '})()' : '', + const wrap = !meta[UNWRAP]; + const wrapTryCatch = wrap && IS_FIREFOX; // FF doesn't show errors in content script's console + const { grant, [TL_AWAIT]: topLevelAwait } = meta; + const startIIFE = topLevelAwait ? 'await(async' : '('; + const grantNone = grant.includes('none'); + const shouldUpdate = !!script.config.shouldUpdate; + // Storing slices separately to reuse JS-internalized strings for code in our storage cache + const injectedCode = []; + const metaCopy = meta::mapEntry(null, pluralizeMeta); + const metaStrMatch = METABLOCK_RE.exec(code); + let codeIndex; + let tmp; + for (const key of META_KEYS_TO_ENSURE) { + if (metaCopy[key] == null) metaCopy[key] = ''; + } + for (const [key, from] of META_KEYS_TO_ENSURE_FROM) { + if (!metaCopy[key] && (tmp = metaCopy[from])) { + metaCopy[key] = tmp; + } + } + metaCopy.options = { // TM-compatibility + check_for_updates: shouldUpdate, + inject_into: custom[INJECT_INTO] || null, + noframes: custom.noframes ?? null, + override: { + merge_excludes: !!custom.origExclude, + merge_includes: !!custom.origInclude, + merge_matches: !!custom.origMatch, + merge_exclude_matches: !!custom.origExcludeMatch, + use_excludes: custom.exclude || [], + use_includes: custom.include || [], + use_matches: custom.match || [], + use_exclude_matches: custom.excludeMatch || [], + }, + run_at: custom[RUN_AT] || null, + tags: custom.tags?.split(' ').filter(Boolean) || [], + user_modified: script.props.lastModified || 0, + }; + if (wrap) { + // TODO: push winKey/dataKey as separate chunks so we can change them for each injection? + injectedCode.push('window.', winKey, '=', + wrapTryCatch && topLevelAwait ? 'async ' : '', + 'function ', dataKey, '(', + // using a shadowed name to avoid scope pollution + grantNone ? GRANT_NONE_VARS : 'GM', + wrapTryCatch ? `,${dataKey}){try{` : '){', + grantNone ? '' : 'with(this)with(c)delete c,', + !topLevelAwait ? '(' : wrapTryCatch ? startIIFE : '(async', + // hiding module interface from @require'd scripts so they don't mistakenly use it + '(define,module,exports)=>{'); + } + tmp = false; + for (const url of meta[S_REQUIRE]) { + const req = require[pathMap[url] || url] || `/* ${VIOLENTMONKEY} is missing @require ${ + url.replace(/\*\//g, '%2A/') + }\n${kTryVacuuming} */`; + if (/\S/.test(req)) { + injectedCode.push(...[ + tmp && isUnsafeConcat(req) && ';', + req, + !NEWLINE_END_RE.test(req) && '\n', + ].filter(Boolean)); + tmp = true; + } + } + if (tmp && isUnsafeConcat(code)) { + injectedCode.push(';'); + } + codeIndex = injectedCode.length; + injectedCode.push(code); + // adding a new line in case the code ends with a line comment + injectedCode.push(...[ + !NEWLINE_END_RE.test(code) && '\n', + wrapTryCatch ? `})()}catch(e){${dataKey}(e)}}` : wrap && `})()}`, // 0 at the end to suppress errors about non-cloneable result of executeScript in FF - `})()}catch(e){${dataKey}(e)}};0`, - // Firefox lists .user.js among our own content scripts so a space at start will group them - `\n//# sourceURL=${extensionRoot}${ua.isFirefox ? '%20' : ''}${name}.user.js#${id}`, - ].join(''); - cacheCode.put(dataKey, injectedCode, TIME_KEEP_DATA); - scripts[index] = { - ...script, - dataKey, + IS_FIREFOX && ';0', + '\n//# sourceURL=', getScriptPrettyUrl(script, displayName), + ].filter(Boolean)); + return { + code: '', displayName, - code: isContent ? '' : injectedCode, - metaStr: code.match(METABLOCK_RE)[1] || '', - values: values[id], + gmi: { + scriptWillUpdate: shouldUpdate, + uuid: props.uuid, + }, + id, + key: plantKey, + meta: metaCopy, + pathMap, + [__CODE]: injectedCode, + [INJECT_INTO]: normalizeScriptRealm(custom, meta), + [META_STR]: [ + '', + codeIndex, + tmp = metaStrMatch && (metaStrMatch.index + metaStrMatch[1].length), + tmp + metaStrMatch?.[4].length, + ], + [RUN_AT]: runAt[id], }; } -function replaceWithFullWidthForm(s) { - // fullwidth range starts at 0xFF00, normal range starts at space char code 0x20 - return String.fromCharCode(s.charCodeAt(0) - 0x20 + 0xFF00); +function triageRealms(scripts, forceContent, tabId, frameId, bag) { + let code; + let wantsPage; + const toContent = []; + for (const /**@type{VMInjection.Script}*/ scr of scripts) { + const metaStr = scr[META_STR]; + if (isContentRealm(scr[INJECT_INTO], forceContent)) { + if (!metaStr[0]) { + const [, i, from, to] = metaStr; + metaStr[0] = scr[__CODE][i].slice(from, to); + } + code = ''; + toContent.push([scr.id, scr.key.data]); + } else { + metaStr[0] = ''; + code = forceContent ? ID_BAD_REALM : scr[__CODE]; + if (!forceContent) wantsPage = true; + } + scr.code = code; + } + if (bag) { + bag[INJECT][PAGE] = wantsPage || triagePageRealm(bag[MORE]); + } + if (toContent[0]) { + // Processing known feedback without waiting for InjectionFeedback message. + // Running in a separate task as executeScript may take a long time to serialize code. + setTimeout(injectContentRealm, 0, toContent, tabId, frameId); + } } -const resolveDataCodeStr = `(${(data) => { - const { vmResolve } = window; - if (vmResolve) { - vmResolve(data); - } else { - // running earlier than the main content script for whatever reason - window.vmData = data; +function triagePageRealm(env, forceContent) { + return env?.[SCRIPTS].some(isPageRealmScript, forceContent || null); +} + +function injectContentRealm(toContent, tabId, frameId) { + for (const [id, dataKey] of toContent) { + const scr = cache.get(S_SCRIPT_PRE + id); // TODO: recreate if expired? + if (!scr || scr.key.data !== dataKey) continue; + browser.tabs.executeScript(tabId, { + code: scr[__CODE].join(''), + [RUN_AT]: `document_${scr[RUN_AT]}`.replace('body', 'start'), + [kFrameId]: frameId, + }).then(scr.meta[UNWRAP] && (() => sendTabCmd(tabId, 'Run', id, { [kFrameId]: frameId }))); } -}})`; +} -function registerScriptDataFF(data, url, allFrames) { - const promise = browser.contentScripts.register({ - allFrames, +// TODO: rework the whole thing to register scripts individually with real `matches` +// (this will also allow proper handling of @noframes) +function registerScriptDataFF(inject, url) { + for (const scr of inject[SCRIPTS]) { + scr.code = scr[__CODE]; + } + return contentScriptsAPI.register({ js: [{ - code: `${resolveDataCodeStr}(${JSON.stringify(data.inject)})`, + code: `${resolveDataCodeStr}(this,${JSON.stringify(inject)})`, }], matches: url.split('#', 1), - runAt: 'document_start', + [RUN_AT]: 'document_start', }); - data.unregister = async () => { - data.unregister = null; - const r = await promise; - r.unregister(); - }; +} + +function unregisterScriptFF(bag) { + const reg = bag[CSAPI_REG]; + if (reg) { + delete bag[CSAPI_REG]; + return reg.then(r => r.unregister()); + } +} + +/** + * @param {chrome.webRequest.WebResponseHeadersDetails} info + * @param {VMInjection.Bag} bag + */ +function detectStrictCsp(info, bag) { + const h = info[kResponseHeaders].find(findCspHeader); + if (!h) return; + let tmp = ''; + let m, scriptSrc, scriptElemSrc, defaultSrc; + while ((m = CSP_RE.exec(h.value))) { + tmp += m[2] ? (defaultSrc = m[3]) : m[1] ? (scriptElemSrc = m[3]) : (scriptSrc = m[3]); + } + if (!tmp) return; + tmp = tmp.match(NONCE_RE); + if (tmp) { + bag[INJECT].nonce = tmp[1]; + } else if ( + scriptSrc && !scriptSrc.includes(UNSAFE_INLINE) || + scriptElemSrc && !scriptElemSrc.includes(UNSAFE_INLINE) || + !scriptSrc && !scriptElemSrc && defaultSrc && !defaultSrc.includes(UNSAFE_INLINE) + ) { + bag[FORCE_CONTENT] = bag[INJECT][FORCE_CONTENT] = true; + } else { + return; + } + m = unregisterScriptFF(bag); + if (m && !tmp) { + // Registering only without nonce, otherwise FF will incorrectly reuse it on tab reload + return Promise.all([ + m, + bag[CSAPI_REG] = registerScriptDataFF(bag[INJECT], info.url), + ]); + } +} + +/** @this {?} truthy = forceContent */ +function isPageRealmScript(scr) { + return !isContentRealm(scr[INJECT_INTO] || normalizeScriptRealm(scr.custom, scr.meta), this); +} + +function onTabRemoved(id /* , info */) { + clearFrameData(id, 0, true); + delete skippedTabs[id]; +} + +function onTabReplaced(addedId, removedId) { + onTabRemoved(removedId); +} + +function clearFrameData(tabId, frameId, tabRemoved) { + clearRequestsByTabId(tabId, frameId); + clearValueOpener(tabId, frameId); + clearNotifications(tabId, frameId, tabRemoved); +} + +function sendPopupShown(tabId, frameDoc) { + setTimeout(sendTabCmd, 0, tabId, 'PopupShown', true, getFrameDocIdAsObj(frameDoc)); } diff --git a/src/background/utils/requests-core.js b/src/background/utils/requests-core.js new file mode 100644 index 0000000000..b8b8cb287d --- /dev/null +++ b/src/background/utils/requests-core.js @@ -0,0 +1,200 @@ +import { buffer2string, getUniqId, isEmpty, noop } from '@/common'; +import { forEachEntry } from '@/common/object'; +import { CHROME } from './ua'; + +let encoder; + +export const VM_VERIFY = getUniqId('VM-Verify'); +/** @type {Object} */ +export const requests = { __proto__: null }; +export const verify = { __proto__: null }; +export const FORBIDDEN_HEADER_RE = re`/ +^( + # prefix matches + proxy-| + sec- +)|^( + # whole name matches + # https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name + # https://cs.chromium.org/?q=file:cc+symbol:IsForbiddenHeader%5Cb + accept-(charset|encoding)| + access-control-request-(headers|method)| + connection| + content-length| + cookie2?| + date| + dnt| + expect| + host| + keep-alive| + origin| + referer| + te| + trailer| + transfer-encoding| + upgrade| + via +)$/ix`; +/** @type {chrome.webRequest.RequestFilter} */ +const API_FILTER = { + urls: [''], + types: ['xmlhttprequest'], +}; +const EXTRA_HEADERS = [ + browser.webRequest.OnBeforeSendHeadersOptions.EXTRA_HEADERS, +].filter(Boolean); +const headersToInject = {}; +/** @param {chrome.webRequest.HttpHeader} header */ +const isVmVerify = header => header.name === VM_VERIFY; +export const kCookie = 'cookie'; +export const kSetCookie = 'set-cookie'; +const SET_COOKIE_VALUE_RE = re` + /^\s* (?:__(Secure|Host)-)? ([^=\s]+) \s*=\s* (")? ([!#-+\--:<-[\]-~]*) \3(.*) /x`; +const SET_COOKIE_ATTR_RE = re` + /\s* ;?\s* (\w+) (?:= (")? ([!#-+\--:<-[\]-~]*) \2)? /xy`; +const SAME_SITE_MAP = { + strict: 'strict', + lax: 'lax', + none: 'no_restriction', +}; +const kRequestHeaders = 'requestHeaders'; +const API_EVENTS = { + onBeforeSendHeaders: [ + onBeforeSendHeaders, kRequestHeaders, 'blocking', ...EXTRA_HEADERS, + ], + onHeadersReceived: [ + onHeadersReceived, kResponseHeaders, 'blocking', ...EXTRA_HEADERS, + ], +}; + +/** @param {chrome.webRequest.WebRequestHeadersDetails} details */ +function onHeadersReceived({ [kResponseHeaders]: headers, requestId, url }) { + const req = requests[verify[requestId]]; + if (req) { + // Populate responseHeaders for GM_xhr's `response` + req[kResponseHeaders] = headers.map(encodeWebRequestHeader).join(''); + const { storeId } = req; + // Drop Set-Cookie headers if anonymous or using a custom storeId + if (!req[kSetCookie] || storeId) { + headers = headers.filter(h => { + if (h.name.toLowerCase() !== kSetCookie) return true; + if (storeId) setCookieInStore(h.value, storeId, url); + }); + return { [kResponseHeaders]: headers }; + } + } +} + +/** @param {chrome.webRequest.WebRequestHeadersDetails} details */ +function onBeforeSendHeaders({ [kRequestHeaders]: headers, requestId, url }) { + // only the first call during a redirect/auth chain will have VM-Verify header + const reqId = verify[requestId] || headers.find(isVmVerify)?.value; + const req = requests[reqId]; + if (req) { + verify[requestId] = reqId; + req.coreId = requestId; + req.url = url; // remember redirected URL with #hash as it's stripped in XHR.responseURL + const headersMap = {}; + const headers2 = headersToInject[reqId]; + const combinedHeaders = headers2 && {}; + let name; + let h2 = !headers2; + for (const h of headers) { + if ((name = h.name) === VM_VERIFY + || (name = name.toLowerCase()) === 'origin' && h.value === extensionOrigin + || name === kCookie && !req[kCookie]) { + continue; + } + if (!h2 && name === kCookie && (h2 = headers2[name])) { + combinedHeaders[name] = { name, value: h.value + '; ' + h2.value }; + } else { + headersMap[name] = h; + } + } + return { + [kRequestHeaders]: Object.values(Object.assign(headersMap, headers2, combinedHeaders)) + }; + } +} + +export function toggleHeaderInjector(reqId, headers) { + if (headers) { + /* Listening even if `headers` array is empty to get the request's id. + * Registering just once to avoid a bug in Chrome: + * it adds a new internal registration even if the function reference is the same */ + if (isEmpty(headersToInject)) { + API_EVENTS::forEachEntry(([name, [listener, ...options]]) => { + browser.webRequest[name].addListener(listener, API_FILTER, options); + }); + } + // Adding even if empty so that the toggle-off `if` runs just once even when called many times + headersToInject[reqId] = headers; + } else if (reqId in headersToInject) { + delete headersToInject[reqId]; + if (isEmpty(headersToInject)) { + API_EVENTS::forEachEntry(([name, [listener]]) => { + browser.webRequest[name].removeListener(listener); + }); + } + } +} + +/** + * Imitating https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/getAllResponseHeaders + * Per the specification https://tools.ietf.org/html/rfc7230 the header name is within ASCII, + * but we'll try encoding it, if necessary, to handle invalid server responses. + */ +function encodeWebRequestHeader({ name, value, binaryValue }) { + return `${string2byteString(name)}: ${ + binaryValue + ? buffer2string(binaryValue) + : string2byteString(value) + }\r\n`; +} + +/** + * @param {string} headerValue + * @param {string} storeId + * @param {string} url + */ +function setCookieInStore(headerValue, storeId, url) { + let m = SET_COOKIE_VALUE_RE.exec(headerValue); + if (m) { + const [, prefix, name, , value, optStr] = m; + const opt = {}; + const isHost = prefix === 'Host'; + SET_COOKIE_ATTR_RE.lastIndex = 0; + while ((m = SET_COOKIE_ATTR_RE.exec(optStr))) { + opt[m[1].toLowerCase()] = m[3]; + } + const sameSite = opt.sameSite?.toLowerCase(); + browser.cookies.set({ + url, + name, + value, + domain: isHost ? undefined : opt.domain, + expirationDate: Math.max(0, +new Date(opt['max-age'] * 1000 || opt.expires)) || undefined, + httpOnly: 'httponly' in opt, + path: isHost ? '/' : opt.path, + sameSite: SAME_SITE_MAP[sameSite], + secure: url.startsWith('https:') && (!!prefix || sameSite === 'none' || 'secure' in opt), + storeId, + }); + } +} + +/** + * Returns a UTF8-encoded binary string i.e. one byte per character. + * Returns the original string in case it was already within ASCII. + */ +function string2byteString(str) { + if (!/[\u0080-\uFFFF]/.test(str)) return str; + if (!encoder) encoder = new TextEncoder(); + return buffer2string(encoder.encode(str)); +} + +// Chrome 74-91 needs an extraHeaders listener at tab load start, https://crbug.com/1074282 +// We're attaching a no-op in non-blocking mode so it's very lightweight and fast. +if (CHROME >= 74 && CHROME <= 91) { + browser.webRequest.onBeforeSendHeaders.addListener(noop, API_FILTER, EXTRA_HEADERS); +} diff --git a/src/background/utils/requests.js b/src/background/utils/requests.js index f5a9e0715a..cdc3cb9877 100644 --- a/src/background/utils/requests.js +++ b/src/background/utils/requests.js @@ -1,44 +1,44 @@ -import { buffer2string, getUniqId, request, i18n, isEmpty, noop, sendTabCmd } from '#/common'; -import { forEachEntry, objectPick } from '#/common/object'; -import ua from '#/common/ua'; +import { blob2base64, sendTabCmd, string2uint8array } from '@/common'; +import { CHARSET_UTF8, FORM_URLENCODED, UA_PROPS, UPLOAD } from '@/common/consts'; +import { downloadBlob } from '@/common/download'; +import { deepEqual, forEachEntry, forEachValue, objectPick } from '@/common/object'; import cache from './cache'; -import { isUserScript, parseMeta } from './script'; -import { extensionRoot } from './init'; -import { commands } from './message'; +import { addPublicCommands, commands } from './init'; +import { + FORBIDDEN_HEADER_RE, VM_VERIFY, requests, toggleHeaderInjector, verify, kCookie, kSetCookie, +} from './requests-core'; +import { getFrameDocIdAsObj, getFrameDocIdFromSrc } from './tabs'; +import { FIREFOX, navUA, navUAD } from './ua'; +import { vetUrl } from './url'; -const VM_VERIFY = 'VM-Verify'; -const requests = {}; -const verify = {}; -const tabRequests = {}; - -Object.assign(commands, { - ConfirmInstall: confirmInstall, - /** @return {string} */ - GetRequestId(eventsToNotify = [], src) { - const id = getUniqId(); - const tabId = src.tab?.id; - requests[id] = { +addPublicCommands({ + /** + * @param {GMReq.Message.Web} opts + * @param {VMMessageSender} src + * @return {Promise} + */ + HttpRequest(opts, src) { + const tabId = src.tab.id; + const frameId = getFrameDocIdFromSrc(src); + const { id, events } = opts; + /** @type {GMReq.BG} */ + const req = requests[id] = { id, tabId, - eventsToNotify, + [kFrameId]: frameId, + frame: getFrameDocIdAsObj(frameId), xhr: new XMLHttpRequest(), }; - if (tabId) { - let set = tabRequests[tabId]; - if (!set) { - set = new Set(); - tabRequests[tabId] = set; - } - set.add(id); - } - return id; - }, - /** @return {void} */ - HttpRequest(details, src) { - const { tab, frameId } = src; - httpRequest(details, src, res => ( - sendTabCmd(tab.id, 'HttpRequested', res, { frameId }) - )); + const cb = res => requests[id] && ( + sendTabCmd(tabId, 'HttpRequested', res, req.frame) + ); + return httpRequest(opts, events, src, cb) + .catch(err => cb({ + id, + [ERROR]: [err.message || `${err}`, err.name], + data: null, + type: ERROR, + })); }, /** @return {void} */ AbortRequest(id) { @@ -57,437 +57,290 @@ Object.assign(commands, { }, }); -const specialHeaders = [ - 'user-agent', - // https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name - // https://cs.chromium.org/?q=file:cc+symbol:IsForbiddenHeader%5Cb - 'accept-charset', - 'accept-encoding', - 'access-control-request-headers', - 'access-control-request-method', - 'connection', - 'content-length', - 'cookie', - 'cookie2', - 'date', - 'dnt', - 'expect', - 'host', - 'keep-alive', - 'origin', - 'referer', - 'te', - 'trailer', - 'transfer-encoding', - 'upgrade', - 'via', -]; -// const tasks = {}; -const HeaderInjector = (() => { - /** @type chrome.webRequest.RequestFilter */ - const apiFilter = { - urls: [''], - types: ['xmlhttprequest'], - }; - const EXTRA_HEADERS = [ - browser.webRequest.OnBeforeSendHeadersOptions.EXTRA_HEADERS, - ].filter(Boolean); - const headersToInject = {}; - /** @param {chrome.webRequest.HttpHeader} header */ - const isVmVerify = header => header.name === VM_VERIFY; - const isNotCookie = header => !/^cookie2?$/i.test(header.name); - const isNotSetCookie = header => !/^set-cookie2?$/i.test(header.name); - const isSendable = header => header.name !== VM_VERIFY; - const isSendableAnon = header => isSendable(header) && isNotCookie(header); - const apiEvents = { - onBeforeSendHeaders: { - options: ['requestHeaders', 'blocking', ...EXTRA_HEADERS], - /** @param {chrome.webRequest.WebRequestHeadersDetails} details */ - listener({ requestHeaders: headers, requestId }) { - // only the first call during a redirect/auth chain will have VM-Verify header - const reqId = headers.find(isVmVerify)?.value || verify[requestId]; - const req = reqId && requests[reqId]; - if (reqId && req) { - verify[requestId] = reqId; - req.coreId = requestId; - headers = (req.noNativeCookie ? headers.filter(isNotCookie) : headers) - .concat(headersToInject[reqId] || []) - .filter(req.anonymous ? isSendableAnon : isSendable); - } - return { requestHeaders: headers }; - }, - }, - onHeadersReceived: { - options: ['responseHeaders', 'blocking', ...EXTRA_HEADERS], - /** @param {chrome.webRequest.WebRequestHeadersDetails} details */ - listener({ responseHeaders: headers, requestId }) { - const req = requests[verify[requestId]]; - if (req) { - const oldLength = headers.length; - if (req.anonymous) headers = headers.filter(isNotSetCookie); - // mimic https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/getAllResponseHeaders - req.responseHeaders = headers - .map(({ name, value }) => `${name}: ${value}\r\n`) - .sort() - .join(''); - if (headers.length < oldLength) return { responseHeaders: headers }; - } - }, - }, - }; - // Chrome 74+ needs to have any extraHeaders listener at tab load start, https://crbug.com/1074282 - // We're attaching a no-op in non-blocking mode so it's very lightweight and fast. - // TODO: check the version range (via feature detection?) when it's fixed in Chrome - if (ua.isChrome && TextEncoder.prototype.encodeInto) { - browser.webRequest.onBeforeSendHeaders.addListener(noop, apiFilter, ['extraHeaders']); - } - return { - add(reqId, headers) { - // need to set the entry even if it's empty [] so that 'if' check in del() runs only once - headersToInject[reqId] = headers; - // need the listener to get the requestId - apiEvents::forEachEntry(([name, { listener, options }]) => { - browser.webRequest[name].addListener(listener, apiFilter, options); - }); - }, - del(reqId) { - if (reqId in headersToInject) { - delete headersToInject[reqId]; - if (isEmpty(headersToInject)) { - apiEvents::forEachEntry(([name, { listener }]) => { - browser.webRequest[name].removeListener(listener); - }); - } - } - }, - }; -})(); +/* 1MB takes ~20ms to encode/decode so it doesn't block the process of the extension and web page, + * which lets us and them be responsive to other events or user input. */ +const CHUNK_SIZE = 1e6; +const TEXT_CHUNK_SIZE = IS_FIREFOX + ? 256e6 // Firefox: max 512MB and string char is 2 bytes (unicode) + : 10e6; // Chrome: max 64MB and string char is 6 bytes max (like \u0001 in internal JSON) +const BLOB_LIFE = 60e3; +const SEND_XHR_PROPS = ['readyState', 'status', 'statusText']; +const SEND_PROGRESS_PROPS = ['lengthComputable', 'loaded', 'total']; +const quoteHeaderValue = str => `"${str.replace(/[\\"]/g, '\\$&')}"`; +const SEC_CH_UA = 'sec-ch-ua'; +const UA_GETTERS = { + __proto__: null, + 'user-agent': val => val, + /** @param {NavigatorUABrandVersion[]} brands */ + [SEC_CH_UA]: brands => brands.map(b => `${quoteHeaderValue(b.brand)};v="${b.version}"`).join(', '), + [SEC_CH_UA + '-mobile']: val => `?${val ? 1 : 0}`, + [SEC_CH_UA + '-platform']: quoteHeaderValue, +}; +const UA_HEADERS = Object.keys(UA_GETTERS); + +function blob2chunk(response, index, size) { + return blob2base64(response, index * size, size); +} + +function blob2objectUrl(response) { + const url = URL.createObjectURL(response); + cache.put(`xhrBlob:${url}`, setTimeout(URL.revokeObjectURL, BLOB_LIFE, url), BLOB_LIFE); + return url; +} -function xhrCallbackWrapper(req) { +function text2chunk(response, index, size) { + return response.substr(index * size, size); +} + +/** + * @param {GMReq.BG} req + * @param {GMReq.EventTypeMap[]} events + * @param {boolean} blobbed + * @param {boolean} chunked + * @param {boolean} isJson + */ +function xhrCallbackWrapper(req, events, blobbed, chunked, isJson) { let lastPromise = Promise.resolve(); let contentType; - let numChunks; + let dataSize; + let numChunks = 0; + let chunkSize; + let getChunk; + let fullResponse = null; let response; - let responseText; let responseHeaders; - let sent = false; - const { id, chunkType, xhr } = req; - // Chrome encodes messages to UTF8 so they can grow up to 4x but 64MB is the message size limit - const chunkSize = 64e6 / 4; - const isBlob = chunkType === 'blob'; - const getChunk = isBlob - ? () => { - const url = URL.createObjectURL(response); - cache.put(`xhrBlob:${url}`, setTimeout(commands.RevokeBlob, 60e3, url), 61e3); - return url; + let sent = true; + let sentTextLength = 0; + let sentReadyState4; + let tmp; + const { id, xhr } = req; + const getResponseHeaders = () => req[kResponseHeaders] || xhr.getAllResponseHeaders(); + const eventQueue = []; + const sequentialize = async () => { + const evt = eventQueue.shift(); + const upload = evt.target === xhr ? 0 : 1; + const { type } = evt; + const shouldNotify = events[upload][type]; + const isEnd = !upload && type === 'loadend'; + const readyState4 = xhr.readyState === 4 || (sentReadyState4 = false); // reset on redirection + if (!shouldNotify && !isEnd && type !== ERROR) { + return; } - : index => buffer2string(response, index * chunkSize, chunkSize); - const getResponseHeaders = () => { - const headers = req.responseHeaders || xhr.getAllResponseHeaders(); - if (responseHeaders !== headers) { - responseHeaders = headers; - return { responseHeaders }; + // Firefox duplicates readystatechange for state=4 randomly, #1862 + if (readyState4 && type === 'readystatechange') { + if (sentReadyState4) return; + sentReadyState4 = true; } - }; - const chainedCallback = (msg) => { - lastPromise = lastPromise.then(() => req.cb(msg)); - }; - return (evt) => { - const type = evt.type; - if (type === 'loadend') clearRequest(req); - if (!req.cb) return; if (!contentType) { - contentType = xhr.getResponseHeader('Content-Type') || 'application/octet-stream'; + contentType = xhr.getResponseHeader('Content-Type') || ''; } - if (xhr.response !== response) { - response = xhr.response; - try { - responseText = xhr.responseText; - if (responseText === response) responseText = ['same']; - } catch (e) { - // ignore if responseText is unreachable - } - if (chunkType && response) { - numChunks = !isBlob && Math.ceil(response.byteLength / chunkSize) || 1; + if (!upload && fullResponse !== xhr[kResponse]) { + fullResponse = response = xhr[kResponse]; + sent = false; + if (response) { + if ((tmp = response.length - sentTextLength)) { // a non-empty text response has `length` + chunked = tmp > TEXT_CHUNK_SIZE; + chunkSize = TEXT_CHUNK_SIZE; + dataSize = tmp; + getChunk = text2chunk; + response = sentTextLength ? response.slice(sentTextLength) : response; + sentTextLength += dataSize; + } else { + chunkSize = CHUNK_SIZE; + dataSize = response.size; + getChunk = blobbed ? blob2objectUrl : blob2chunk; + } + numChunks = chunked ? Math.ceil(dataSize / chunkSize) || 1 + : blobbed ? 1 : 0; } } - const shouldNotify = req.eventsToNotify.includes(type); - // only send response when XHR is complete - const shouldSendResponse = xhr.readyState === 4 && shouldNotify && !sent; - chainedCallback({ - contentType, - id, - chunkType, - numChunks, - type, - data: shouldNotify && { - finalUrl: xhr.responseURL, - ...getResponseHeaders(), - ...objectPick(xhr, ['readyState', 'status', 'statusText']), - ...shouldSendResponse && { - response: numChunks ? getChunk(0) : response, - responseText, - }, - ...('loaded' in evt) && objectPick(evt, ['lengthComputable', 'loaded', 'total']), - }, - }); + if (response && isEnd && req[kFileName]) { + downloadBlob(response, req[kFileName]); + } + const shouldSendResponse = !upload && shouldNotify && (!isJson || readyState4) && !sent; if (shouldSendResponse) { sent = true; for (let i = 1; i < numChunks; i += 1) { - chainedCallback({ + await req.cb({ id, - chunkIndex: i, - chunk: getChunk(i), + i, + chunk: i * chunkSize, + data: await getChunk(response, i, chunkSize), + size: dataSize, }); } } + /* WARNING! We send `null` in the mandatory props because Chrome can't send `undefined`, + * and for simple destructuring and `prop?.foo` in the receiver without getOwnProp checks. */ + await req.cb({ + blobbed, + chunked, + contentType, + id, + type, + /** @type {VMScriptResponseObject} */ + data: shouldNotify ? { + finalUrl: req.url || xhr.responseURL, + ...objectPick(xhr, SEND_XHR_PROPS), + ...objectPick(evt, SEND_PROGRESS_PROPS), + [kResponse]: shouldSendResponse + ? (numChunks ? await getChunk(response, 0, chunkSize) : response) + : null, + [kResponseHeaders]: responseHeaders !== (tmp = getResponseHeaders()) + ? (responseHeaders = tmp) + : null, + } : null, + [UPLOAD]: upload, + }); + if (isEnd) { + clearRequest(req); + } + }; + return (evt) => { + eventQueue.push(evt); + lastPromise = lastPromise.then(sequentialize); }; -} - -function isSpecialHeader(lowerHeader) { - return specialHeaders.includes(lowerHeader) - || lowerHeader.startsWith('proxy-') - || lowerHeader.startsWith('sec-'); } /** - * @param {Object} details - * @param {chrome.runtime.MessageSender} src + * @param {GMReq.Message.Web} opts + * @param {GMReq.EventTypeMap[]} events + * @param {VMMessageSender} src * @param {function} cb + * @returns {Promise} */ -async function httpRequest(details, src, cb) { - const { id, chunkType, overrideMimeType, url } = details; +async function httpRequest(opts, events, src, cb) { + const { tab } = src; + const { incognito } = tab; + const { anonymous, id, overrideMimeType, [kXhrType]: xhrType } = opts; + const url = vetUrl(opts.url, src.url, true); const req = requests[id]; if (!req || req.cb) return; req.cb = cb; - req.anonymous = details.anonymous; - // Firefox applies page CSP even to content script fetches of own blobs https://bugzil.la/1294996 - req.chunkType = chunkType && (src.tab.incognito || ua.isFirefox) ? 'arraybuffer' : chunkType; + req[kFileName] = opts[kFileName]; const { xhr } = req; - const vmHeaders = []; - // Firefox doesn't send cookies, - // https://github.com/violentmonkey/violentmonkey/issues/606 - let shouldSendCookies = ua.isFirefox && !details.anonymous; - xhr.open(details.method || 'GET', url, true, details.user || '', details.password || ''); + const vmHeaders = {}; + // Firefox can send Blob/ArrayBuffer directly + const willStringifyBinaries = xhrType && !IS_FIREFOX; + // Chrome can't fetch Blob URL in incognito so we use chunks + const chunked = willStringifyBinaries && incognito; + const blobbed = willStringifyBinaries && !incognito; + const [body, contentType] = decodeBody(opts.data); + // Firefox doesn't send cookies, https://github.com/violentmonkey/violentmonkey/issues/606 + // Both Chrome & FF need explicit routing of cookies in containers or incognito + const shouldSendCookies = !anonymous && (incognito || IS_FIREFOX); + const uaHeaders = []; + req[kCookie] = !anonymous && !shouldSendCookies; + req[kSetCookie] = !anonymous; + xhr.open(opts.method || 'GET', url, true, opts.user || '', opts.password || ''); xhr.setRequestHeader(VM_VERIFY, id); - details.headers::forEachEntry(([name, value]) => { - const lowerName = name.toLowerCase(); - if (isSpecialHeader(lowerName)) { - vmHeaders.push({ name, value }); - } else if (!lowerName.startsWith('vm-')) { - // `VM-` headers are reserved + if (contentType) xhr.setRequestHeader('Content-Type', contentType); + opts.headers::forEachEntry(([name, value]) => { + const nameLow = name.toLowerCase(); + const i = UA_HEADERS.indexOf(nameLow); + if (i >= 0 && (uaHeaders[i] = true) || FORBIDDEN_HEADER_RE.test(name)) { + pushWebRequestHeader(vmHeaders, name, value, nameLow); + } else { xhr.setRequestHeader(name, value); } - if (lowerName === 'cookie') { - shouldSendCookies = false; + }); + opts.ua.forEach((val, i) => { + if (!uaHeaders[i] && !deepEqual(val, !i ? navUA : navUAD[UA_PROPS[i]])) { + const name = UA_HEADERS[i]; + pushWebRequestHeader(vmHeaders, name, UA_GETTERS[name](val), name); } }); - xhr.responseType = req.chunkType || 'text'; - xhr.timeout = Math.max(0, Math.min(0x7FFF_FFFF, details.timeout)) || 0; + xhr[kResponseType] = willStringifyBinaries && 'blob' || xhrType || 'text'; + xhr.timeout = Math.max(0, Math.min(0x7FFF_FFFF, opts.timeout)) || 0; if (overrideMimeType) xhr.overrideMimeType(overrideMimeType); if (shouldSendCookies) { + for (const store of await browser.cookies.getAllCookieStores()) { + if (store.tabIds.includes(tab.id)) { + if (IS_FIREFOX ? !store.id.endsWith('-default') : store.id !== '0') { + /* Cookie routing. For the main store we rely on the browser. + * The ids are hard-coded as `stores` may omit the main store if no such tabs are open. */ + req.storeId = store.id; + } + break; + } + } const now = Date.now() / 1000; const cookies = (await browser.cookies.getAll({ url, - storeId: src.tab.cookieStoreId, - ...ua.isFirefox >= 59 && { firstPartyDomain: null }, + storeId: req.storeId, + ...FIREFOX >= 59 && { firstPartyDomain: null }, })).filter(c => c.session || c.expirationDate > now); // FF reports expired cookies! if (cookies.length) { - req.noNativeCookie = true; - vmHeaders.push({ - name: 'cookie', - value: cookies.map(c => `${c.name}=${c.value};`).join(' '), - }); + pushWebRequestHeader(vmHeaders, kCookie, + cookies.map(c => `${c.name}=${c.value};`).join(' ')); } } - HeaderInjector.add(id, vmHeaders); - const callback = xhrCallbackWrapper(req); - req.eventsToNotify.forEach(evt => { xhr[`on${evt}`] = callback; }); + toggleHeaderInjector(id, vmHeaders); + // Sending as params to avoid storing one-time init data in `requests` + const callback = xhrCallbackWrapper(req, events, blobbed, chunked, opts[kResponseType] === 'json'); + const onerror = 'on' + ERROR; + for (const evt in events[0]) xhr[`on${evt}`] = callback; + for (const evt in events[1]) xhr[UPLOAD][`on${evt}`] = callback; xhr.onloadend = callback; // always send it for the internal cleanup - xhr.send(details.data ? decodeBody(details.data) : null); + xhr[onerror] = xhr[UPLOAD][onerror] = callback; // show it in tab's console if there's no callback + xhr.send(body); } -function clearRequest(req) { - if (req.coreId) delete verify[req.coreId]; - delete requests[req.id]; - HeaderInjector.del(req.id); - tabRequests[req.tabId]?.delete(req.id); +/** @param {GMReq.BG} req */ +function clearRequest({ id, coreId }) { + delete verify[coreId]; + delete requests[id]; + toggleHeaderInjector(id, false); } -function decodeBody(obj) { - const { cls, value } = obj; - if (cls === 'formdata') { - const result = new FormData(); - if (value) { - value::forEachEntry(([key, items]) => { - items.forEach((item) => { - result.append(key, decodeBody(item)); - }); - }); +export function clearRequestsByTabId(tabId, frameId) { + requests::forEachValue(req => { + if ((tabId == null || req.tabId === tabId) + && (!frameId || req[kFrameId] === frameId)) { + commands.AbortRequest(req.id); } - return result; - } - if (['blob', 'file'].includes(cls)) { - const { type, name, lastModified } = obj; - const array = new Uint8Array(value.length); - for (let i = 0; i < value.length; i += 1) array[i] = value.charCodeAt(i); - const data = [array.buffer]; - if (cls === 'file') return new File(data, name, { type, lastModified }); - return new Blob(data, { type }); - } - if (value) return JSON.parse(value); + }); } -// Watch URL redirects -// browser.webRequest.onBeforeRedirect.addListener(details => { -// const reqId = verify[details.requestId]; -// if (reqId) { -// const req = requests[reqId]; -// if (req) req.finalUrl = details.redirectUrl; -// } -// }, { -// urls: [''], -// types: ['xmlhttprequest'], -// }); - -// tasks are not necessary now, turned off -// Stop redirects -// browser.webRequest.onHeadersReceived.addListener(details => { -// const task = tasks[details.requestId]; -// if (task) { -// delete tasks[details.requestId]; -// if (task === 'Get-Location' && [301, 302, 303].includes(details.statusCode)) { -// const locationHeader = details.responseHeaders.find( -// header => header.name.toLowerCase() === 'location'); -// const base64 = locationHeader && locationHeader.value; -// return { -// redirectUrl: `data:text/plain;charset=utf-8,${base64 || ''}`, -// }; -// } -// } -// }, { -// urls: [''], -// types: ['xmlhttprequest'], -// }, ['blocking', 'responseHeaders']); -// browser.webRequest.onCompleted.addListener(details => { -// delete tasks[details.requestId]; -// }, { -// urls: [''], -// types: ['xmlhttprequest'], -// }); -// browser.webRequest.onErrorOccurred.addListener(details => { -// delete tasks[details.requestId]; -// }, { -// urls: [''], -// types: ['xmlhttprequest'], -// }); - -async function confirmInstall({ code, from, url }, { tab = {} }) { - if (!code) code = (await request(url)).data; - // TODO: display the error in UI - if (!isUserScript(code)) throw i18n('msgInvalidScript'); - cache.put(url, code, 3000); - const confirmKey = getUniqId(); - const { id: tabId, incognito } = tab; - cache.put(`confirm-${confirmKey}`, { incognito, url, from, tabId }); - const { windowId } = await browser.tabs.create({ - url: `/confirm/index.html#${confirmKey}`, - index: tab.index + 1 || undefined, - active: !!tab.active, - ...tabId >= 0 && ua.openerTabIdSupported && !incognito && { - openerTabId: tabId, - }, +export function reifyRequests(tabId, documentId) { + const frameObj = getFrameDocIdAsObj(0); + requests::forEachValue(req => { + if (req.tabId === tabId && req[kFrameId] === documentId) { + req[kFrameId] = 0; + req.frame = frameObj; + } }); - if (windowId !== tab.windowId) { - await browser.windows.update(windowId, { focused: true }); - } } -const whitelistRe = new RegExp(`^https://(${ - [ - 'greasyfork\\.org/scripts/%/code/', - 'openuserjs\\.org/install/%/', - 'github\\.com/%/%/raw/%/', - 'github\\.com/%/%/releases/%/download/', - 'raw\\.githubusercontent\\.com(/%){3}/', - 'gist\\.github\\.com/.*?/', - ].join('|') -})%?\\.user\\.js([?#]|$)`.replace(/%/g, '[^/]*')); - -const blacklistRe = new RegExp(`^https://(${ - [ - '(gist\\.)?github\\.com', - 'greasyfork\\.org', - 'openuserjs\\.org', - ].join('|') -})/`); - -browser.tabs.onCreated.addListener((tab) => { - // FF 68+ can't read file URLs directly so we need to keep the tab open - if (/\.user\.js([?#]|$)/.test(tab.pendingUrl || tab.url) - && !(ua.isFirefox >= 68 && tab.url.startsWith('file:'))) { - cache.put(`autoclose:${tab.id}`, true, 10e3); - } -}); - -browser.webRequest.onBeforeRequest.addListener((req) => { - const { method, tabId, url } = req; - if (method !== 'GET') { - return; - } - // open a real URL for simplified userscript URL listed in devtools of the web page - if (url.startsWith(extensionRoot)) { - const id = +url.split('#').pop(); - const redirectUrl = `${extensionRoot}options/index.html#scripts/${id}`; - return { redirectUrl }; - } - if (!cache.has(`bypass:${url}`) - && (!blacklistRe.test(url) || whitelistRe.test(url))) { - maybeInstallUserJs(tabId, url); - return { redirectUrl: 'javascript:void 0' }; // eslint-disable-line no-script-url - } -}, { - urls: [ - // 1. *:// comprises only http/https - // 2. the API ignores #hash part - // 3. Firefox: onBeforeRequest does not work with file:// or moz-extension:// - '*://*/*.user.js', - '*://*/*.user.js?*', - 'file://*/*.user.js', - 'file://*/*.user.js?*', - `${extensionRoot}*.user.js`, - ], - types: ['main_frame'], -}, ['blocking']); - -async function maybeInstallUserJs(tabId, url) { - const { data: code } = await request(url).catch(noop) || {}; - if (code && parseMeta(code).name) { - const tab = tabId >= 0 && await browser.tabs.get(tabId) || {}; - confirmInstall({ code, url, from: tab.url }, { tab }); - if (cache.has(`autoclose:${tabId}`) - || tab.pendingUrl && tab.url === 'chrome://newtab/') { - browser.tabs.remove(tabId); +/** Polyfill for browser's inability to send complex types over extension messaging */ +function decodeBody([body, type, wasBlob]) { + if (type === 'fd') { + // FF supports FormData over messaging + // Chrome doesn't - we use this code only with an empty FormData just to create the object + const res = new FormData(); + body.forEach(entry => res.append(...entry)); + body = res; + type = ''; + } else if (type === 'usp') { + type = FORM_URLENCODED + ';' + CHARSET_UTF8; + } else if (type != null) { + const res = string2uint8array(undefined, body.slice(body.indexOf(',') + 1)); + if (!wasBlob) { + type = body.match(/^data:(.+?);base64/)[1].replace(/(boundary=)[^;]+/, + // using a function so it runs only if "boundary" was found + (_, p1) => p1 + String.fromCharCode(...res.slice(2, res.indexOf(13)))); } - } else { - cache.put(`bypass:${url}`, true, 10e3); - if (tabId >= 0) browser.tabs.update(tabId, { url }); + body = res; } + return [body, type]; } -// In Firefox with production code of Violentmonkey, scripts can be injected before `tabs.onUpdated` is fired. -// Ref: https://github.com/violentmonkey/violentmonkey/issues/1255 - -browser.tabs.onRemoved.addListener((tabId) => { - clearRequestsByTabId(tabId); -}); - -export function clearRequestsByTabId(tabId) { - const set = tabRequests[tabId]; - if (set) { - delete tabRequests[tabId]; - for (const id of set) { - commands.AbortRequest(id); - } - } +/** + * @param {Object} res + * @param {string} name + * @param {string} value + * @param {string} [nameLow] + */ +function pushWebRequestHeader(res, name, value, nameLow = name) { + res[nameLow] = { name, value }; } diff --git a/src/background/utils/script.js b/src/background/utils/script.js index fb9d822d3e..cf41f773f1 100644 --- a/src/background/utils/script.js +++ b/src/background/utils/script.js @@ -1,29 +1,49 @@ -import { getUniqId, encodeFilename } from '#/common'; -import { METABLOCK_RE } from '#/common/consts'; -import { mapEntry } from '#/common/object'; -import { commands } from './message'; -import { getOption } from './options'; -import cache from './cache'; +import { + encodeFilename, getFullUrl, getScriptHome, getScriptSupportUrl, i18n, noop, sendCmd, +} from '@/common'; +import { + __CODE, HOMEPAGE_URL, INFERRED, METABLOCK_RE, SUPPORT_URL, TL_AWAIT, UNWRAP, +} from '@/common/consts'; +import { formatDate } from '@/common/date'; +import { mapEntry } from '@/common/object'; +import defaults, { kScriptTemplate } from '@/common/options-defaults'; +import { addOwnCommands, commands } from './init'; +import { getOption, hookOptionsInit } from './options'; +import storage, { S_MOD_PRE, S_SCRIPT_PRE } from './storage'; +import { injectableRe } from './tabs'; -Object.assign(commands, { - /** @return {string} */ - CacheNewScript(data) { - const id = getUniqId(); - cache.put(`new-${id}`, newScript(data)); - return id; +addOwnCommands({ + async NewScript(tabId) { + const tabUrl = (tabId >= 0 && await browser.tabs.get(tabId).catch(noop) || {}).url; + const url = injectableRe.test(tabUrl) && `${tabUrl.split(/[#?]/)[0]}*`; + const { host = 'example.org', domain } = url ? commands.GetTabDomain(url) : {}; + return newScript({ + url: url || `*://${host}/*`, + name: domain || '', + }); }, - /** @return {VMScript} */ - NewScript(id) { - return id && cache.get(`new-${id}`) || newScript(); - }, - ParseMeta: parseMeta, }); -export function isUserScript(text) { - if (/^\s* { + if (!firstRun && kScriptTemplate in changes) { + const errors = []; + const tpl = changes[kScriptTemplate]; + const meta = !tpl /*empty = default*/ || parseMeta(tpl, { errors }); + if (!meta) errors.unshift(i18n('msgInvalidScript')); + if (errors.length) throw errors; + } +}); + +/** @type {{ [id: string]: VMScript }} */ +export const scriptMap = {}; +/** @type {{ [id: string]: number }} */ +export const scriptSiteVisited = {}; +/** @type {VMScript[]} */ +export const aliveScripts = []; +/** @type {VMScript[]} */ +export const removedScripts = []; +/** @return {boolean|?RegExpExecArray} */ +export const matchUserScript = text => !/^\s* [], @@ -32,6 +52,10 @@ const arrayType = { return res; }, }; +const booleanType = { + default: () => false, + transform: () => true, +}; const defaultType = { default: () => null, transform: (res, val) => (res == null ? val : res), @@ -51,37 +75,69 @@ const metaTypes = { }, }, grant: arrayType, - noframes: { - default: () => false, - transform: () => true, - }, }; const metaOptionalTypes = { antifeature: arrayType, compatible: arrayType, connect: arrayType, + noframes: booleanType, + [TL_AWAIT]: booleanType, + [UNWRAP]: booleanType, }; -export function parseMeta(code) { +/** 0 1 2 3 4 */ +const META_ITEM_RE = /(?:^|\n)(.*?)\/\/([\x20\t]*)(@\S+)(.*)/g; +export const ERR_META_SPACE_BEFORE = 'Unexpected text before "//" in '; +export const ERR_META_SPACE_INSIDE = 'Expected a single space after "//" in '; + +/** + * @param {string} code + * @param {object} [opts] + * @param {Array} [opts.errors] - to collect errors + * @param {boolean} [opts.retDefault] - returns the default empty meta if no meta is found + * @param {boolean} [opts.retMetaStr] - adds the matched part as [__CODE] prop in result + * @return {VMScript['meta'] | false} + */ +export function parseMeta(code, { errors, retDefault, retMetaStr } = {}) { // initialize meta - const meta = metaTypes::mapEntry(([, value]) => value.default()); - const metaBody = code.match(METABLOCK_RE)[1] || ''; - metaBody.replace(/(?:^|\n)\s*\/\/\x20(@\S+)(.*)/g, (_match, rawKey, rawValue) => { - const [keyName, locale] = rawKey.slice(1).split(':'); + const meta = metaTypes::mapEntry(value => value.default()); + const match = matchUserScript(code); + if (!match) return retDefault ? meta : false; + // TODO: use `null` instead of `false` + null check in all callers? + if (errors) checkMetaItemErrors(match, 1, errors); + let parts; + while ((parts = META_ITEM_RE.exec(match[4]))) { + const [keyName, locale] = parts[3].slice(1).split(':'); const camelKey = keyName.replace(/[-_](\w)/g, (m, g) => g.toUpperCase()); const key = locale ? `${camelKey}:${locale.toLowerCase()}` : camelKey; - const val = rawValue.trim(); + const val = parts[4].trim(); const metaType = metaTypes[key] || metaOptionalTypes[key] || defaultType; let oldValue = meta[key]; if (typeof oldValue === 'undefined') oldValue = metaType.default(); + if (errors) checkMetaItemErrors(parts, 0, errors); meta[key] = metaType.transform(oldValue, val); - }); + } + if (errors) checkMetaItemErrors(match, 5, errors); meta.resources = meta.resource; delete meta.resource; - // @homepageURL: compatible with @homepage - if (!meta.homepageURL && meta.homepage) meta.homepageURL = meta.homepage; + if (retMetaStr) meta[__CODE] = match[0]; return meta; } +function checkMetaItemErrors(parts, index, errors) { + let clipped; + if (parts[index + 1].match(/\S/)) { + errors.push(ERR_META_SPACE_BEFORE + (clipped = clipString(parts[index], 50))); + } + if (parts[index + 2] !== ' ') { + errors.push(ERR_META_SPACE_INSIDE + (clipped || clipString(parts[index], 50))); + } +} + +function clipString(line, maxLen) { + line = line.trim(); + return JSON.stringify(line.length > maxLen ? line.slice(0, maxLen) + '...' : line); +} + export function getDefaultCustom() { return { origInclude: true, @@ -95,21 +151,22 @@ export function newScript(data) { const state = { url: '*://*/*', name: '', - date: new Date().toLocaleString(), ...data, }; - const code = getOption('scriptTemplate') - .replace(/{{(\w+)}}/g, (str, name) => { - const value = state[name]; - return value == null ? str : value; - }); + const code = (getOption(kScriptTemplate) || defaults[kScriptTemplate]) + .replace(/{{(\w+)(?::(.+?))?}}/g, (str, name, format) => state[name] ?? ( + name !== 'date' ? str + : format ? formatDate(format) + : new Date().toLocaleString() + )); const script = { custom: getDefaultCustom(), config: { enabled: 1, shouldUpdate: 1, }, - meta: parseMeta(code), + meta: parseMeta(code, { retDefault: true }), + props: {}, }; return { script, code }; } @@ -121,3 +178,108 @@ export function getNameURI(script) { if (!ns && !name) nameURI += script.props.id || ''; return nameURI; } + +/** + * @param {VMScript} script + * @returns {string | undefined} + */ +function inferScriptHome(script) { + let u = script.custom.lastInstallURL; + if (u) { + u = u.split('/', 6); + switch (u[2]) { + case 'update.greasyfork.org': + case 'update.sleazyfork.org': + u[2] = u[2].slice(7); + // fallthrough + case 'greasyfork.org': + case 'sleazyfork.org': + if (u[3] !== 'scripts') u.splice(3, 1); + break; + case 'raw.githubusercontent.com': + u[2] = 'github.com'; + break; + case 'github.com': + break; + case 'openuserjs.org': + u[3] = 'scripts'; + u[4] = u[4].replace(/(\.min)?\.user\.js$/, ''); + break; + default: + u = false; + } + if (u) { + u.length = 5; // scheme + 1 + host + group + name + u = u.join('/'); + } + } + if (!u) { + u = script.meta.namespace; + u = /^https?:\/\/(?!tampermonkey\.net\/)/.test(u) + && getFullUrl(u).replace(/^https?(:\/\/userscripts)(\.org\/users\/\w)/, 'https$1-mirror$2'); + } + return u; +} + +/** + * @param {VMScript} script + * @param {string} [home] + * @returns {string | undefined} + */ +function inferScriptSupportUrl(script, home = getScriptHome(script)) { + let u = home && home.match(re`/ + ^https:\/\/(?: + (?: + (greas|sleaz)yfork\.(?:org|cc)(?:\/(?!scripts)[^/]+)? | + openuserjs\.org + )(?=\/scripts\/) | + github\.com + )\/[^/]+\/[^/]+/x`); + if (u) { + return `${u[0]}/${u[1] ? 'feedback' : 'issues'}`; + } +} + +/** @param {VMScript} script */ +export function inferScriptProps(script) { + const data = script[INFERRED] ??= {}; + const home = data[HOMEPAGE_URL] ??= getScriptHome(script) || inferScriptHome(script); + data[SUPPORT_URL] ??= !getScriptSupportUrl(script) && inferScriptSupportUrl(script, home); + data.visit = scriptSiteVisited[script.props.id]; +} + +/** + * @param {VMInjection.Script[] | number[]} arr + * @param {boolean} [isIds] + */ +export function updateVisitedTime(arr, isIds) { + const now = Date.now(); + const toBroadcast = {}; + const toWrite = {}; + for (let v of arr) { + if (!isIds) v = v.id; + scriptSiteVisited[v] = toBroadcast[v] = toWrite[S_MOD_PRE + v] = now; + } + sendCmd('Visited', toBroadcast); + storage.api.set(toWrite); +} + +/** `key` must be already verified to start with S_SCRIPT_PRE */ +export function updateScriptMap(key, val) { + if ((key = +key.slice(S_SCRIPT_PRE.length))) { + if (val) { + const oldScript = scriptMap[key]; + if (oldScript) { + const i1 = aliveScripts.indexOf(oldScript); + const i2 = removedScripts.indexOf(oldScript); + if (i1 >= 0) aliveScripts[i1] = val; + if (i2 >= 0) removedScripts[i2] = val; + val[INFERRED] ??= oldScript[INFERRED]; + } + scriptMap[key] = val; + } else { + delete scriptMap[key]; + } + return true; + } +} diff --git a/src/background/utils/search.js b/src/background/utils/search.js deleted file mode 100644 index 3e7697e835..0000000000 --- a/src/background/utils/search.js +++ /dev/null @@ -1,13 +0,0 @@ -export function loadQuery(string) { - return string.split('&').reduce((data, piece) => { - const [key, val] = piece.split('=').map(decodeURIComponent); - data[key] = val; - return data; - }, {}); -} - -export function dumpQuery(dict) { - return Object.entries(dict) - .map(keyVal => keyVal.map(encodeURIComponent).join('=')) - .join('&'); -} diff --git a/src/background/utils/storage-cache.js b/src/background/utils/storage-cache.js new file mode 100644 index 0000000000..fe9493f73d --- /dev/null +++ b/src/background/utils/storage-cache.js @@ -0,0 +1,247 @@ +import { ensureArray, ignoreChromeErrors, initHooks, isEmpty, sendCmd } from '@/common'; +import initCache from '@/common/cache'; +import { INFERRED, WATCH_STORAGE } from '@/common/consts'; +import { deepCopy, deepCopyDiff, deepSize, forEachEntry } from '@/common/object'; +import { dbKeys, scriptSizes, sizesPrefixRe } from './db'; +import { scriptSiteVisited, updateScriptMap } from './script'; +import storage, { S_MOD_PRE, S_SCRIPT_PRE, S_VALUE, S_VALUE_PRE } from './storage'; +import { clearValueOpener } from './values'; + +/** Throttling browser API for `storage.value`, processing requests sequentially, + so that we can supersede an earlier chained request if it's obsolete now, + e.g. in a chain like [GET:foo, SET:foo=bar] `bar` will be used in GET. */ +let valuesToFlush = {}; +/** @type {Object} */ +let valuesToWatch = {}; +let flushTimer = 0; +let undoing; +const watchers = {}; +/** Reading the entire db in init/vacuum/sizing shouldn't be cached for long. */ +const TTL_SKIM = 5e3; +/** Keeping data for long time since chrome.storage.local is insanely slow in Chrome, + * so reading just a few megabytes would inject all scripts after document-end. */ +const TTL_MAIN = 3600e3; +/** Keeping tiny info for extended period of time as it's inexpensive. */ +const TTL_TINY = 24 * 3600e3; +const cache = initCache({ lifetime: TTL_MAIN }); +const api = /** @type {browser.storage.StorageArea} */ storage.api; +/** Using a simple delay with setTimeout to avoid infinite debouncing due to periodic activity */ +const FLUSH_DELAY = 100; +const FLUSH_SIZE_STEP = 1e6; // each step increases delay by FLUSH_DELAY +const FLUSH_MAX_DELAY = 1000; // e.g. when writing more than 10MB for step=1MB and delay=100ms +const { hook, fire } = initHooks(); + +/** + * Not using browser.storage.onChanged to improve performance, as it sends data across processes. + * WARNING: when editing the db directly in devtools, restart the background page via Ctrl-R. +*/ +export const onStorageChanged = hook; +export const clearStorageCache = () => { + cache.destroy(); + dbKeys.clear(); +}; +export const storageCacheHas = cache.has; + +export const cachedStorageApi = storage.api = { + + async get(keys) { + const res = {}; + cache.batch(true); + keys = keys?.filter(key => { + const cached = cache.get(key); + const ok = cached !== undefined; + if (ok) res[key] = deepCopy(cached); + return !ok && dbKeys.get(key) !== 0; + }); + if (!keys || keys.length) { + let lifetime, id; + if (!keys) lifetime = TTL_SKIM; // DANGER! Must be `undefined` otherwise. + (await api.get(keys))::forEachEntry(([key, val]) => { + res[key] = val; + dbKeys.set(key, 1); + cache.put(key, deepCopy(val), lifetime); + if (key.startsWith(S_SCRIPT_PRE)) { + updateScriptMap(key, val); + } else if (key.startsWith(S_MOD_PRE) && (id = +key.slice(S_MOD_PRE.length))) { + scriptSiteVisited[id] = val; + } + }); + keys?.forEach(key => !hasOwnProperty(res, key) && dbKeys.set(key, 0)); + } + cache.batch(false); + return res; + }, + + async set(data, flushNow) { + const toWrite = {}; + const keys = []; + cache.batch(true); + data::forEachEntry(([key, val]) => { + const copy = deepCopyDiff(val, cache.get(key)); + if (copy !== undefined) { + cache.put(key, copy); + dbKeys.set(key, 1); + keys.push(key); + if (undoing) { + toWrite[key] = val; + return; + } + if (!flushNow && key.startsWith(S_VALUE_PRE)) { + valuesToFlush[key] = copy; + } else { + toWrite[key] = val; + if (key.startsWith(S_SCRIPT_PRE) && updateScriptMap(key, val) && val[INFERRED]) { + delete (toWrite[key] = { ...val })[INFERRED]; + } + updateScriptSizeContributor(key, val); + } + } + }); + cache.batch(false); + if (!isEmpty(toWrite)) await api.set(toWrite); + if (undoing) return; + if (keys.length) fire(keys, data); + flushLater(); + }, + + async remove(keys) { + const toDelete = keys.filter(key => { + let ok = dbKeys.get(key) !== 0; + if (ok) { + cache.del(key); + dbKeys.set(key, 0); + if (undoing) return ok; + if (storage[S_VALUE].toId(key)) { + valuesToFlush[key] = null; + ok = false; + } else { + if (key.startsWith(S_SCRIPT_PRE)) updateScriptMap(key); + updateScriptSizeContributor(key); + } + } + return ok; + }); + if (toDelete.length) await api.remove(toDelete); + if (undoing) return; + if (keys.length) fire(keys); + flushLater(); + }, +}; + +setInterval(() => { + dbKeys.forEach((val, key) => !val && dbKeys.delete(key)); +}, TTL_TINY); +window[WATCH_STORAGE] = fn => { + const id = performance.now(); + watchers[id] = fn; + return id; +}; +browser.runtime.onConnect.addListener(port => { + if (port.name === 'undoImport') return undoImport(port); + if (!port.name.startsWith(WATCH_STORAGE)) return; + const { id, cfg, tabId } = JSON.parse(port.name.slice(WATCH_STORAGE.length)); + const fn = id ? watchers[id] : port.postMessage.bind(port); + watchStorage(fn, cfg); + port.onDisconnect.addListener(() => { + clearValueOpener(tabId); + watchStorage(fn, cfg, false); + delete watchers[id]; + }); +}); + +function watchStorage(fn, cfg, state = true) { + if (state && !valuesToWatch) { + valuesToWatch = {}; + } + cfg::forEachEntry(([area, ids]) => { + const { prefix } = storage[area]; + for (const id of ensureArray(ids)) { + const key = prefix + id; + const list = valuesToWatch[key] || state && (valuesToWatch[key] = []); + const i = list ? list.indexOf(fn) : -1; + if (i >= 0 && !state) { + list.splice(i, 1); + if (!list.length) delete valuesToWatch[key]; + } else if (i < 0 && state) { + list.push(fn); + } + } + }); + if (isEmpty(valuesToWatch)) { + valuesToWatch = null; + } +} + +async function updateScriptSizeContributor(key, val) { + const area = sizesPrefixRe.exec(key); + if (area && area[0] !== S_SCRIPT_PRE) { + const size = scriptSizes[key] = deepSize(val); + if (size === 2 && area[0] === S_VALUE_PRE) { + scriptSizes[key] = 0; // don't count an empty {} + } + } +} + +async function flush() { + const keys = Object.keys(valuesToFlush); + const toRemove = []; + const toFlush = valuesToFlush; + valuesToFlush = {}; + flushTimer = 0; + keys.forEach(key => { + const val = toFlush[key]; + if (!val) { + delete toFlush[key]; + toRemove.push(key); + } + updateScriptSizeContributor(key, val); + }); + if (!isEmpty(toFlush)) await api.set(toFlush); + if (toRemove.length) await api.remove(toRemove); + if (valuesToWatch) setTimeout(notifyWatchers, 0, toFlush, toRemove); +} + +function flushLater() { + if (!flushTimer && !isEmpty(valuesToFlush)) { + flushTimer = setTimeout(flush, + Math.min(FLUSH_MAX_DELAY, FLUSH_DELAY * Math.max(1, deepSize(valuesToFlush) / FLUSH_SIZE_STEP))); + } +} + +function notifyWatchers(toFlush, toRemove) { + const byFn = new Map(); + let newValue; + let changes; + for (const key in valuesToWatch) { + if ((newValue = toFlush[key]) || toRemove.includes(key)) { + for (const fn of valuesToWatch[key]) { + if (!(changes = byFn.get(fn))) byFn.set(fn, changes = {}); + changes[key] = { newValue }; + } + } + } + byFn.forEach((val, fn) => fn(val)); +} + +async function undoImport(port) { + let drop; + let old; + port.onDisconnect.addListener(() => { + ignoreChromeErrors(); + drop = true; + }); + port.onMessage.addListener(async () => { + valuesToFlush = {}; + const cur = await cachedStorageApi.get(); + const toRemove = Object.keys(cur).filter(k => !(k in old)); + const delay = Math.max(50, Math.min(500, performance.getEntries()[0]?.duration || 200)); + undoing = true; + if (toRemove.length) await cachedStorageApi.remove(toRemove); + await cachedStorageApi.set(old); + port.postMessage(true); + await sendCmd('Reload', delay); + location.reload(); + }); + old = await api.get(); + if (!drop) port.postMessage(true); +} diff --git a/src/background/utils/storage-fetch.js b/src/background/utils/storage-fetch.js index db510753aa..24a6a2e9ce 100644 --- a/src/background/utils/storage-fetch.js +++ b/src/background/utils/storage-fetch.js @@ -1,54 +1,88 @@ -import { request } from '#/common'; -import storage from '#/common/storage'; +import { isCdnUrlRe, isDataUri, isRemote, makeRaw, request } from '@/common'; +import { NO_CACHE } from '@/common/consts'; +import storage from './storage'; +import { getUpdateInterval } from './update'; +import { requestLimited } from './url'; -/** @type { function(url, options, check): Promise } or throws on error */ storage.cache.fetch = cacheOrFetch({ - init(options) { - return { ...options, responseType: 'arraybuffer' }; - }, - async transform(response, url, options, check) { - const [type, body] = storage.cache.makeRaw(response, true); - await check?.(url, response.data, type); - return `${type},${body}`; - }, + init: options => ({ ...options, [kResponseType]: 'blob' }), + transform: response => makeRaw(response), }); -/** @type { function(url, options): Promise } or throws on error */ -storage.require.fetch = cacheOrFetch(); +storage.require.fetch = cacheOrFetch({ + transform: ({ data }, url) => ( + /^\s* */ return function cacheOrFetchHandler(...args) { const [url] = args; const promise = requests[url] || (requests[url] = this::doFetch(...args)); return promise; }; - /** @this storage. */ + /** @this {VMStorageArea} */ async function doFetch(...args) { const [url, options] = args; try { - const res = await request(url, init?.(options) || options); - if (await isModified(res, url)) { + const res = await requestNewer(url, init ? init(options) : options); + if (res) { const result = transform ? await transform(res, ...args) : res.data; - await this.set(url, result); + await this.setOne(url, result); + if (options === 'res') { + return result; + } } - } catch (err) { - if (process.env.DEBUG) console.error(`Error fetching: ${url}`, err); - throw err; } finally { delete requests[url]; } } } -async function isModified({ headers }, url) { - const mod = headers.get('etag') - || +new Date(headers.get('last-modified')) - || +new Date(headers.get('date')); - if (!mod || mod !== await storage.mod.getOne(url)) { - if (mod) await storage.mod.set(url, mod); - return true; +/** + * @param {string} url + * @param {VMReq.OptionsMulti} [opts] + * @return {Promise | void} + */ +export async function requestNewer(url, opts) { + if (isDataUri(url)) { + return; + } + let multi, modOld, modDate; + const isLocal = !isRemote(url); + if (!isLocal && opts && (multi = opts[MULTI]) + && isObject(modOld = await storage.mod.getOne(url))) { + [modOld, modDate] = modOld; + } + if (multi === AUTO && modDate > Date.now() - getUpdateInterval()) { + return; + } + for (const get of multi ? [0, 1] : [1]) { + if (modOld || get) { + const req = await (isLocal || isCdnUrlRe.test(url) ? request : requestLimited)(url, + get ? opts + : { ...opts, ...NO_CACHE, method: 'HEAD' }); + const { headers } = req; + const mod = ( + headers.get('etag') + || +new Date(headers.get('last-modified')) + || +new Date(headers.get('date')) + ); + if (mod && mod === modOld) { + return; + } + if (get) { + if (mod) storage.mod.setOne(url, [mod, Date.now()]); + else if (modOld) storage.mod.remove(url); + return req; + } + } } } diff --git a/src/background/utils/storage.js b/src/background/utils/storage.js new file mode 100644 index 0000000000..076251ee2e --- /dev/null +++ b/src/background/utils/storage.js @@ -0,0 +1,122 @@ +import { mapEntry } from '@/common/object'; +import { ensureArray } from '@/common/util'; +import { addOwnCommands } from './init'; + +let api = browser.storage.local; + +/** @prop {VMStorageFetch} [fetch] */ +class VMStorageArea { + constructor(name, prefix) { + storageByPrefix[prefix] = this; // eslint-disable-line no-use-before-define + this.name = name; + this.prefix = prefix; + } + + /** @return {string} */ + toKey(id) { + return this.prefix + id; + } + + /** @return {string} */ + toId(key) { + return key.startsWith(this.prefix) + ? key.slice(this.prefix.length) + : ''; + } + + /** + * @param {string|number} id + * @return {Promise} + */ + async getOne(id) { + const key = this.toKey(id); + return (await api.get([key]))[key]; + } + + /** + * @param {?string[]} [ids] - if null/absent, the entire storage is returned + * @param {function(val:?,key:string):?} [transform] + * @return {Promise} - single value or object of id:value + */ + async getMulti(ids, transform) { + const keys = ids?.map(this.toKey, this); + const data = await api.get(keys); + return transform || this.prefix + ? data::mapEntry(transform, this.toId, this) + : data; + } + + /** + * @param {string|number|Array} id + * @return {Promise} + */ + async remove(id) { + const keys = ensureArray(id).filter(Boolean).map(this.toKey, this); + if (keys.length) await api.remove(keys); + } + + async setOne(id, value) { + if (id) return this.set({ [id]: value }); + } + + /** + * @param {Object} data + * @return {Promise} same object + */ + async set(data) { + if (process.env.DEV && !isObject(data)) { + throw 'VMStorageArea.set: data is not an object'; + } + await api.set(this.prefix + ? data::mapEntry(null, this.toKey, this) + : data); + return data; + } +} + +// TODO: add Firefox version to the comment when https://bugzil.la/1910669 is fixed +/** @type {() => Promise} Chromium 130+ */ +export const getStorageKeys = api.getKeys; +export const S_CACHE = 'cache'; +export const S_CACHE_PRE = 'cac:'; +export const S_CODE = 'code'; +export const S_CODE_PRE = 'code:'; +export const S_MOD = 'mod'; +export const S_MOD_PRE = 'mod:'; +export const S_REQUIRE = 'require'; +export const S_REQUIRE_PRE = 'req:'; +export const S_SCRIPT = 'script'; +export const S_SCRIPT_PRE = 'scr:'; +export const S_VALUE = 'value'; +export const S_VALUE_PRE = 'val:'; +/** @type {{ [prefix: string]: VMStorageArea }} */ +export const storageByPrefix = {}; +/** + * @prop {VMStorageArea} cache + * @prop {VMStorageArea} code + * @prop {VMStorageArea} mod + * @prop {VMStorageArea} require + * @prop {VMStorageArea} script + * @prop {VMStorageArea} value + */ +const storage = { + get api() { return api; }, + set api(val) { api = val; }, + /** @return {?VMStorageArea} */// eslint-disable-next-line no-use-before-define + forKey: key => storageByPrefix[/^\w+:|$/.exec(key)[0]], + base: new VMStorageArea('base', ''), + [S_CACHE]: new VMStorageArea(S_CACHE, S_CACHE_PRE), + [S_CODE]: new VMStorageArea(S_CODE, S_CODE_PRE), + /** last-modified HTTP header value per URL */ + [S_MOD]: new VMStorageArea(S_MOD, S_MOD_PRE), + [S_REQUIRE]: new VMStorageArea(S_REQUIRE, S_REQUIRE_PRE), + [S_SCRIPT]: new VMStorageArea(S_SCRIPT, S_SCRIPT_PRE), + [S_VALUE]: new VMStorageArea(S_VALUE, S_VALUE_PRE), +}; +export default storage; + +addOwnCommands({ + Storage([area, method, ...args]) { + return storage[area][method](...args); + }, +}); diff --git a/src/background/utils/tab-redirector.js b/src/background/utils/tab-redirector.js new file mode 100644 index 0000000000..904690e655 --- /dev/null +++ b/src/background/utils/tab-redirector.js @@ -0,0 +1,161 @@ +import { browserWindows, request, noop, i18n, getUniqId } from '@/common'; +import { FILE_GLOB_ALL } from '@/common/consts'; +import cache from './cache'; +import { addPublicCommands, commands } from './init'; +import { getOption } from './options'; +import { parseMeta, matchUserScript } from './script'; +import { fileSchemeRequestable, getTabUrl, NEWTAB_URL_RE, tabsOnUpdated } from './tabs'; +import { FIREFOX } from './ua'; + +addPublicCommands({ + async CheckInstallerTab(tabId, src) { + const tab = IS_FIREFOX && (src.url || '').startsWith('file:') + && await browser.tabs.get(tabId).catch(noop); + return tab && getTabUrl(tab).startsWith(CONFIRM_URL_BASE); + }, + ConfirmInstall: confirmInstall, +}); + +async function confirmInstall({ code, from, url, fs, parsed }, { tab = {} }) { + if (!fs) { + code ??= parsed + ? request(url).then(r => r.data) // cache the Promise and start fetching now + : (await request(url)).data; + // TODO: display the error in UI + if (!parsed && !matchUserScript(code)) { + throw `${i18n('msgInvalidScript')}\n\n${ + code.trim().split(/[\r\n]+\s*/, 9/*max lines*/).join('\n') + .slice(0, 500/*max overall length*/) + }...`; + } + cache.put(url, code, 3000); + } + const confirmKey = getUniqId(); + const { active, id: tabId, incognito } = tab; + // Not testing tab.pendingUrl because it will be always equal to `url` + const canReplaceCurTab = (!incognito || IS_FIREFOX) && ( + url === from + || cache.has(`autoclose:${tabId}`) + || NEWTAB_URL_RE.test(from)); + /** @namespace VM.ConfirmCache */ + cache.put(`confirm-${confirmKey}`, { incognito, url, from, tabId, fs, ff: FIREFOX }); + const confirmUrl = CONFIRM_URL_BASE + confirmKey; + const { [kWindowId]: windowId } = canReplaceCurTab + // The tab may have been closed already, in which case we'll open a new tab + && await browser.tabs.update(tabId, { url: confirmUrl }).catch(noop) + || await commands.TabOpen({ url: confirmUrl, active: !!active }, { tab }); + if (active && windowId !== tab[kWindowId]) { + await browserWindows?.update(windowId, { focused: true }); + } +} + +const CONFIRM_URL_BASE = `${extensionRoot}confirm/index.html#`; +const whitelistRe = re`/^https:\/\/( + (greas|sleaz)yfork\.(org|cc)\/scripts\/[^/]*\/code| + update\.(greas|sleaz)yfork\.(org|cc)\/scripts| + openuserjs\.org\/install\/[^/]*| + github\.com\/[^/]*\/[^/]*\/( + raw\/[^/]*| + releases\/( + download\/[^/]* | + latest\/download + ) + )| + raw\.githubusercontent\.com(\/[^/]*){3}| + gist\.github\.com\/.*? +)\/[^/]*?\.user\.js ([?#]|$) /ix`; +const blacklistRe = re`/^https?:\/\/( + (gist\.)?github\.com| + ((greas|sleaz)yfork|openuserjs)\.(org|cc) +)\//ix`; +const resolveVirtualUrl = url => ( + `${extensionOptionsPage}${ROUTE_SCRIPTS}/${+url.split('#')[1]}` +); +// FF can't intercept virtual .user.js URL via webRequest, so we redirect it explicitly +const virtualUrlRe = IS_FIREFOX && new RegExp(( + `^(view-source:)?(${extensionRoot.replace('://', '$&)?')}[^/]*\\.user\\.js#\\d+` +)); +const maybeRedirectVirtualUrlFF = virtualUrlRe && ((tabId, src) => { + if (virtualUrlRe.test(src)) { + browser.tabs.update(tabId, { url: resolveVirtualUrl(src) }); + } +}); + +async function maybeInstallUserJs(tabId, url, isWhitelisted) { + // Getting the tab now before it navigated + const tab = tabId >= 0 && await browser.tabs.get(tabId) || {}; + const { data: code } = !isWhitelisted && await request(url).catch(noop) || {}; + if (isWhitelisted || code && parseMeta(code).name) { + confirmInstall({ code, url, from: tab.url, parsed: true }, { tab }); + } else { + cache.put(`bypass:${url}`, true, 10e3); + const error = `${VIOLENTMONKEY} installer skipped ${url}. +Either not a userscript or the metablock comment is malformed: +${code?.length > 1e6 ? code.slice(0, 1e6) + '...' : code}`; + if (tabId < 0) { + console.warn(error); + } else { + browser.tabs.executeScript(tabId, { + code: `console.warn(${JSON.stringify(error)})`, + }); + browser.tabs.update(tabId, { url }); + } + } +} + +if (virtualUrlRe) { + tabsOnUpdated.addListener( + (tabId, { url }) => url && maybeRedirectVirtualUrlFF(tabId, url), + FIREFOX && { properties: [FIREFOX >= 88 ? 'url' : 'status'] } + ); +} + +browser.tabs.onCreated.addListener((tab) => { + const { id, title } = tab; + const url = getTabUrl(tab); + const isFile = url.startsWith('file:'); + const isUserJS = /\.user\.js([?#]|$)/.test(url); + /* Determining if this tab can be auto-closed (replaced, actually). + FF>=68 allows reading file: URL only in the tab's content script so the tab must stay open. */ + if (isUserJS && (!isFile || FIREFOX < 68)) { + cache.put(`autoclose:${id}`, true, 10e3); + } + if (virtualUrlRe && url === 'about:blank') { + maybeRedirectVirtualUrlFF(id, title); + } + if (isUserJS && isFile && !fileSchemeRequestable && !IS_FIREFOX + && getOption('helpForLocalFile')) { + confirmInstall({ url, fs: true }, { tab }); + } +}); + +browser.webRequest.onBeforeRequest.addListener((req) => { + const { method, tabId, url } = req; + if (method !== 'GET') { + return; + } + // open a real URL for simplified userscript URL listed in devtools of the web page + if (url.startsWith(extensionRoot)) { + return { redirectUrl: resolveVirtualUrl(url) }; + } + let isWhitelisted; + if (!cache.has(`bypass:${url}`) + && ((isWhitelisted = whitelistRe.test(url)) || !blacklistRe.test(url))) { + maybeInstallUserJs(tabId, url, isWhitelisted); + return IS_FIREFOX + ? { cancel: true } // for sites with strict CSP in FF + : { redirectUrl: 'javascript:void 0' }; // eslint-disable-line no-script-url + } +}, { + urls: [ + // 1. *:// comprises only http/https + // 2. the API ignores #hash part + // 3. Firefox: onBeforeRequest does not work with file:// or moz-extension:// + '*://*/*.user.js', + '*://*/*.user.js?*', + `${FILE_GLOB_ALL}.user.js`, + `${FILE_GLOB_ALL}.user.js?*`, + `${extensionRoot}*.user.js`, + ], + types: ['main_frame'], +}, ['blocking']); diff --git a/src/background/utils/tabs.js b/src/background/utils/tabs.js index 02bf12c930..3bc87c0311 100644 --- a/src/background/utils/tabs.js +++ b/src/background/utils/tabs.js @@ -1,91 +1,177 @@ -import { getActiveTab, noop, sendTabCmd, getFullUrl } from '#/common'; -import ua from '#/common/ua'; -import { extensionRoot } from './init'; -import { commands } from './message'; +import { browserWindows, getActiveTab, makePause, noop, sendTabCmd } from '@/common'; +import { getDomain } from '@/common/tld'; +import { addOwnCommands, addPublicCommands, commands } from './init'; import { getOption } from './options'; +import { testScript } from './tester'; +import { CHROME, FIREFOX } from './ua'; +import { vetUrl } from './url'; const openers = {}; +const openerTabIdSupported = !IS_FIREFOX // supported in Chrome + || !!(window.AbortSignal && browserWindows); // and FF57+ except mobile +const EDITOR_ROUTE = extensionOptionsPage + ROUTE_SCRIPTS + '/'; // followed by id +export const NEWTAB_URL_RE = re`/ +^( + about:(home|newtab) # Firefox + | (chrome|edge):\/\/( + newtab\/ # Chrome, Edge + | startpageshared\/ # Opera + | vivaldi-webui\/startpage # Vivaldi + ) +)$ +/x`; +/** @returns {string|number} documentId for a pre-rendered top page, frameId otherwise */ +export const getFrameDocId = (isTop, docId, frameId) => ( + isTop === 2 && docId || frameId +); +/** @param {VMMessageSender} src */ +export const getFrameDocIdFromSrc = src => ( + src[kTop] === 2 && src[kDocumentId] || src[kFrameId] +); +export const getFrameDocIdAsObj = id => +id >= 0 + ? { [kFrameId]: +id } + : { [kDocumentId]: id }; +/** + * @param {chrome.tabs.Tab} tab + * @returns {string} + */ +export const getTabUrl = tab => ( + tab.pendingUrl || tab.url || '' +); +export const tabsOnUpdated = browser.tabs.onUpdated; +export const tabsOnRemoved = browser.tabs.onRemoved; +export let injectableRe = /^(https?|file|ftps?):/; +export let fileSchemeRequestable; +let cookieStorePrefix; -Object.assign(commands, { +try { + // onUpdated is filterable only in desktop FF 61+ + // but we use a try-catch anyway to detect this feature in nonstandard browsers + tabsOnUpdated.addListener(noop, { properties: ['status'] }); + tabsOnUpdated.removeListener(noop); +} catch (e) { + tabsOnUpdated.addListener = new Proxy(tabsOnUpdated.addListener, { + apply: (fn, thisArg, args) => thisArg::fn(args[0]), + }); +} + +addOwnCommands({ + GetTabDomain(url) { + const host = url && new URL(url).hostname; + return { + host, + domain: host && getDomain(host) || host, + }; + }, /** - * @param {string} [pathId] - path or id to add to #scripts route in dashboard, - if absent a new script will be created for active tab's URL - * @returns {Promise<{id: number}>} + * @param {string} [pathId] - path or id: added to #scripts/ route in dashboard, + * falsy: creates a new script for active tab's URL + * @param {VMMessageSender} [src] */ - async OpenEditor(pathId) { - if (!pathId) { - const { tab, domain } = await commands.GetTabDomain(); - const id = domain && commands.CacheNewScript({ - url: (tab.pendingUrl || tab.url).split(/[#?]/)[0], - name: `- ${domain}`, - }); - pathId = `_new${id ? `/${id}` : ''}`; - } - return commands.TabOpen({ - url: `/options/index.html#scripts/${pathId}`, - maybeInWindow: true, - }); + async OpenEditor(pathId, src) { + return openDashboard(`${SCRIPTS}/${ + pathId || `_new/${src?.tab?.id || (await getActiveTab()).id}` + }`, src); }, - /** @return {Promise<{ id: number }>} */ + OpenDashboard: openDashboard, +}); + +addPublicCommands({ + /** @return {Promise<{ id: number } | chrome.tabs.Tab>} new tab is returned for internal calls */ async TabOpen({ url, active = true, container, insert = true, - maybeInWindow = false, pinned, }, src = {}) { + const isRemoved = src._removed; // src.tab may be absent when invoked from popup (e.g. edit/create buttons) - const srcTab = src.tab || await getActiveTab() || {}; + const srcTab = !isRemoved && src.tab + || await getActiveTab(isRemoved && src.tab[kWindowId]) + || {}; // src.url may be absent when invoked directly as commands.TabOpen const srcUrl = src.url; const isInternal = !srcUrl || srcUrl.startsWith(extensionRoot); // only incognito storeId may be specified when opening in an incognito window - const { incognito, windowId } = srcTab; + const { incognito } = srcTab; + const canOpenIncognito = !incognito || IS_FIREFOX || !/^(chrome[-\w]*):/.test(url); + const tabOpts = { + // normalizing as boolean because the API requires strict types + active: !!active, + pinned: !!pinned, + }; + let windowId = srcTab[kWindowId]; + let newTab; // Chrome can't open chrome-xxx: URLs in incognito windows + // TODO: for src._removed maybe create a new window if cookieStoreId of active tab is different let storeId = srcTab.cookieStoreId; if (storeId && !incognito) { - storeId = getContainerId(isInternal ? 0 : container) || storeId; + if (!cookieStorePrefix) { + cookieStorePrefix = (await browser.cookies.getAllCookieStores())[0].id.split('-')[0]; + } + if (isInternal || container === 0) { + storeId = cookieStorePrefix + '-default'; + } else if (container > 0) { + storeId = `${cookieStorePrefix}-container-${container}`; + } } if (storeId) storeId = { cookieStoreId: storeId }; - if (!url.startsWith('blob:')) { - // URL needs to be expanded for `canOpenIncognito` below - if (!isInternal) url = getFullUrl(url, srcUrl); - else if (!/^\w+:/.test(url)) url = browser.runtime.getURL(url); + // URL needs to be expanded for `canOpenIncognito` below + if (!/^[-\w]+:/.test(url)) { + url = isInternal + ? browser.runtime.getURL(url) + : vetUrl(url, srcUrl); } - const canOpenIncognito = !incognito || ua.isFirefox || !/^(chrome[-\w]*):/.test(url); - let newTab; - if (maybeInWindow && browser.windows && getOption('editorWindow')) { + if (isInternal + && url.startsWith(EDITOR_ROUTE) + && browserWindows + && getOption('editorWindow') + /* cookieStoreId in windows.create() is supported since FF64 https://bugzil.la/1393570 + * and a workaround is too convoluted to add it for such an ancient version */ + && (!storeId || FIREFOX >= 64)) { const wndOpts = { url, incognito: canOpenIncognito && incognito, ...getOption('editorWindowSimple') && { type: 'popup' }, - ...ua.isChrome && { focused: !!active }, // FF doesn't support this + ...!IS_FIREFOX && { focused: !!active }, // FF doesn't support this ...storeId, }; const pos = getOption('editorWindowPos'); const hasPos = pos && 'top' in pos; - const wnd = await browser.windows.create({ ...wndOpts, ...pos }).catch(hasPos && noop) - || hasPos && await browser.windows.create(wndOpts); + const wnd = await browserWindows.create({ ...wndOpts, ...pos }).catch(hasPos && noop) + || hasPos && await browserWindows.create(wndOpts); newTab = wnd.tabs[0]; + } else if (isInternal && canOpenIncognito && NEWTAB_URL_RE.test(getTabUrl(srcTab))) { + // Replacing the currently focused start tab page for internal commands + newTab = await browser.tabs.update(srcTab.id, { url, ...tabOpts }).catch(noop); } - const { id, windowId: newWindowId } = newTab || await browser.tabs.create({ - url, - // normalizing as boolean because the API requires strict types - active: !!active, - pinned: !!pinned, - ...storeId, - ...canOpenIncognito && { - windowId, - ...insert && { index: srcTab.index + 1 }, - ...ua.openerTabIdSupported && { openerTabId: srcTab.id }, - }, - }); - if (active && newWindowId !== windowId) { - await browser.windows.update(newWindowId, { focused: true }); + for (let retry = 0; !newTab && retry < 2; retry++) try { + newTab = await browser.tabs.create({ + url, + ...tabOpts, + ...storeId, + ...canOpenIncognito && { + [kWindowId]: windowId, + ...insert && srcTab.index != null && { index: srcTab.index + 1 }, + ...openerTabIdSupported && { openerTabId: srcTab.id }, + }, + }); + } catch (err) { + const m = err.message; + if (m.startsWith('Illegal to set private')) storeId = null; + else if (m.startsWith('No tab')) srcTab.id = null; + else if (m.startsWith('No window')) windowId = null; + else if (m.startsWith('Tabs cannot be edited')) await makePause(100); + else throw err; // TODO: put in storage and show in UI + } + if (active && newTab[kWindowId] !== windowId) { + await browserWindows?.update(newTab[kWindowId], { focused: true }); } - openers[id] = srcTab.id; - return { id }; + if (!isInternal && srcTab.id != null) { + openers[newTab.id] = srcTab.id; + } + return isInternal ? newTab : { id: newTab.id }; }, /** @return {void} */ TabClose({ id } = {}, src) { @@ -94,21 +180,11 @@ Object.assign(commands, { }, TabFocus(_, src) { browser.tabs.update(src.tab.id, { active: true }).catch(noop); + browserWindows?.update(src.tab[kWindowId], { focused: true }).catch(noop); }, }); -// Firefox Android does not support `openerTabId` field, it fails if this field is passed -// XXX openerTabId seems buggy on Chrome, https://crbug.com/967150 -// It seems to do nothing even set successfully with `browser.tabs.update`. -ua.ready.then(() => { - Object.defineProperties(ua, { - openerTabIdSupported: { - value: ua.isChrome || ua.isFirefox >= 57 && ua.os !== 'android', - }, - }); -}); - -browser.tabs.onRemoved.addListener((id) => { +tabsOnRemoved.addListener((id) => { const openerId = openers[id]; if (openerId >= 0) { sendTabCmd(openerId, 'TabClosed', id); @@ -116,7 +192,53 @@ browser.tabs.onRemoved.addListener((id) => { } }); -function getContainerId(index) { - return index === 0 && 'firefox-default' - || index > 0 && `firefox-container-${index}`; +(async () => { + // FF68+ can't fetch file:// from extension context but it runs content scripts in file:// tabs + const fileScheme = IS_FIREFOX + || await new Promise(r => chrome.extension.isAllowedFileSchemeAccess(r)); + fileSchemeRequestable = FIREFOX < 68 || !IS_FIREFOX && fileScheme; + // Since users in FF can override UA we detect FF 90 via feature + if (IS_FIREFOX && [].at || CHROME >= 88) { + injectableRe = fileScheme ? /^(https?|file):/ : /^https?:/; + } else if (!fileScheme) { + injectableRe = /^(ht|f)tps?:/; + } +})(); + +export async function forEachTab(callback) { + const tabs = await browser.tabs.query({}); + let i = 0; + for (const tab of tabs) { + callback(tab); + i += 1; + // we'll run at most this many tabs in one event loop cycle + // because hundreds of tabs would make our extension process unresponsive, + // the same process used by our own pages like the background page, dashboard, or popups + if (i % 20 === 0) await new Promise(setTimeout); + } +} + +/** + * @param {string} [route] without # + * @param {VMMessageSender} [src] + */ +export async function openDashboard(route, src) { + const url = extensionOptionsPage + (route ? '#' + route : ''); + for (const tab of await browser.tabs.query({ url: extensionOptionsPage })) { + const tabUrl = tab.url; + // query() can't handle #hash so it returns tabs both with #hash and without it + if (tabUrl === url || !route && tabUrl === url + ROUTE_SCRIPTS) { + browserWindows?.update(tab[kWindowId], { focused: true }); + return browser.tabs.update(tab.id, { active: true }); + } + } + return commands.TabOpen({ url }, src); +} + +/** Reloads the active tab if script matches the URL */ +export async function reloadTabForScript(script) { + const { url, id } = await getActiveTab(); + if (injectableRe.test(url) && testScript(url, script)) { + return browser.tabs.reload(id); + } } diff --git a/src/background/utils/template-hook.js b/src/background/utils/template-hook.js deleted file mode 100644 index 1ed5ca8823..0000000000 --- a/src/background/utils/template-hook.js +++ /dev/null @@ -1,44 +0,0 @@ -import { postInitialize } from './init'; -import { getDefaultOption, getOption, setOption } from './options'; - -export const SCRIPT_TEMPLATE = 'scriptTemplate'; -const SCRIPT_TEMPLATE_EDITED = `${SCRIPT_TEMPLATE}Edited`; -const INITIAL_TEMPLATE = `\ -// ==UserScript== -// @name New Script -// @namespace Violentmonkey Scripts -// @match {{url}} -// @grant none -// ==/UserScript== -`; - -postInitialize.push(() => { - let edited = getOption(SCRIPT_TEMPLATE_EDITED); - // Preserve an edited template - if (edited) return; - const template = getOption(SCRIPT_TEMPLATE); - // When updating from an old version, set the edited flag retroactively - if (edited == null) { - edited = template !== INITIAL_TEMPLATE; - if (edited) setOption(SCRIPT_TEMPLATE_EDITED, true); - else resetScriptTemplate(); - // When updating VM, update to the new default template - } else if (template !== getDefaultOption(SCRIPT_TEMPLATE)) { - resetScriptTemplate(); - } -}); - -export function resetScriptTemplate(changes = {}) { - const defaultTemplate = getDefaultOption(SCRIPT_TEMPLATE); - let template = changes[SCRIPT_TEMPLATE]; - if (!template) { - template = defaultTemplate; - changes[SCRIPT_TEMPLATE] = template; - setOption(SCRIPT_TEMPLATE, template); - } - const edited = template !== defaultTemplate; - if (edited !== changes[SCRIPT_TEMPLATE_EDITED]) { - changes[SCRIPT_TEMPLATE_EDITED] = edited; - setOption(SCRIPT_TEMPLATE_EDITED, edited); - } -} diff --git a/src/background/utils/tester.js b/src/background/utils/tester.js index 9f18cb8752..c5d53cca4f 100644 --- a/src/background/utils/tester.js +++ b/src/background/utils/tester.js @@ -1,255 +1,373 @@ -import * as tld from '#/common/tld'; -import cache from './cache'; -import { postInitialize } from './init'; -import { commands } from './message'; -import { getOption, hookOptions } from './options'; - -Object.assign(commands, { - TestBlacklist: testBlacklist, +/* eslint-disable max-classes-per-file */ +import { escapeStringForRegExp, getScriptPrettyUrl } from '@/common'; +import { ERR_BAD_PATTERN, BLACKLIST, BLACKLIST_NET, ERRORS } from '@/common/consts'; +import initCache from '@/common/cache'; +import { getPublicSuffix } from '@/common/tld'; +import { hookOptionsInit } from './options'; +import storage from './storage'; + +const matchAlways = { test: () => 1 }; +/** + * Using separate caches to avoid memory consumption for thousands of prefixed long urls + * TODO: switch `cache` to hubs internally and add a prefix parameter or accept an Array for key + */ +const cacheMat = initCache({ lifetime: 60 * 60e3 }); +const cacheInc = initCache({ lifetime: 60 * 60e3 }); +const cacheResultMat = initCache({ lifetime: 60e3 }); +const cacheResultInc = initCache({ lifetime: 60e3 }); +/** Simple matching for valid patterns */ +const RE_MATCH_PARTS = re`/^ + (\*|http([s*])?|file|ftp|urn):\/\/ + ([^/]*)\/ + (.*) +/x`; +/** Resilient matching for broken patterns allows reporting errors with a helpful message */ +const RE_MATCH_BAD = re`/^ + ( + \*| + # allowing the incorrect http* scheme which is the same as * + http([s*])?| + file| + ftp| + urn| + # detecting an unknown scheme + ([^:]*?)(?=:) + ) + # detecting a partially missing :// + (:(?:\/(?:\/)?)?)? + ([^/]*) + # detecting a missing / for path + (?:\/(.*))? +/x`; +/** Simpler matching for a valid URL */ +const RE_URL_PARTS = /^([^:]*):\/\/([^/]*)\/(.*)/; +const RE_STR_ANY = '(?:|[^:/]*?\\.)'; +const RE_STR_TLD = '(|(?:\\.[-\\w]+)+)'; +const MAX_BL_CACHE_LENGTH = 100e3; +const blacklist = { + [BLACKLIST]: CreateBlacklist(), + [BLACKLIST_NET]: CreateBlacklist(), +}; +export const { + reset: resetBlacklist, + test: testBlacklist, +} = blacklist[BLACKLIST]; +export const testBlacklistNet = blacklist[BLACKLIST_NET].test; +// Context start +let batchErrors; +let curUrl; +let curScheme; +let curHost; +let curTail; +let urlResultsMat; +let urlResultsInc; +// Context end + +hookOptionsInit((changes) => { + for (const key in blacklist) { + if (key in changes) { + const errors = blacklist[key].reset(changes[key] || []); + const res = errors.length ? errors : null; + storage.base.setOne(key + ERRORS, res); + if (res) throw res; // will be passed to the UI + } + } }); -postInitialize.push(resetBlacklist); +export class MatchTest { + constructor(rule, scheme, httpMod, host, path) { + const isWild = scheme === '*' || httpMod === '*'; + this.scheme = isWild ? 'http' : scheme; + this.scheme2 = isWild ? 'https' : null; + this.host = host === '*' ? null : hostMatcher(host); + this.path = path === '*' ? null : pathMatcher(path); + } -tld.initTLD(true); + test() { + return (this.scheme === curScheme || this.scheme2 === curScheme) + && this.host?.test(curHost) !== false + && this.path?.test(curTail) !== false; + } -const RE_MATCH_PARTS = /(.*?):\/\/([^/]*)\/(.*)/; -let blacklistRules = []; -hookOptions((changes) => { - if ('blacklist' in changes) resetBlacklist(changes.blacklist || ''); -}); -const RE_HTTP_OR_HTTPS = /^https?$/i; - -/* - Simple FIFO queue for the results of testBlacklist, cached separately from the main |cache| - because the blacklist is updated only once in a while so its entries would be crowding - the main cache and reducing its performance (objects with lots of keys are slow to access). - - We also don't need to auto-expire the entries after a timeout. - The only limit we're concerned with is the overall memory used. - The limit is specified in the amount of unicode characters (string length) for simplicity. - Disregarding deduplication due to interning, the actual memory used is approximately twice as big: - 2 * keyLength + objectStructureOverhead * objectCount -*/ -const MAX_BL_CACHE_LENGTH = 100e3; -let blCache = {}; -let blCacheSize = 0; - -function testRules(url, rules, prefix, ruleBuilder) { - return rules.some(rule => { - const key = `${prefix}:${rule}`; - let matcher = cache.get(key); - if (matcher) cache.hit(key); - else cache.put(key, (matcher = ruleBuilder(rule))); - return matcher.test(url); - }); + /** + * @returns {MatchTest|matchAlways} + * @throws {string} + */ + static try(rule) { + let parts = rule.match(RE_MATCH_PARTS); + if (parts) return new MatchTest(...parts); + if (rule === '') return matchAlways; // checking it second as it's super rare + // Report failed parts in detail + parts = rule.match(RE_MATCH_BAD); + parts = !parts ? '' : ( + (parts[3] != null ? `${parts[3] ? 'unknown' : 'missing'} scheme, ` : '') + + (parts[4] !== '://' ? 'missing "://", ' : '') + || (parts[6] == null ? 'missing "/" for path, ' : '') + ).slice(0, -2) + ' in '; + throw `${ERR_BAD_PATTERN} ${parts}${rule}`; + } } -/** - * Test glob rules like `@include` and `@exclude`. - */ -export function testGlob(url, rules) { - return testRules(url, rules, 're', autoReg); +/** For strings without wildcards/tld it's 1.5x faster and much more memory-efficient than RegExp */ +class StringTest { + constructor(str, i, ignoreCase) { + this.s = ignoreCase ? str.toLowerCase() : str; + this.i = !!ignoreCase; // must be boolean to ensure test() returns boolean + this.cmp = i < 0 ? '' : (i && 'startsWith' || 'endsWith'); + } + + test(str) { + const { s, cmp } = this; + const delta = str.length - s.length; + const res = delta >= 0 && ( + cmp && delta + ? str[cmp](s) || this.i && str.toLowerCase()[cmp](s) + : str === s || !delta && this.i && str.toLowerCase() === s + ); + return res; + } + + /** @returns {?StringTest} */ + static try(rule, ignoreCase) { + // TODO: support *. for domain if it's faster than regex + const i = rule.indexOf('*'); + if (i === rule.length - 1) { + rule = rule.slice(0, -1); // prefix* + } else if (i === 0 && rule.indexOf('*', 1) < 0) { + rule = rule.slice(1); // *suffix + } else if (i >= 0) { + return; // *wildcards*anywhere* + } + return new StringTest(rule, i, ignoreCase); + } } -/** - * Test match rules like `@match` and `@exclude_match`. - */ -export function testMatch(url, rules) { - return testRules(url, rules, 'match', matchTester); + +export function testerBatch(arr) { + cacheMat.batch(arr); + cacheInc.batch(arr); + cacheResultMat.batch(arr); + cacheResultInc.batch(arr); + batchErrors = Array.isArray(arr) && arr; +} + +function setContext(url) { + curUrl = url; + [, curScheme, curHost, curTail] = url + ? url.match(RE_URL_PARTS) + : ['', '', '', '']; // parseMetaWithErrors uses an empty url for tests + urlResultsMat = url ? (cacheResultMat.get(url) || cacheResultMat.put(url, {})) : null; + urlResultsInc = url ? (cacheResultInc.get(url) || cacheResultInc.put(url, {})) : null; } +/** + * As this code is *very* hot, we avoid calling functions or creating possibly big arrays + * or creating copies of thousands of keys by prefixing them in `cache`, thus we avoid pauses + * due to major GC. The speedup is ~3x (from ~40ms to ~14ms) on a 4GHz CPU + * with popular scripts that have lots of @match e.g. Handy Image. + */ export function testScript(url, script) { - cache.batch(true); + let matex1; // main @match / @exclude-match + let matex2; // custom @match / @exclude-match + let inex1; // main @include / @exclude + let inex2; // custom @include / @exclude const { custom, meta } = script; - const mat = mergeLists(custom.origMatch && meta.match, custom.match); - const inc = mergeLists(custom.origInclude && meta.include, custom.include); - const exc = mergeLists(custom.origExclude && meta.exclude, custom.exclude); - const excMat = mergeLists(custom.origExcludeMatch && meta.excludeMatch, custom.excludeMatch); - // match all if no @match or @include rule - let ok = !mat.length && !inc.length; - // @match - ok = ok || testMatch(url, mat); - // @include - ok = ok || testGlob(url, inc); - // @exclude-match - ok = ok && !testMatch(url, excMat); - // @exclude - ok = ok && !testGlob(url, exc); - cache.batch(false); + const len = (matex1 = custom.origMatch && meta.match || '').length + + (matex2 = custom.match || '').length + + (inex1 = custom.origInclude && meta.include || '').length + + (inex2 = custom.include || '').length; + const ok = ( + // Ok if lists are empty or @match + @include apply + !len || testRules(url, script, matex1, matex2, inex1, inex2) + ) && !( + // and no excludes apply + ((matex1 = custom.origExcludeMatch && meta.excludeMatch || '').length + + (matex2 = custom.excludeMatch || '').length + + (inex1 = custom.origExclude && meta.exclude || '').length + + (inex2 = custom.exclude || '').length + ) && testRules(url, script, matex1, matex2, inex1, inex2) + ); return ok; } -function mergeLists(...args) { - return args.reduce((res, item) => (item ? res.concat(item) : res), []); +function testRules(url, script, ...list) { + if (curUrl !== url) setContext(url); + // TODO: combine all non-regex rules in one big smart regexp + // e.g. lots of `*://foo/*` can be combined into `^https?://(foo|bar|baz)/` + for (let i = 0, m, rules, builder, cache, urlResults, res, err, scriptUrl; i < 4; i += 1) { + // [matches, matches, includes, includes], some items may be empty + if ((rules = list[i]).length) { + if (!cache) { // happens one time for 0 or 1 and another time for 2 or 3 + if (i < 2) { // matches1, matches2 + builder = MatchTest.try; + cache = cacheMat; + urlResults = urlResultsMat; + } else { // includes1, includes2 + builder = autoReg; + cache = cacheInc; + urlResults = urlResultsInc; + } + } + for (const rule of rules) { + if (url && (res = urlResults[rule])) { + return res; + } + if (res == null) { + if (!(m = cache.get(rule))) { + try { + m = builder(rule); + } catch (e) { + m = { err: e }; + } + cache.put(rule, m); + } + if ((err = m.err)) { + if (batchErrors) { + err = err.message || err; + err = url + ? `${err} - ${scriptUrl || (scriptUrl = getScriptPrettyUrl(script))}` + : err; + batchErrors.push(err); + } + } else if (url && (urlResults[rule] = +!!m.test(url))) { + return true; + } + } + } + } + if (i === 1) cache = false; // this will switch cache+builder for includes if they're non-empty + } } function str2RE(str) { - const re = str.replace(/([.?+[\]{}()|^$])/g, '\\$1').replace(/\*/g, '.*?'); - return re; -} - -function bindRE(re) { - return re.test.bind(re); + return escapeStringForRegExp(str).replace(/\*/g, '.*?'); } function autoReg(str) { // regexp mode: case-insensitive per GM documentation if (str.length > 1 && str[0] === '/' && str[str.length - 1] === '/') { - let re; - try { re = new RegExp(str.slice(1, -1), 'i'); } catch (e) { /* ignore */ } - return { test: re ? bindRE(re) : () => false }; + return new RegExp(str.slice(1, -1), 'i'); } - // glob mode: case-insensitive to match GM4 & Tampermonkey bugged behavior - const reStr = str2RE(str.toLowerCase()); - if (tld.isReady() && str.includes('.tld/')) { - const reTldStr = reStr.replace('\\.tld/', '((?:\\.[-\\w]+)+)/'); - return { - test: (tstr) => { - const matches = tstr.toLowerCase().match(reTldStr); - if (matches) { - const suffix = matches[1].slice(1); - if (tld.getPublicSuffix(suffix) === suffix) return true; - } - return false; - }, - }; + const isTld = str.includes('.tld/'); + const strTester = !isTld && StringTest.try(str, true); + if (strTester) { + return strTester; } - const re = new RegExp(`^${reStr}$`, 'i'); // String with wildcards - return { test: bindRE(re) }; + // glob mode: case-insensitive to match GM4 & Tampermonkey bugged behavior + const reStr = `^${str2RE(str)}$`; + const reTldStr = isTld ? reStr.replace('\\.tld/', '((?:\\.[-\\w]+)+)/') : reStr; + const re = RegExp(reTldStr, 'i'); + if (reStr !== reTldStr) re.test = matchTld; + return re; } -function matchScheme(rule, data) { - // exact match - if (rule === data) return 1; - // * = http | https - // support http* - if ([ - '*', - 'http*', - ].includes(rule) && RE_HTTP_OR_HTTPS.test(data)) return 1; - return 0; +function matchTld(tstr) { + const matches = tstr.match(this); + const suffix = matches?.[1]?.slice(1).toLowerCase(); + // Must return a proper boolean + return !!suffix && getPublicSuffix(suffix) === suffix; } -const RE_STR_ANY = '(?:|.*?\\.)'; -const RE_STR_TLD = '((?:\\.[-\\w]+)+)'; function hostMatcher(rule) { - // * matches all - if (rule === '*') { - return () => 1; - } + // host matching is case-insensitive // *.example.com // www.google.* // www.google.tld - const ruleLC = rule.toLowerCase(); // host matching is case-insensitive + const isTld = rule.endsWith('.tld'); let prefix = ''; - let base = ruleLC; + let base = rule; let suffix = ''; + let strTester; if (rule.startsWith('*.')) { base = base.slice(2); prefix = RE_STR_ANY; + } else if (!isTld && (strTester = StringTest.try(rule, true))) { + return strTester; } - if (tld.isReady() && rule.endsWith('.tld')) { + if (isTld) { base = base.slice(0, -4); suffix = RE_STR_TLD; } - const re = new RegExp(`^${prefix}${str2RE(base)}${suffix}$`); - return (data) => { - // exact match, case-insensitive - data = data.toLowerCase(); - if (ruleLC === data) return 1; - // full check - const matches = data.match(re); - if (matches) { - const [, tldStr] = matches; - if (!tldStr) return 1; - const tldSuffix = tldStr.slice(1); - return tld.getPublicSuffix(tldSuffix) === tldSuffix; - } - return 0; - }; + const re = RegExp(`^${prefix}${str2RE(base)}${suffix}$`, 'i'); + if (isTld) re.test = matchTld; + return re; } -function pathMatcher(rule) { - const iHash = rule.indexOf('#'); - let iQuery = rule.indexOf('?'); - let strRe = str2RE(rule); - if (iQuery > iHash) iQuery = -1; - if (iHash < 0) { - if (iQuery < 0) strRe = `^${strRe}(?:[?#]|$)`; - else strRe = `^${strRe}(?:#|$)`; - } - return bindRE(new RegExp(strRe)); +function pathMatcher(tail) { + const iQuery = tail.indexOf('?'); + const hasHash = tail.indexOf('#', iQuery + 1) >= 0; + return hasHash && StringTest.try(tail) + || RegExp(`^${str2RE(tail)}${hasHash ? '$' : `($|${iQuery >= 0 ? '#' : '[?#]'})`}`); } -function matchTester(rule) { - let test; - if (rule === '') { - test = () => true; - } else { - const ruleParts = rule.match(RE_MATCH_PARTS); - if (ruleParts) { - const matchHost = hostMatcher(ruleParts[2]); - const matchPath = pathMatcher(ruleParts[3]); - test = (url) => { - const parts = url.match(RE_MATCH_PARTS); - return !!ruleParts && !!parts - && matchScheme(ruleParts[1], parts[1]) - && matchHost(parts[2]) - && matchPath(parts[3]); - }; - } else { - // Ignore invalid match rules - test = () => false; +function CreateBlacklist() { + let cache = {}; + let cacheSize = 0; + let rules = []; + return { reset, test }; + function emplace(accum, rule, builder) { + return accum.get(rule) || accum.put(rule, builder(rule)); + } + function reset(value) { + const errors = []; + testerBatch(true); + if (process.env.DEBUG) { + console.info('Reset blacklist:', value); + } + // XXX compatible with {Array} list in v2.6.1- + rules = []; + for (let text of Array.isArray(value) ? value : (value || '').split('\n')) { + try { + text = text.trim(); + if (!text || text.startsWith('#')) continue; + const mode = text.startsWith('@') && text.split(/\s/, 1)[0]; + const rule = mode ? text.slice(mode.length + 1).trim() : text; + const isInc = mode === '@include'; + const m = (isInc || mode === '@exclude') && emplace(cacheInc, rule, autoReg) + || !mode && !rule.includes('/') && emplace(cacheMat, `*://${rule}/*`, MatchTest.try) // domain + || emplace(cacheMat, rule, MatchTest.try); // @match and @exclude-match + m.reject = !(mode === '@match' || isInc); // @include and @match = whitelist + m.text = text; + rules.push(m); + } catch (err) { + errors.push(err); + } } + cache = {}; + cacheSize = 0; + testerBatch(); + return errors; } - return { test }; -} - -export function testBlacklist(url) { - let res = blCache[url]; - if (res === undefined) { - const rule = blacklistRules.find(({ test }) => test(url)); - res = rule?.reject && rule.text; - updateBlacklistCache(url, res || false); + function test(url) { + let res = cache[url]; + if (res === undefined) { + if (curUrl !== url) setContext(url); + res = rules.find(m => m.test(url)); + res = res?.reject && res.text; + updateCache(url, res || false); + } + return res; } - return res; -} - -export function resetBlacklist(list) { - cache.batch(true); - const rules = list == null ? getOption('blacklist') : list; - if (process.env.DEBUG) { - console.info('Reset blacklist:', rules); - } - // XXX compatible with {Array} list in v2.6.1- - blacklistRules = (Array.isArray(rules) ? rules : (rules || '').split('\n')) - .map((text) => { - text = text.trim(); - if (!text || text.startsWith('#')) return null; - const mode = text.startsWith('@') && text.split(/\s/, 1)[0]; - const rule = mode ? text.slice(mode.length + 1).trim() : text; - const reject = mode !== '@include' && mode !== '@match'; // @include and @match = whitelist - const { test } = mode === '@include' || mode === '@exclude' && autoReg(rule) - || !mode && !rule.includes('/') && matchTester(`*://${rule}/*`) // domain - || matchTester(rule); // @match and @exclude-match - return { reject, test, text }; - }) - .filter(Boolean); - blCache = {}; - blCacheSize = 0; - cache.batch(false); -} - -function updateBlacklistCache(key, value) { - blCache[key] = value; - blCacheSize += key.length; - if (blCacheSize > MAX_BL_CACHE_LENGTH) { - Object.keys(blCache) - .some((k) => { - blCacheSize -= k.length; - delete blCache[k]; - // reduce the cache to 75% so that this function doesn't run too often. - return blCacheSize < MAX_BL_CACHE_LENGTH * 3 / 4; - }); + /** + Simple FIFO queue for the results of testBlacklist, cached separately from the main |cache| + because the blacklist is updated only once in a while so its entries would be crowding + the main cache and reducing its performance (objects with lots of keys are slow to access). + We also don't need to auto-expire the entries after a timeout. + The only limit we're concerned with is the overall memory used. + The limit is specified in the amount of unicode characters (string length) for simplicity. + Disregarding deduplication due to interning, the actual memory used is approximately twice as big: + 2 * keyLength + objectStructureOverhead * objectCount + */ + function updateCache(key, value) { + cache[key] = value; + cacheSize += key.length; + if (cacheSize > MAX_BL_CACHE_LENGTH) { + for (const k in cache) { + if (delete cache[k] && (cacheSize -= k.length) < MAX_BL_CACHE_LENGTH * 0.75) { + // Reduced the cache to 75% so that this function doesn't run too often + return; + } + } + } } } diff --git a/src/background/utils/ua.js b/src/background/utils/ua.js new file mode 100644 index 0000000000..ff144eeb24 --- /dev/null +++ b/src/background/utils/ua.js @@ -0,0 +1,67 @@ +import { browserWindows } from '@/common'; +import { listenOnce } from '@/common/browser'; +import { addOwnCommands, init } from './init'; + +export const { + userAgent: navUA, + userAgentData: navUAD, +} = navigator; +const uaVer = navUA.match(/\s(?:Chrom(?:e|ium)|Firefox)\/(\d+[.0-9]*)|$/i)[1]; +const kFullVersionList = 'fullVersionList'; + +/** @type {VMScriptGMInfoPlatform} */ +export const ua = { + mobile: navUAD ? navUAD.mobile : navUA.includes('Android'), +}; +/** @type {number|void} This value can be trusted because the only way to spoof it in Chrome/ium + * is to manually open devtools for the background page in device emulation mode. + * Using `void` for numeric comparisons like CHROME < 100 to be false in Firefox */ +export const CHROME = !IS_FIREFOX ? parseFloat(uaVer) || 1 : undefined; +/** @type {number|void} DANGER! Until init is done the only sure thing about this value + * is whether it's truthy, because UA can be overridden by about:config. + * Using `void` for numeric comparisons like FIREFOX < 100 to be false in Chrome */ +export let FIREFOX = IS_FIREFOX ? parseFloat(uaVer) || 1 : undefined; + +addOwnCommands({ + UA: () => ua, +}); + +init.deps.push( + Promise.all([ + browser.runtime.getPlatformInfo(), + browser.runtime.getBrowserInfo?.(), + navUAD?.getHighEntropyValues([kFullVersionList]), + IS_FIREFOX ? [] : browserWindows.getAll(), + ]).then(([ + { os, arch }, + { name, version } = {}, + { [kFullVersionList]: list } = {}, + [wnd], + ]) => { + if (!version && list?.[0]) { + [name, version] = list.map(({ brand, version: v }) => ( + /[^\sa-z]/i.test(brand) ? '3' : // downgrading GREASE value + brand === 'Chromium' ? '2' + brand : // known generic value + '1' + brand // preferring non-generic value + ) + '\n' + v).sort()[0].slice(1).split('\n'); + } + ua.arch = arch; + ua.os = os; + setBrowserName(name || 'chrome'); + ua.browserVersion = version || uaVer; + ua[kFullVersionList] = list; + if (FIREFOX) FIREFOX = parseFloat(version); + else if (wnd) checkVivaldi(wnd); + else browserWindows.onCreated::listenOnce(checkVivaldi); + }) +); + +function checkVivaldi(wnd) { + if (wnd.vivExtData/*new*/ || wnd.extData/*old*/) { + setBrowserName('Vivaldi'); + } +} + +function setBrowserName(name) { + ua.browserName = name; +} diff --git a/src/background/utils/update.js b/src/background/utils/update.js index a40a845d54..b7d212c26b 100644 --- a/src/background/utils/update.js +++ b/src/background/utils/update.js @@ -1,123 +1,141 @@ -import { getScriptName, i18n, request, compareVersion, sendCmd, trueJoin } from '#/common'; -import { CMD_SCRIPT_UPDATE } from '#/common/consts'; -import ua from '#/common/ua'; -import { fetchResources, getScriptById, getScripts, parseScript } from './db'; +import { + compareVersion, ensureArray, getScriptName, getScriptUpdateUrl, i18n, sendCmd, trueJoin, +} from '@/common'; +import { + __CODE, FETCH_OPTS, METABLOCK_RE, NO_CACHE, TIMEOUT_24HOURS, TIMEOUT_MAX, +} from '@/common/consts'; +import { fetchResources, getScriptById, getScripts, notifyToOpenScripts, parseScript } from './db'; +import { addOwnCommands, commands, init } from './init'; import { parseMeta } from './script'; -import { getOption, setOption } from './options'; -import { commands } from './message'; - -Object.assign(commands, { - /** @return {Promise} */ - async CheckUpdate(id) { - const script = getScriptById(id); - const results = await checkAllAndNotify([script]); - return results[0]; - }, - /** @return {Promise} */ - async CheckUpdateAll() { - setOption('lastUpdate', Date.now()); - const toUpdate = getScripts().filter(item => item.config.shouldUpdate); - const results = await checkAllAndNotify(toUpdate); - return results.includes(true); - }, -}); - -async function checkAllAndNotify(scripts) { - const notes = []; - const results = await Promise.all(scripts.map(item => checkUpdate(item, notes))); - if (notes.length === 1) { - notify(notes[0]); - } else if (notes.length) { - notify({ - // FF doesn't show notifications of type:'list' so we'll use `text` everywhere - text: notes.map(n => n.text).join('\n'), - onClick: browser.runtime.openOptionsPage, - }); - } - return results; -} +import { getOption, hookOptions, setOption } from './options'; +import { kUpdateEnabledScriptsOnly } from '@/common/options-defaults'; +import { requestNewer } from './storage-fetch'; const processes = {}; -const NO_HTTP_CACHE = { - 'Cache-Control': 'no-cache, no-store, must-revalidate', +const FAST_CHECK = { + ...NO_CACHE, + // Smart servers like OUJS send a subset of the metablock without code + headers: { Accept: 'text/x-userscript-meta,*/*' }, }; +const kChecking = 'checking'; -// resolves to true if successfully updated -function checkUpdate(script, notes) { - const { id } = script.props; - const promise = processes[id] || (processes[id] = doCheckUpdate(script, notes)); - return promise; -} +init.then(autoUpdate); +hookOptions(changes => 'autoUpdate' in changes && autoUpdate()); -async function doCheckUpdate(script, notes) { - const { id } = script.props; +addOwnCommands({ + /** + * @param {number | number[] | 'auto'} [id] - when omitted, all scripts are checked + * @return {Promise} number of updated scripts + */ + async CheckUpdate(id) { + const isAuto = id === AUTO; + const isAll = isAuto || !id; + const scripts = isAll ? getScripts() : ensureArray(id).map(getScriptById).filter(Boolean); + const urlOpts = { + all: true, + allowedOnly: isAll, + enabledOnly: isAll && getOption(kUpdateEnabledScriptsOnly), + }; + const opts = { + [FETCH_OPTS]: { + ...NO_CACHE, + [MULTI]: isAuto ? AUTO : isAll, + }, + }; + const jobs = scripts.map(script => { + const curId = script.props.id; + const urls = getScriptUpdateUrl(script, urlOpts); + return urls && ( + processes[curId] || ( + processes[curId] = doCheckUpdate(curId, script, urls, opts) + ) + ); + }).filter(Boolean); + const results = await Promise.all(jobs); + const notes = results.filter(r => r?.text); + if (notes.length) { + notifyToOpenScripts( + notes.some(n => n.err) ? i18n('msgOpenUpdateErrors') + : IS_FIREFOX ? i18n('optionUpdate') + : '', // Chrome confusingly shows the title next to message using the same font + notes.map(n => `* ${n.text}\n`).join(''), + notes.map(n => n.script.props.id), + ); + } + if (isAll) setOption('lastUpdate', Date.now()); + return results.reduce((num, r) => num + (r === true), 0); + }, +}); + +async function doCheckUpdate(id, script, urls, opts) { + let res; let msgOk; let msgErr; - let resourceOpts; try { const { update } = await parseScript({ id, - code: await downloadUpdate(script), - update: { checking: false }, + code: await downloadUpdate(script, urls, opts), + bumpDate: true, + update: { [kChecking]: false }, + ...opts, }); msgOk = i18n('msgScriptUpdated', [getScriptName(update)]); - resourceOpts = { headers: NO_HTTP_CACHE }; - return true; + res = true; } catch (update) { - msgErr = update.error; - // Either proceed with normal fetch on no-update or skip it altogether on error - resourceOpts = !update.error && !update.checking && {}; + msgErr = update.error + || !update[kChecking] && await fetchResources(script, opts); if (process.env.DEBUG) console.error(update); } finally { - if (resourceOpts) { - msgErr = await fetchResources(script, null, resourceOpts); - if (process.env.DEBUG && msgErr) console.error(msgErr); - } if (canNotify(script) && (msgOk || msgErr)) { - notes.push({ + res = { script, text: [msgOk, msgErr]::trueJoin('\n'), - }); + err: !!msgErr, + }; } delete processes[id]; } + return res; } -async function downloadUpdate({ props: { id }, meta, custom }) { - const downloadURL = custom.downloadURL || meta.downloadURL || custom.lastInstallURL; - const updateURL = custom.updateURL || meta.updateURL || downloadURL; - if (!updateURL) throw false; +async function downloadUpdate(script, urls, opts) { let errorMessage; + const { meta, props: { id } } = script; + const [downloadURL, updateURL] = urls; const update = {}; const result = { update, where: { id } }; announce(i18n('msgCheckingForUpdate')); try { - const { data } = await request(updateURL, { - headers: { ...NO_HTTP_CACHE, Accept: 'text/x-userscript-meta,*/*' }, - }); - const { version } = parseMeta(data); + const { data } = await requestNewer(updateURL, { ...FAST_CHECK, ...opts }) || {}; + const { version, [__CODE]: metaStr } = data ? parseMeta(data, { retMetaStr: true }) : {}; if (compareVersion(meta.version, version) >= 0) { - announce(i18n('msgNoUpdate'), { checking: false }); + announce(i18n('msgNoUpdate'), { [kChecking]: false }); } else if (!downloadURL) { - announce(i18n('msgNewVersion'), { checking: false }); + announce(i18n('msgNewVersion'), { [kChecking]: false }); + } else if (downloadURL === updateURL && data?.replace(METABLOCK_RE, '').trim()) { + // Code is present, so this is not a smart server, hence the response is the entire script + announce(i18n('msgUpdated')); + return data; } else { announce(i18n('msgUpdating')); errorMessage = i18n('msgErrorFetchingScript'); - return (await request(downloadURL, { headers: NO_HTTP_CACHE })).data; + return downloadURL === updateURL && metaStr.trim() !== data.trim() + ? data + : (await requestNewer(downloadURL, { ...NO_CACHE, ...opts })).data; } } catch (error) { if (process.env.DEBUG) console.error(error); announce(errorMessage || i18n('msgErrorFetchingUpdateInfo'), { error }); } throw update; - function announce(message, { error, checking = !error } = {}) { + function announce(message, { error, [kChecking]: checking = !error } = {}) { Object.assign(update, { message, - checking, + [kChecking]: checking, error: error ? `${i18n('genericError')} ${error.status}, ${error.url}` : null, // `null` is transferable in Chrome unlike `undefined` }); - sendCmd(CMD_SCRIPT_UPDATE, result); + sendCmd('UpdateScript', result); } } @@ -128,16 +146,19 @@ function canNotify(script) { : script.config.notifyUpdates ?? allowed; } -function notify({ - script, - text, - onClick = () => commands.OpenEditor(script.props.id), -}) { - commands.Notification({ - text, - // FF doesn't show the name of the extension in the title of the notification - title: ua.isFirefox ? `${i18n('titleScriptUpdated')} - ${i18n('extName')}` : '', - }, undefined, { - onClick, - }); +function autoUpdate() { + const interval = getUpdateInterval(); + if (!interval) return; + let elapsed = Date.now() - getOption('lastUpdate'); + if (elapsed >= interval) { + // Wait on startup for things to settle and after unsuspend for network reconnection + setTimeout(commands.CheckUpdate, 20e3, AUTO); + elapsed = 0; + } + clearTimeout(autoUpdate.timer); + autoUpdate.timer = setTimeout(autoUpdate, Math.min(TIMEOUT_MAX, interval - elapsed)); +} + +export function getUpdateInterval() { + return (+getOption('autoUpdate') || 0) * TIMEOUT_24HOURS; } diff --git a/src/background/utils/url.js b/src/background/utils/url.js new file mode 100644 index 0000000000..3d7013e1fd --- /dev/null +++ b/src/background/utils/url.js @@ -0,0 +1,45 @@ +import { isCdnUrlRe, isDataUri, isRemote, makeRaw, request, tryUrl } from '@/common'; +import { VM_HOME } from '@/common/consts'; +import limitConcurrency from '@/common/limit-concurrency'; +import { addOwnCommands } from './init'; +import { testBlacklistNet } from './tester'; + +export const requestLimited = limitConcurrency(request, 4, 100, 1000, + url => url.split('/')[2] // simple extraction of the `host` part +); + +addOwnCommands({ + async Request({ url, vet, ...opts }) { + const vettedUrl = vet ? vetUrl(url) : url; + const fn = isRemote(vettedUrl) && !isCdnUrlRe.test(vettedUrl) + ? requestLimited + : request; + const res = await fn(vettedUrl, opts); + return opts[kResponseType] === 'blob' + ? makeRaw(res) + : res.data; // TODO: if we ever need headers, send it as [...headers] to make it transferable + }, +}); + +/** + * @param {string} url + * @param {string} [base] + * @param {boolean} [throwOnFailure] + * @returns {string} a resolved `url` or `data:,Invalid URL ${url}` + */ +export function vetUrl(url, base = VM_HOME, throwOnFailure) { + let res, err; + if (isDataUri(url)) { + res = url; + } else { + res = tryUrl(url, base); + err = !res ? 'Invalid' + : (res.startsWith(extensionRoot) || testBlacklistNet(res)) && 'Blacklisted'; + if (err) { + err = `${err} URL ${res || url}`; + if (throwOnFailure) throw err; + res = `data:,${err}`; + } + } + return res; +} diff --git a/src/background/utils/values.js b/src/background/utils/values.js index 4732204b8b..aed47fc5ae 100644 --- a/src/background/utils/values.js +++ b/src/background/utils/values.js @@ -1,126 +1,144 @@ -import { isEmpty, sendTabCmd } from '#/common'; -import { forEachEntry, forEachKey, objectSet } from '#/common/object'; -import { getScript, getValueStoresByIds, dumpValueStores } from './db'; -import { commands } from './message'; +import { isEmpty, makePause, sendTabCmd } from '@/common'; +import { forEachEntry, forEachValue, nest, objectGet, objectSet } from '@/common/object'; +import { getScript } from './db'; +import { addOwnCommands, addPublicCommands } from './init'; +import storage, { S_VALUE, S_VALUE_PRE } from './storage'; +import { cachedStorageApi } from './storage-cache'; +import { getFrameDocIdAsObj, getFrameDocIdFromSrc } from './tabs'; -const openers = {}; // { scriptId: { tabId: { frameId: 1, ... }, ... } } -let cache = {}; // { scriptId: { key: { last: value, tabId: { frameId: value } } } } -let updateScheduled; +/** { scriptId: { tabId: { frameId: {key: raw}, ... }, ... } } */ +const openers = {}; +let chain = Promise.resolve(); +let toCommit = {}; +let toCommitPending; +let toSend = {}; -Object.assign(commands, { - /** @return {Promise} */ - async GetValueStore(id) { - const stores = await getValueStoresByIds([id]); - return stores[id] || {}; +addOwnCommands({ + async GetValueStore(id, { tab }) { + const frames = nest(nest(openers, id), tab.id); + const values = frames[0] || (frames[0] = await storage[S_VALUE].getOne(id)); + return values; }, - /** @param {{ where, store }[]} data - * @return {Promise} */ - async SetValueStores(data) { - // Value store will be replaced soon. - const stores = data.reduce((res, { where, store }) => { - const id = where.id || getScript(where)?.props.id; - if (id) res[id] = store; - return res; - }, {}); - await Promise.all([ - dumpValueStores(stores), - broadcastValueStores(groupStoresByFrame(stores)), - ]); - }, - /** @return {void} */ - UpdateValue({ id, key, value = null }, src) { - objectSet(cache, [id, key, 'last'], value); - objectSet(cache, [id, key, src.tab.id, src.frameId], value); - updateLater(); + /** + * @param {Object} data - key can be an id or a uri + */ + SetValueStores(data) { + toCommit = {}; + data::forEachEntry(([id, store = {}]) => { + id = getScript({ id: +id, uri: id })?.props.id; + if (id) { + toCommit[S_VALUE_PRE + id] = store; + toSend[id] = store; + } + }); + commit(true); }, }); -browser.tabs.onRemoved.addListener(resetValueOpener); -browser.tabs.onReplaced.addListener((addedId, removedId) => resetValueOpener(removedId)); +addPublicCommands({ + UpdateValue(what, src) { + for (const id in what) { + const values = objectGet(openers, [id, src.tab.id, getFrameDocIdFromSrc(src)]); + // preventing the weird case of message arriving after the page navigated + if (!values) return; + const hub = nest(toSend, id); + const data = what[id]; + for (const key in data) { + const raw = data[key]; + if (raw) values[key] = raw; else delete values[key]; + hub[key] = raw || null; + } + toCommit[S_VALUE_PRE + id] = values; + } + toCommitPending ??= setTimeout(commit); + }, +}); -export function resetValueOpener(tabId) { - openers::forEachEntry(([id, openerTabs]) => { - if (tabId in openerTabs) { - delete openerTabs[tabId]; - if (isEmpty(openerTabs)) delete openers[id]; +export function clearValueOpener(tabId, frameId) { + if (tabId == null) { + toSend = {}; + } + openers::forEachEntry(([id, tabs]) => { + const frames = tabs[tabId]; + if (frames) { + if (frameId) { + delete frames[frameId]; + if (isEmpty(frames)) delete tabs[tabId]; + } else { + delete tabs[tabId]; + } + } + if (tabId == null || isEmpty(tabs)) { + delete openers[id]; } }); } -export function addValueOpener(tabId, frameId, scriptIds) { - scriptIds.forEach((id) => { - objectSet(openers, [id, tabId, frameId], 1); - }); +/** + * @param {VMInjection.Script[] | number[]} injectedScripts + * @param {number} tabId + * @param {number|string} frameId + */ +export async function addValueOpener(injectedScripts, tabId, frameId) { + const valuesById = +injectedScripts[0] // restoring storage for page from bfcache + && await storage[S_VALUE].getMulti(injectedScripts); + for (const script of injectedScripts) { + const id = valuesById ? script : script.id; + const values = valuesById ? valuesById[id] || null : script[VALUES]; + if (values) objectSet(openers, [id, tabId, frameId], Object.assign({}, values)); + else delete openers[id]; + } } -async function updateLater() { - while (!updateScheduled) { - updateScheduled = true; - await 0; - const currentCache = cache; - cache = {}; - await doUpdate(currentCache); - updateScheduled = false; - if (isEmpty(cache)) break; +/** Moves values of a pre-rendered page identified by documentId to frameId:0 */ +export function reifyValueOpener(ids, documentId) { + for (const id of ids) { + openers[id]::forEachValue(frames => { + if (documentId in frames) { + frames[0] = frames[documentId]; + delete frames[documentId]; + } + }); } } -async function doUpdate(currentCache) { - const ids = Object.keys(currentCache); - const valueStores = await getValueStoresByIds(ids); - ids.forEach((id) => { - currentCache[id]::forEachEntry(([key, { last }]) => { - objectSet(valueStores, [id, key], last || undefined); - }); - }); - await Promise.all([ - dumpValueStores(valueStores), - broadcastValueStores(groupCacheByFrame(currentCache), { partial: true }), - ]); +function commit(flushNow) { + cachedStorageApi.set(toCommit, flushNow); + chain = chain.catch(console.warn).then(broadcast); + toCommit = {}; + toCommitPending = null; } -async function broadcastValueStores(tabFrameData, { partial } = {}) { - const tasks = []; - for (const [tabId, frames] of Object.entries(tabFrameData)) { - for (const [frameId, frameData] of Object.entries(frames)) { - if (!isEmpty(frameData)) { - if (partial) frameData.partial = true; - tasks.push(sendTabCmd(+tabId, 'UpdatedValues', frameData, { frameId: +frameId })); - if (tasks.length === 20) await Promise.all(tasks.splice(0)); // throttling +async function broadcast() { + const toTabs = {}; + let num = 0; + toSend::forEachEntry(groupByTab, toTabs); + toSend = {}; + for (const [tabId, frames] of Object.entries(toTabs)) { + for (const [frameId, toFrame] of Object.entries(frames)) { + if (!isEmpty(toFrame)) { + // Not awaiting because the tab may be busy/sleeping + sendTabCmd(+tabId, 'UpdatedValues', toFrame, getFrameDocIdAsObj(frameId)); + if (!(++num % 20)) await makePause(); // throttling } } } - await Promise.all(tasks); -} - -// Returns per tab/frame data with only the changed values -function groupCacheByFrame(cacheData) { - const toSend = {}; - cacheData::forEachEntry(([id, scriptData]) => { - const dataEntries = Object.entries(scriptData); - openers[id]::forEachEntry(([tabId, frames]) => { - frames::forEachKey((frameId) => { - dataEntries.forEach(([key, history]) => { - // Skipping this frame if its last recorded value is identical - if (history.last !== history[tabId]?.[frameId]) { - objectSet(toSend, [tabId, frameId, id, key], history.last); - } - }); - }); - }); - }); - return toSend; } -// Returns per tab/frame data -function groupStoresByFrame(stores) { - const toSend = {}; - stores::forEachEntry(([id, store]) => { - openers[id]::forEachEntry(([tabId, frames]) => { - frames::forEachKey(frameId => { - objectSet(toSend, [tabId, frameId, id], store); +/** @this {Object} accumulator */ +function groupByTab([id, valuesToSend]) { + const entriesToSend = Object.entries(valuesToSend); + openers[id]::forEachEntry(([tabId, frames]) => { + if (tabId < 0) return; // script values editor watches for changes differently + const toFrames = nest(this, tabId); + frames::forEachEntry(([frameId, last]) => { + const toScript = nest(nest(toFrames, frameId), id); + entriesToSend.forEach(([key, raw]) => { + if (raw !== last[key]) { + if (raw) last[key] = raw; else delete last[key]; + toScript[key] = raw; + } }); }); }); - return toSend; } diff --git a/src/common/browser.js b/src/common/browser.js index 5aad72c058..1e1acccc0c 100644 --- a/src/common/browser.js +++ b/src/common/browser.js @@ -1,126 +1,184 @@ +let { browser } = global; +const kAddListener = 'addListener'; +const kRemoveListener = 'removeListener'; + // Since this also runs in a content script we'll guard against implicit global variables // for DOM elements with 'id' attribute which is a standard feature, more info: // https://github.com/mozilla/webextension-polyfill/pull/153 // https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object -if (!global.browser?.runtime?.sendMessage) { - const { chrome, Promise } = global; - const wrapAPIs = (source, meta = {}) => { - return Object.entries(source) - .reduce((target, [key, value]) => { - const metaVal = meta[key]; - if (metaVal) { - if (typeof metaVal === 'function') { - value = source::metaVal(value); - } else if (typeof metaVal === 'object' && typeof value === 'object') { - value = wrapAPIs(value, metaVal); - } - target[key] = value; - } - return target; - }, {}); +if (!IS_FIREFOX && !browser?.runtime) { + const { Proxy: SafeProxy } = global; + const { bind } = SafeProxy; + const MESSAGE = 'message'; + const STACK = 'stack'; + const isSyncMethodName = key => key === kAddListener + || key === kRemoveListener + || key === 'hasListener' + || key === 'hasListeners'; + /** API types or enums or literal constants */ + const proxifyValue = (target, key, src, metaVal) => { + const srcVal = src[key]; + if (srcVal === undefined) return; + let res; + if (isFunction(metaVal)) { + res = metaVal(src, srcVal); + } else if (isFunction(srcVal)) { + res = metaVal === 0 || isSyncMethodName(key) || !hasOwnProperty(src, key) + ? srcVal::bind(src) + : wrapAsync(src, srcVal); // eslint-disable-line no-use-before-define + } else if (isObject(srcVal) && metaVal !== 0) { + res = proxifyGroup(srcVal, metaVal); // eslint-disable-line no-use-before-define + } else { + res = srcVal; + } + target[key] = res; + return res; }; - const wrapAsync = function wrapAsync(func) { - return (...args) => { - const promise = new Promise((resolve, reject) => { - this::func(...args, (res) => { - const err = chrome.runtime.lastError; - if (err) reject(err); - else resolve(res); - }); + const proxifyGroup = (src, meta) => new SafeProxy({ __proto__: null }, { + __proto__: null, + get: (group, key) => group[key] ?? proxifyValue(group, key, src, meta?.[key]), + }); + /** + * @param {Object} thisArg - original API group + * @param {function} func - original API function + * @param {WrapAsyncPreprocessorFunc} [preprocessorFunc] - modifies the API callback's response + */ + const wrapAsync = (thisArg, func, preprocessorFunc) => ( + (...args) => { + let resolve; + let reject; + /* Using resolve/reject to call API in the scope of this function, not inside Promise, + because an API validation exception is thrown synchronously both in Chrome and FF + so the caller can use try/catch to detect it like we've been doing in icon.js */ + const promise = new SafePromise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; }); - if (process.env.DEBUG) promise.catch(err => console.warn(args, err?.message || err)); + // Make the error messages actually useful by capturing a real stack + const stackInfo = new SafeError(`callstack before invoking ${func.name || 'chrome API'}:`); + // A single parameter `result` is fine because we don't use API that return more + const cb = result => { + const runtimeErr = chrome.runtime.lastError; + const err = runtimeErr || ( + preprocessorFunc + ? preprocessorFunc(resolve, result) + : resolve(result) + ); + // Prefer `reject` over `throw` which stops debugger in 'pause on exceptions' mode + if (err) { + if (!runtimeErr) stackInfo[STACK] = `${err[1]}\n${stackInfo[STACK]}`; + stackInfo[MESSAGE] = runtimeErr ? err[MESSAGE] : `${err[0]}`; + stackInfo.isRuntime = !!runtimeErr; + reject(stackInfo); + } + }; + if (process.env.IS_INJECTED) { + safePush(args, cb); /* global safePush */ + try { + safeApply(func, thisArg, args); + } catch (e) { + if (e[MESSAGE] === 'Extension context invalidated.') { + /* global logging */// only used with process.env.IS_INJECTED=content + logging.error(`Please reload the tab to restore ${VIOLENTMONKEY} API for userscripts.`); + } else { + throw e; + } + } + } else { + /* Not process.env.IS_INJECTED */// eslint-disable-next-line no-restricted-syntax + thisArg::func(...args, cb); + } + if (process.env.DEBUG) promise.catch(err => console.warn(args, err?.[MESSAGE] || err)); return promise; - }; + } + ); + const wrapResponse = (result, error) => { + if (process.env.DEBUG) console[error ? 'warn' : 'log']('sendResponse', error || result); + return [ + result ?? null, // `undefined` is not transferable in Chrome, but `null` is + error && ( + error[MESSAGE] + ? [error[MESSAGE], error[STACK]] + : [error, new SafeError()[STACK]] + ), + ]; }; - const wrapMessageListener = listener => (message, sender, sendResponse) => { - if (process.env.DEBUG) console.info('receive', message); - const result = listener(message, sender); - if (typeof result?.then === 'function') { - result.then((data) => { - if (process.env.DEBUG) console.info('send', data); - sendResponse({ data }); - }, (error) => { - if (process.env.DEBUG) console.warn(error); - sendResponse({ error: error instanceof Error ? error.stack : error }); - }) - .catch(() => {}); // Ignore sendResponse error - return true; + const sendResponseAsync = async (result, sendResponse) => { + try { + sendResponse(wrapResponse(await result)); + } catch (err) { + sendResponse(wrapResponse(0, err)); } - if (typeof result !== 'undefined') { - // In some browsers (e.g Chrome 56, Vivaldi), the listener in - // popup pages are not properly cleared after closed. - // They may send `undefined` before the real response is sent. - sendResponse({ data: result }); + }; + const onMessageListener = (listener, message, sender, sendResponse) => { + if (process.env.DEBUG) console.info('receive', message); + try { + const result = listener(message, sender); + if (result && ( + process.env.IS_INJECTED + ? isPromise(result) /* global isPromise */ + : result instanceof Promise + )) { + sendResponseAsync(result, sendResponse); + return true; + } else if (result !== undefined) { + /* WARNING: when using onMessage in extension pages don't use `async` + * and make sure to return `undefined` for content messages like GetInjected */ + sendResponse(wrapResponse(result)); + } + } catch (err) { + sendResponse(wrapResponse(0, err)); } }; - const meta = { - browserAction: true, - commands: true, - cookies: true, - extension: true, - i18n: true, - notifications: { - onClicked: true, - onClosed: true, - clear: wrapAsync, - create: wrapAsync, - }, + /** @type {WrapAsyncPreprocessorFunc} */ + const unwrapResponse = (resolve, response) => ( + !response && 'null response' + || response[1] // error created in wrapResponse + || resolve(response[0]) // result created in wrapResponse + ); + const wrapSendMessage = (runtime, sendMessage) => ( + wrapAsync(runtime, sendMessage, unwrapResponse) + ); + /** + * 0 = non-async method or the entire group + * function = transformer like (originalObj, originalFunc): function + */ + browser = global.browser = proxifyGroup(chrome, { + extension: 0, // we don't use its async methods + i18n: 0, // we don't use its async methods runtime: { - connect: true, - getManifest: true, - getPlatformInfo: wrapAsync, - getURL: true, - openOptionsPage: wrapAsync, - onConnect: true, - onMessage: onMessage => ({ - addListener: listener => onMessage.addListener(wrapMessageListener(listener)), - }), - sendMessage(sendMessage) { - const promisifiedSendMessage = wrapAsync(sendMessage); - const unwrapResponse = ({ data: response, error } = {}) => { - if (error) throw error; - return response; - }; - return data => promisifiedSendMessage(data).then(unwrapResponse); - }, - }, - storage: { - local: { - get: wrapAsync, - set: wrapAsync, - remove: wrapAsync, + connect: 0, + getManifest: 0, + getURL: 0, + onMessage: { + [kAddListener]: (onMessage, addListener) => ( + listener => { + if (process.env.DEV + && !process.env.IS_INJECTED + && /^async/.test(listener)) { + throw new Error('onMessage listener cannot be async'); + // ...because it must be able to return `undefined` for unintended messages + // to allow onMessage of the intended context to handle this message + // TODO: migrate to addRuntimeListener(fn, commands: object) + } + return onMessage::addListener(onMessageListener::bind(null, listener)); + } + ), }, - onChanged: true, + sendMessage: wrapSendMessage, }, - tabs: { - onCreated: true, - onUpdated: true, - onRemoved: true, - onReplaced: true, - create: wrapAsync, - get: wrapAsync, - query: wrapAsync, - reload: wrapAsync, - remove: wrapAsync, - sendMessage: wrapAsync, - update: wrapAsync, - executeScript: wrapAsync, + tabs: !process.env.IS_INJECTED && { + connect: 0, + sendMessage: wrapSendMessage, }, - webRequest: true, - windows: { - create: wrapAsync, - getCurrent: wrapAsync, - update: wrapAsync, - }, - }; - global.browser = wrapAPIs(chrome, meta); -} else if (process.env.DEBUG && !global.chrome.app) { + }); +} else if (process.env.DEBUG && IS_FIREFOX) { + /* eslint-disable no-restricted-syntax */// this is a debug-only section let counter = 0; - const { runtime } = global.browser; + const { runtime } = browser; const { sendMessage, onMessage } = runtime; const log = (type, args, id, isResponse) => console.info( - `%c${type}Message#%d${isResponse ? ' response' : ''}`, - isResponse ? '' : 'color:yellow', + `${type}Message#%d${isResponse ? ' response' : ''}`, id, ...args, ); @@ -139,8 +197,28 @@ if (!global.browser?.runtime?.sendMessage) { const { frameId, tab, url } = sender; log('on', [msg, { frameId, tab, url }], id); const result = listener(msg, sender); - (typeof result?.then === 'function' ? result : Promise.resolve(result)) + (isFunction(result?.then) ? result : SafePromise.resolve(result)) .then(data => log('on', [data], id, true), console.warn); return result; }); + /* eslint-enable no-restricted-syntax */ +} + +/** + * @callback WrapAsyncPreprocessorFunc + * @param {function(any)} resolve - called on success + * @param {any} response - API callback's response + * @returns {?string[]} - [errorMessage, errorStack] array on error + */ + +export default browser; + +/** @this {object} browser api event like browser.tabs.onRemoved */ +export function listenOnce(cb) { + const event = this; + const onceFn = data => { + event[kRemoveListener](onceFn); + cb(data); + }; + event[kAddListener](onceFn); } diff --git a/src/common/cache.js b/src/common/cache.js index 9daa9ee916..2d0c566c19 100644 --- a/src/common/cache.js +++ b/src/common/cache.js @@ -15,35 +15,43 @@ export default function initCache({ let batchStartTime; // eslint-disable-next-line no-return-assign const getNow = () => batchStarted && batchStartTime || (batchStartTime = performance.now()); - return { - batch, get, getValues, pop, put, del, has, hit, destroy, + const OVERRUN = 1000; // in ms, to reduce frequency of calling setTimeout + const exports = { + batch, get, some, pop, put, del, has, hit, destroy, }; + if (process.env.DEV) Object.defineProperty(exports, 'data', { get: () => cache }); + return exports; function batch(enable) { batchStarted = enable; batchStartTime = 0; } - function get(key, def) { + function get(key, def, shouldHit = true) { const item = cache[key]; + if (item && shouldHit) { + reschedule(item, item.lifetime); + } return item ? item.value : def; } - function getValues() { - return Object.values(cache).map(item => item.value); + /** + * @param {(val:?, key:string) => void} fn + * @param {Object} [thisObj] + */ + function some(fn, thisObj) { + for (const key in cache) { + const item = cache[key]; + // Might be already deleted by fn + if (item && fn.call(thisObj, item.value, key)) { + return true; + } + } } function pop(key, def) { const value = get(key, def); del(key); return value; } - function put(key, value, lifetime = defaultLifetime) { - if (value) { - cache[key] = { - value, - expiry: lifetime + getNow(), - }; - reschedule(lifetime); - } else { - del(key); - } + function put(key, value, lifetime) { + reschedule(cache[key] = lifetime ? { value, lifetime } : { value }, lifetime); return value; } function del(key) { @@ -54,13 +62,12 @@ export default function initCache({ } } function has(key) { - return cache[key]; + return key in cache; } - function hit(key, lifetime = defaultLifetime) { + function hit(key, lifetime) { const entry = cache[key]; if (entry) { - entry.expiry = lifetime + getNow(); - reschedule(lifetime); + reschedule(entry, lifetime); } } function destroy() { @@ -77,18 +84,17 @@ export default function initCache({ clearTimeout(timer); timer = 0; } - function reschedule(lifetime) { + function reschedule(entry, lifetime = defaultLifetime) { + entry.expiry = lifetime + getNow(); if (timer) { if (lifetime >= minLifetime) return; clearTimeout(timer); } minLifetime = lifetime; - timer = setTimeout(trim, lifetime); + timer = setTimeout(trim, lifetime + OVERRUN); } function trim() { - // next timer won't be able to run earlier than 10ms - // so we'll sweep the upcoming expired entries in this run - const now = performance.now() + 10; + const now = performance.now(); let closestExpiry = Number.MAX_SAFE_INTEGER; // eslint-disable-next-line guard-for-in for (const key in cache) { @@ -101,7 +107,7 @@ export default function initCache({ } minLifetime = closestExpiry - now; timer = closestExpiry < Number.MAX_SAFE_INTEGER - ? setTimeout(trim, minLifetime) + ? setTimeout(trim, minLifetime + OVERRUN) : 0; } } diff --git a/src/common/consts-sync.js b/src/common/consts-sync.js new file mode 100644 index 0000000000..331556d808 --- /dev/null +++ b/src/common/consts-sync.js @@ -0,0 +1,14 @@ +//#region common +export const USER_CONFIG = 'userConfig'; +export const PASSWORD = 'password'; +//#endregion +//#region WebDAV +export const ANONYMOUS = 'anonymous'; +export const SERVER_URL = 'serverUrl'; +export const USERNAME = 'username'; +//#endregion +//#region SyncMode +export const SYNC_MERGE = 'merge'; +export const SYNC_PUSH = 'push'; +export const SYNC_PULL = 'pull'; +//#endregion diff --git a/src/common/consts.js b/src/common/consts.js index 0b8b6bb13b..bd79942cc9 100644 --- a/src/common/consts.js +++ b/src/common/consts.js @@ -1,33 +1,91 @@ -export const INJECT_AUTO = 'auto'; -export const INJECT_PAGE = 'page'; -export const INJECT_CONTENT = 'content'; +// SAFETY WARNING! Exports used by `injected` must make ::safe() calls and use __proto__:null -export const INJECT_MAPPING = { - // `auto` tries to provide `window` from the real page as `unsafeWindow` - [INJECT_AUTO]: [INJECT_PAGE, INJECT_CONTENT], - // inject into page context - [INJECT_PAGE]: [INJECT_PAGE], - // inject into content context only - [INJECT_CONTENT]: [INJECT_CONTENT], -}; - -export const CMD_SCRIPT_ADD = 'AddScript'; -export const CMD_SCRIPT_UPDATE = 'UpdateScript'; - -// Allow metadata lines to start with WHITESPACE? '//' SPACE -// Allow anything to follow the predefined text of the metaStart/End -// The SPACE must be on the same line and specifically \x20 as \s would also match \r\n\t -// Note: when there's no valid metablock, an empty string is matched for convenience -export const METABLOCK_RE = /(?:^|\n)\s*\/\/\x20==UserScript==([\s\S]*?\n)\s*\/\/\x20==\/UserScript==|$/; - -export const INJECTABLE_TAB_URL_RE = /^(https?|file|ftps?):/; +export const CHARSET_UTF8 = 'charset=UTF-8'; +export const FORM_URLENCODED = 'application/x-www-form-urlencoded'; +export const INFERRED = 'inferred'; +export const HOMEPAGE_URL = 'homepageURL'; +export const SUPPORT_URL = 'supportURL'; +/** A relaxed check, see METABLOCK_RE description */ +export const USERSCRIPT_META_INTRO = '==UserScript=='; +/** A strictly valid metablock should start at the beginning of the line: + * "// ==UserScript==" with exactly one \x20 space inside. + * To match Tampermonkey's relaxed parsing, we allow any preceding text at line start + * (i.e. not just spaces for indented metablock comments, but literally anything) + * and inside, but we'll warn about this later in the installer/editor. */ +export const METABLOCK_RE = re`/ +# 1 2 3 + ((?:^|\n)(.*?)\/\/([\x20\t]*)==UserScript==) +# 4 + ([\s\S]*?\n) +# 5 6 7 + ((.*?)\/\/([\x20\t]*)==\/UserScript==) +/x`; +export const META_STR = 'metaStr'; +export const NEWLINE_END_RE = /\n((?!\n)\s)*$/; +export const WATCH_STORAGE = 'watchStorage'; // `browser` is a local variable since we remove the global `chrome` and `browser` in injected* // to prevent exposing them to userscripts with `@inject-into content` -export const { browser } = global; +export const browser = process.env.IS_INJECTED !== 'injected-web' && global.browser; // setTimeout truncates the delay to a 32-bit signed integer so the max delay is ~24 days export const TIMEOUT_MAX = 0x7FFF_FFFF; export const TIMEOUT_HOUR = 60 * 60 * 1000; export const TIMEOUT_24HOURS = 24 * 60 * 60 * 1000; export const TIMEOUT_WEEK = 7 * 24 * 60 * 60 * 1000; + +export const BLACKLIST = 'blacklist'; +export const BLACKLIST_NET = BLACKLIST + 'Net'; +export const ERRORS = 'Errors'; +export const RUN_AT_RE = /^document-(start|body|end|idle)$/; +export const KNOWN_INJECT_INTO = { + // Using the default injection order: auto, page, content + [AUTO]: 1, + [PAGE]: 1, + [CONTENT]: 1, +}; +export const NO_CACHE = { cache: 'no-cache' }; +export const __CODE = /*@__PURE__*/Symbol('code'); // not enumerable and stripped when serializing +export const UA_PROPS = ['userAgent', 'brands', 'mobile', 'platform']; +export const TL_AWAIT = 'topLevelAwait'; +export const UNWRAP = 'unwrap'; +export const FETCH_OPTS = 'fetchOpts'; +export const ERR_BAD_PATTERN = 'Bad pattern:'; +export const VM_HOME = 'https://violentmonkey.github.io/'; +export const VM_DOCS_MATCHING = VM_HOME + 'api/matching/'; +export const FILE_GLOB_ALL = 'file://*/*'; +export const XHR_COOKIE_RE = /:\W+([-\w]+)/; // extracts ://id in Chrome, ://{id} in Firefox +/** @type {(str: string, opts?: {}) => Uint8Array} */ +export const U8_fromBase64 = process.env.IS_INJECTED !== 'injected-web' && Uint8Array.fromBase64; +export const UPLOAD = 'upload'; +export const GM_API_NAMES = [ + 'GM', + 'GM_addElement', + 'GM_addStyle', + 'GM_addValueChangeListener', + 'GM_deleteValue', + 'GM_deleteValues', + 'GM_download', + 'GM_getResourceText', + 'GM_getResourceURL', + 'GM_getValue', + 'GM_getValues', + 'GM_info', + 'GM_listValues', + 'GM_log', + 'GM_notification', + 'GM_openInTab', + 'GM_registerMenuCommand', + 'GM_removeValueChangeListener', + 'GM_setClipboard', + 'GM_setValue', + 'GM_setValues', + 'GM_unregisterMenuCommand', + 'GM_xmlhttpRequest', + 'unsafeWindow', +]; +export const GM4_ALIAS = { + __proto__: null, + getResourceURL: 'getResourceUrl', + xmlhttpRequest: 'xmlHttpRequest', +}; diff --git a/src/common/date.js b/src/common/date.js new file mode 100644 index 0000000000..8f75ae87e0 --- /dev/null +++ b/src/common/date.js @@ -0,0 +1,64 @@ +const DAY_MS = 24 * 3600e3; +const WEEK_MS = 7 * 24 * 3600e3; +// Using simple padding functions because String#padStart is Chrome57+ but our minimum is 55 +const pad2 = num => `${num < 10 ? '0' : ''}${num}`; +const pad3 = num => `${num < 10 && '00' || num < 100 && '0' || ''}${num}`; +const getYear = date => date.getFullYear(); +const getDayOfYear = date => Math.floor((date - new Date(getYear(date), 0, 1)) / DAY_MS) + 1; +const getWeekOfYear = date => Math.floor((date - new Date(getYear(date), 0, 1)) / WEEK_MS) + 1; +const toLocaleString = (date, opts) => date.toLocaleString([navigator.language], opts); + +/** @type {Objectstring>} */ +export const DATE_FMT = { + M: date => date.getMonth() + 1, // 1 2 ... 11 12 + MM: date => pad2(date.getMonth() + 1), // 01 02 ... 11 12 + MMM: date => toLocaleString(date, { month: 'short' }), // Jan Feb + MMMM: date => toLocaleString(date, { month: 'long' }), // January February + Q: date => Math.floor(date.getMonth() / 3) + 1, // 1 2 3 4 + D: date => date.getDate(), // 1 2 ... 30 31 + DD: date => pad2(date.getDate()), // 01 02 ... 30 31 + DDD: getDayOfYear, // 1 2 ... 364 365 + DDDD: date => pad3(getDayOfYear(date)), // 001 002 ... 364 365 + d: date => date.getDay(), // 0 1 ... 5 6 + dd: date => toLocaleString(date, { weekday: 'short' }).slice(0, 2), // Su Mo ... Fr Sa + ddd: date => toLocaleString(date, { weekday: 'short' }), // Sun Mon ... Fri Sat + dddd: date => toLocaleString(date, { weekday: 'long' }), // Sunday Monday ... Friday Saturday + w: getWeekOfYear, // 1 2 ... 52 53 + ww: date => pad2(getWeekOfYear(date)), // 01 02 ... 52 53 + Y: getYear, + YY: date => pad2(getYear(date) % 100), + YYYY: date => `${getYear(date)}`.slice(-4), + H: date => date.getHours(), // 0 1 ... 22 23 + HH: date => pad2(date.getHours()), // 00 01 ... 22 23 + m: date => date.getMinutes(), // 0 1 ... 58 59 + mm: date => pad2(date.getMinutes()), // 00 01 ... 58 59 + s: date => date.getSeconds(), // 0 1 ... 58 59 + ss: date => pad2(date.getSeconds()), // 00 01 ... 58 59 + S: date => `${+date}`.slice(-3, -2), // fractional second 0 1 ... 8 9 + SS: date => `${+date}`.slice(-3, -1), // fractional second 00 01 ... 98 99 + SSS: date => `${+date}`.slice(-3), // fractional second 000 001 ... 998 999 + ZZ: date => { // -0700 -0600 ... +0600 +0700 + const tz = date.getTimezoneOffset(); + const tza = Math.abs(tz); + return `${tz < 0 ? '-' : '+'}${pad2(Math.floor(tza / 60))}${pad2(Math.floor(tza % 60))}`; + }, +}; + +let re; + +export function formatDate(tpl, date = new Date()) { + if (!re) { + re = new RegExp(`${ + // moment.js escaping for [literal text] + /\[([^[\]]*)]/.source + }|${ + // Matching longest first to allow omitting separators e.g. HHMM + Object.keys(DATE_FMT).sort((a, b) => b.length - a.length).join('|') + }`, 'g'); + } + return tpl.replace(re, (s, literal) => ( + hasOwnProperty(DATE_FMT, s) + ? DATE_FMT[s](date) + : literal ?? s + )); +} diff --git a/src/common/download.js b/src/common/download.js index 65accf8276..40c592fa30 100644 --- a/src/common/download.js +++ b/src/common/download.js @@ -1,10 +1,29 @@ -import { makePause } from '#/common'; +import { makePause } from '@/common'; +import { addPublicCommands } from '@/background/utils'; -export function downloadBlob(blob, name) { - const url = URL.createObjectURL(blob); +let chain = Promise.resolve(); + +addPublicCommands({ + DownloadBlob(args) { + downloadBlob(...args); + }, +}); + +/** + * @param {Blob|string} what + * @param {string} name + * @param {boolean} force + */ +export function downloadBlob(what, name, force) { + // Frequent downloads are ignored in Chrome and possibly other browsers + if (!force) { + chain = chain.then(() => (downloadBlob(what, name, true), makePause(150))); + return; + } + const url = isObject(what) ? URL.createObjectURL(what) : what; const a = document.createElement('a'); a.href = url; a.download = name || ''; - a.dispatchEvent(new MouseEvent('click')); - makePause(3000).then(() => URL.revokeObjectURL(url)); + a.click(); + if (isObject(what)) makePause(3000).then(() => URL.revokeObjectURL(url)); } diff --git a/src/common/handlers.js b/src/common/handlers.js index 8c964ad146..2cac0dd9bc 100644 --- a/src/common/handlers.js +++ b/src/common/handlers.js @@ -1,7 +1,11 @@ -import { browser } from '#/common/consts'; +import { showUnhandledError } from '@/common/ui'; import options from './options'; const handlers = { + __proto__: null, + Reload(delay) { + setTimeout(() => location.reload(), delay); + }, UpdateOptions(data) { options.update(data); }, @@ -9,7 +13,12 @@ const handlers = { browser.runtime.onMessage.addListener((res, src) => { const handle = handlers[res.cmd]; - if (handle) handle(res.data, src); + if (handle) { + src.url = res.url || src.url; // MessageSender.url doesn't change on soft navigation + res = handle(res.data, src); + res?.catch?.(showUnhandledError); + return res; + } }); export default handlers; diff --git a/src/common/index.js b/src/common/index.js index 31ac998ec5..4cb3ad84fb 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -1,146 +1,158 @@ -import { browser } from '#/common/consts'; +// SAFETY WARNING! Exports used by `injected` must make ::safe() calls and use __proto__:null + +import { browser } from './consts'; import { deepCopy } from './object'; -import { noop } from './util'; export { normalizeKeys } from './object'; +export * from './script'; +export * from './string'; export * from './util'; -export const defaultImage = '/public/images/icon128.png'; - -export function initHooks() { - const hooks = []; - - function fire(data) { - hooks.slice().forEach((cb) => { - cb(data); - }); +if (process.env.DEV && process.env.IS_INJECTED !== 'injected-web') { + const get = () => { + throw 'Do not use `for-of` with Map/Set. Use forEach or for-of with a [...copy]' + + '\n(not supported due to our config of @babel/plugin-transform-for-of).'; + }; + for (const obj of [Map, Set, WeakMap, WeakSet]) { + Object.defineProperty(obj.prototype, 'length', { get, configurable: true }); } +} - function hook(callback) { - hooks.push(callback); - return () => { - const i = hooks.indexOf(callback); - if (i >= 0) hooks.splice(i, 1); - }; - } +export const ignoreChromeErrors = () => chrome.runtime.lastError; +export const browserWindows = !process.env.IS_INJECTED && browser.windows; +export const defaultImage = !process.env.IS_INJECTED && `${ICON_PREFIX}128.png`; +/** @return {'0' | '1' | ''} treating source as abstract truthy/falsy to ensure consistent result */ +const PORT_ERROR_RE = /(Receiving end does not exist)|The message port closed before|moved into back\/forward cache|$/; - return { hook, fire }; +export function initHooks() { + const hooks = new Set(); + return { + hook(cb) { + hooks.add(cb); + return () => hooks.delete(cb); + }, + fire(...data) { + // Set#forEach correctly iterates the remainder even if current callback unhooks itself + hooks.forEach(cb => cb(...data)); + }, + }; } /** * @param {string} cmd * @param data - * @param {{retry?: boolean, ignoreError?: boolean}} [options] + * @param {{retry?: boolean}} [options] * @return {Promise} */ export function sendCmd(cmd, data, options) { + // Firefox+Vue3 bug workaround for "Proxy object could not be cloned" + if (!process.env.IS_INJECTED && IS_FIREFOX && window._bg !== 1 && isObject(data)) { + data = deepCopy(data); + } return sendMessage({ cmd, data }, options); } -export function sendCmdDirectly(cmd, data, options) { - const bg = browser.extension.getBackgroundPage?.(); - return bg && bg !== window && bg.deepCopy - ? bg.handleCommandMessage(bg.deepCopy({ cmd, data })).then(deepCopy) - : sendCmd(cmd, data, options); +// These need `src` parameter so we'll use sendCmd for them. We could have forged `src` via +// browser.tabs.getCurrent but there's no need as they normally use only a tiny amount of data. +const COMMANDS_WITH_SRC = [ + 'ConfirmInstall', + 'Notification', + 'TabClose', + 'TabFocus', + 'TabOpen', +/* + These are used only by content scripts where sendCmdDirectly can't be used anyway + 'GetInjected', + 'GetRequestId', + 'HttpRequest', + 'InjectionFeedback', + 'SetPopup', +*/ +]; +export const getBgPage = () => browser.extension.getBackgroundPage?.(); + +/** + * Sends the command+data directly so it's synchronous and faster than sendCmd thanks to deepCopy. + * WARNING! Make sure `cmd` handler doesn't use `src` or `cmd` is listed in COMMANDS_WITH_SRC. + */ +export function sendCmdDirectly(cmd, data, options, fakeSrc) { + const bg = !COMMANDS_WITH_SRC.includes(cmd) && getBgPage(); + const bgCopy = bg && bg !== window && bg.deepCopy; + if (!bgCopy) { + return sendCmd(cmd, data, options); + } + if (fakeSrc) { + fakeSrc = bgCopy(fakeSrc); + fakeSrc.fake = true; + } + return bg.handleCommandMessage(bgCopy({ cmd, data }), fakeSrc).then(deepCopy); } /** * @param {number} tabId * @param {string} cmd * @param data - * @param {{frameId?: number}} [options] + * @param {VMMessageTargetFrame} [options] * @return {Promise} */ export function sendTabCmd(tabId, cmd, data, options) { - return browser.tabs.sendMessage(tabId, { cmd, data }, options).catch(noop); + return browser.tabs.sendMessage(tabId, { cmd, data }, options).catch(ignoreNoReceiver); } -// ignoreError is always `true` when sending from the background script because it's a broadcast -export function sendMessage(payload, { retry, ignoreError } = {}) { +// Used by `injected` +export function sendMessage(payload, { retry } = {}) { if (retry) return sendMessageRetry(payload); let promise = browser.runtime.sendMessage(payload); - if (ignoreError || window === browser.extension.getBackgroundPage?.()) { - promise = promise.catch(noop); + // Ignoring errors when sending from the extension script because it's a broadcast + if (!process.env.IS_INJECTED) { + promise = promise.catch(ignoreNoReceiver); } return promise; } /** + * Used by `injected` * The active tab page and its [content] scripts load before the extension's * persistent background script when Chrome starts with a URL via command line * or when configured to restore the session, https://crbug.com/314686 */ -export async function sendMessageRetry(payload, retries = 10) { - let pauseDuration = 10; - for (; retries > 0; retries -= 1) { +export async function sendMessageRetry(payload, maxDuration = 10e3) { + for (let start = performance.now(); performance.now() - start < maxDuration;) { try { const data = await sendMessage(payload); - if (data) return data; + if (data !== undefined) { + return data; + } } catch (e) { - if (typeof e === 'string') throw e; // not a connection error which is an object + if (!PORT_ERROR_RE.exec(e)[1]) { + throw e; + } } - await makePause(pauseDuration); - pauseDuration *= 2; + // Not using setTimeout which may be cleared by the web page + await browser.storage.local.get(VIOLENTMONKEY); } - throw new Error('Violentmonkey cannot connect to the background page.'); -} - -export function leftpad(input, length, pad = '0') { - let num = input.toString(); - while (num.length < length) num = `${pad}${num}`; - return num; + throw new Error(VIOLENTMONKEY + ' cannot connect to the background page.'); } -/** - * Get locale attributes such as `@name:zh-CN` - */ -export function getLocaleString(meta, key) { - const localeMeta = navigator.languages - // Use `lang.toLowerCase()` since v2.6.5 - .map(lang => meta[`${key}:${lang}`] || meta[`${key}:${lang.toLowerCase()}`]) - .find(Boolean); - return localeMeta || meta[key] || ''; -} - -export function getScriptName(script) { - return script.custom.name || getLocaleString(script.meta, 'name') || `#${script.props.id}`; -} - -export function getFullUrl(url, base) { - const obj = new URL(url, base); - // Use protocol whitelist to filter URLs - if (![ - 'http:', - 'https:', - 'ftp:', - 'data:', - ].includes(obj.protocol)) obj.protocol = 'http:'; - return obj.href; -} - -export function isRemote(url) { - return url && !(/^(file:|data:|https?:\/\/localhost[:/]|http:\/\/127\.0\.0\.1[:/])/.test(url)); -} - -export function encodeFilename(name) { - // `escape` generated URI has % in it - return name.replace(/[-\\/:*?"<>|%\s]/g, (m) => { - let code = m.charCodeAt(0).toString(16); - if (code.length < 2) code = `0${code}`; - return `-${code}`; - }); -} - -export function decodeFilename(filename) { - return filename.replace(/-([0-9a-f]{2})/g, (_m, g) => String.fromCharCode(parseInt(g, 16))); +export function ignoreNoReceiver(err) { + if (!PORT_ERROR_RE.exec(err)[0]) { + return Promise.reject(err); + } } -export async function getActiveTab() { - const [tab] = await browser.tabs.query({ - active: true, - windowId: -2, // chrome.windows.WINDOW_ID_CURRENT works when debugging the popup in devtools - }); - return tab; +export async function getActiveTab(windowId = -2 /*chrome.windows.WINDOW_ID_CURRENT*/) { + return ( + await browser.tabs.query({ + active: true, + [kWindowId]: windowId, + }) + )[0] || browserWindows && ( + // Chrome bug workaround when an undocked devtools window is focused + await browser.tabs.query({ + active: true, + [kWindowId]: (await browserWindows.getCurrent()).id, + }) + )[0]; } export function makePause(ms) { @@ -148,7 +160,3 @@ export function makePause(ms) { ? Promise.resolve() : new Promise(resolve => setTimeout(resolve, ms)); } - -export function trueJoin(separator) { - return this.filter(Boolean).join(separator); -} diff --git a/src/common/keyboard.js b/src/common/keyboard.js index e4ec82987d..7c0404d5b2 100644 --- a/src/common/keyboard.js +++ b/src/common/keyboard.js @@ -1,4 +1,5 @@ import { KeyboardService } from '@violentmonkey/shortcut'; +import { getActiveElement } from '@/common/ui'; export * from '@violentmonkey/shortcut'; @@ -6,8 +7,8 @@ export const keyboardService = new KeyboardService(); bindKeys(); -export function isInput(el) { - return ['input', 'textarea'].includes(el?.tagName?.toLowerCase()); +export function isInput({ localName: n } = {}) { + return n === 'input' || n === 'button' || n === 'select' || n === 'textarea'; } function handleFocus(e) { @@ -27,10 +28,6 @@ function handleBlur(e) { } } -function handleEscape() { - document.activeElement.blur(); -} - export function toggleTip(el) { const event = new CustomEvent('tiptoggle', { bubbles: true, @@ -39,21 +36,12 @@ export function toggleTip(el) { } function bindKeys() { - document.addEventListener('focus', handleFocus, true); - document.addEventListener('blur', handleBlur, true); - keyboardService.register('escape', handleEscape); - keyboardService.register('c-[', handleEscape); + addEventListener('focus', handleFocus, true); + addEventListener('blur', handleBlur, true); keyboardService.register('enter', () => { - const { activeElement } = document; - activeElement.click(); - }, { - condition: '!inputFocus', - }); - keyboardService.register('?', () => { - toggleTip(document.activeElement); + getActiveElement().click(); }, { condition: '!inputFocus', - caseSensitive: true, }); } @@ -62,13 +50,13 @@ function bindKeys() { * Ref: https://stackoverflow.com/a/11713537/4238335 */ export function handleTabNavigation(dir) { - const els = Array.from(document.querySelectorAll('[tabindex="0"],a[href],button,input,select,textarea')) - .filter(el => { + const els = document.querySelectorAll('[tabindex="0"],a[href],button,input,select,textarea') + ::[].filter(el => { if (el.tabIndex < 0) return false; const rect = el.getBoundingClientRect(); return rect.width > 0 && rect.height > 0; }); - let index = els.indexOf(document.activeElement); + let index = els.indexOf(getActiveElement()); index = (index + dir + els.length) % els.length; els[index].focus(); } diff --git a/src/common/limit-concurrency.js b/src/common/limit-concurrency.js new file mode 100644 index 0000000000..f825b0a07a --- /dev/null +++ b/src/common/limit-concurrency.js @@ -0,0 +1,45 @@ +import { makePause } from '@/common/index'; + +/** + * @param {function} fn + * @param {number} max + * @param {number} diffKeyDelay + * @param {number} sameKeyDelay + * @param {function(...args: any[]): string} getKey + * @return {function(...args: any[]): Promise} + */ +function limitConcurrency(fn, max, diffKeyDelay, sameKeyDelay, getKey) { + const keyPromise = {}; + const keyTime = {}; + const pool = new Set(); + const maxDelay = Math.max(diffKeyDelay, sameKeyDelay); + let lastTime, lastKey; + return async function limiter(...args) { + let resolve, t; + const key = getKey(...args); + const old = keyPromise[key]; + const promise = keyPromise[key] = new Promise(cb => { resolve = cb; }).catch(console.warn); + if (old) await old; + // Looping because the oldest awaiting instance will immediately add to `pool` + while (pool.size === max) await Promise.race(pool); + pool.add(promise); + if (key === lastKey) { + t = keyTime[key]; + t = maxDelay - (t ? performance.now() - t : 0); + } else if (lastTime) { + t = diffKeyDelay - (performance.now() - lastTime); + } + if (t > 0) await makePause(t); + try { + lastKey = key; + return await fn(...args); + } finally { + pool.delete(promise); + if (keyPromise[key] === promise) delete keyPromise[key]; + lastTime = keyTime[key] = performance.now(); + resolve(); + } + }; +} + +export default limitConcurrency; diff --git a/src/common/load-script-icon.js b/src/common/load-script-icon.js index ff7bb7b2c2..dddae23bd4 100644 --- a/src/common/load-script-icon.js +++ b/src/common/load-script-icon.js @@ -1,24 +1,57 @@ -import { sendCmdDirectly } from '#/common/index'; +import { isDataUri, isValidHttpUrl, noop, sendCmdDirectly } from '@/common'; +// TODO: convert this into a component tag e.g. const KEY = 'safeIcon'; +const KEY_DEFAULT = 'noIcon'; /** * Sets script's safeIcon property after the image is successfully loaded * @param {VMScript} script - * @param {Object} [cache] + * @param {{cache?:{}, isHiDPI?:boolean}} [store] + * @param {boolean} [showDefault] */ -export async function loadScriptIcon(script, cache = {}) { - const { icon } = script.meta; - const url = script.custom?.pathMap?.[icon] || icon; +export async function loadScriptIcon(script, store, showDefault) { + let def; + const { icon: customIcon, pathMap } = script.custom || {}; + const icon = customIcon || script.meta.icon; + const { cache = {}, isHiDPI } = store || {}; + const url = pathMap?.[icon] || icon || showDefault && ( + def = `${ICON_PREFIX}${isHiDPI && 128 || (script.config.removed ? 32 : 38)}.png` + ); if (!url || url !== script[KEY]) { + // exposing scripts with no icon for user's CustomCSS + script[KEY_DEFAULT] = def ? '' : null; // creates an observable property so Vue will see the change after `await` - script[KEY] = null; + if (!(KEY in script)) { + script[KEY] = null; + } if (url) { script[KEY] = cache[url] - || url.startsWith('data:') && url - || await sendCmdDirectly('GetImageData', url) + || isDataUri(url) && url + || isHiDPI && def // Using our big icon directly as its data URI is rendered slower + || (def || isValidHttpUrl(url)) + && (cache[url] = await sendCmdDirectly('GetImageData', url).catch(noop)) || null; } } return script[KEY]; } + +/** + * Sets script's safeIcon property after the image is successfully loaded + * @param {{}} cmdOpts + * @param {{cache?:{}}} store + */ +export async function loadCommandIcon(cmdOpts, store) { + const { icon } = cmdOpts; + const cache = store.cache ??= {}; + if (icon && !(KEY in cmdOpts)) { + cmdOpts[KEY] = null; // creating an observable property + let url = cache[icon] || isDataUri(icon) && icon; + if (!url && isValidHttpUrl(icon)) { + url = cache[icon] = sendCmdDirectly('GetImageData', icon).catch(noop); + } + if (isObject(url)) url = await url; + cmdOpts[KEY] = cache[icon] = url || null; + } +} diff --git a/src/common/object.js b/src/common/object.js index 52a66b9d8a..0d84e06928 100644 --- a/src/common/object.js +++ b/src/common/object.js @@ -1,12 +1,5 @@ -export const { - assign, - defineProperty, - getOwnPropertyDescriptor: describeProperty, - entries: objectEntries, - keys: objectKeys, - values: objectValues, -} = Object; -const { forEach, reduce } = Array.prototype; +/** @type {boolean} */ +let deepDiff; export function normalizeKeys(key) { if (key == null) return []; @@ -14,88 +7,90 @@ export function normalizeKeys(key) { return `${key}`.split('.').filter(Boolean); } -export function objectGet(obj, rawKey, def) { - const keys = normalizeKeys(rawKey); - let res = obj; - keys.every((key) => { - if (res && typeof res === 'object' && (key in res)) { - res = res[key]; - return true; - } - res = def; - return false; - }); - return res; -} - -export function objectSet(obj, rawKey, val) { - const keys = normalizeKeys(rawKey); - if (!keys.length) return val; - const root = obj || {}; - let sub = root; - const lastKey = keys.pop(); - keys.forEach((key) => { - sub = sub[key] || (sub[key] = {}); - }); - if (typeof val === 'undefined') { - delete sub[lastKey]; - } else { - sub[lastKey] = val; +export function objectGet(obj, rawKey) { + for (const key of normalizeKeys(rawKey)) { + if (!obj || typeof obj !== 'object') break; + obj = obj[key]; } - return root; + return obj; } -export function objectPurify(obj) { - // Remove keys with undefined values - if (Array.isArray(obj)) { - obj.forEach(objectPurify); - } else if (obj && typeof obj === 'object') { - obj::forEachEntry(([key, value]) => { - if (typeof value === 'undefined') delete obj[key]; - else objectPurify(value); - }); +/** + * @param {Object} [obj = {}] + * @param {string|string[]} [rawKey] + * @param {?} [val] - if `undefined` or omitted the value is deleted + * @param {boolean} [retParent] + * @return {Object} the original object or the parent of `val` if retParent is set + */ +export function objectSet(obj, rawKey, val, retParent) { + rawKey = normalizeKeys(rawKey); + let res = obj || {}; + let key; + for (let i = 0; (key = rawKey[i], i < rawKey.length - 1); i += 1) { + res = res[key] || (res[key] = {}); } - return obj; + if (val === undefined) { + delete res[key]; + } else { + res[key] = val; + } + return retParent ? res : obj; } +/** + * @param {{}} obj + * @param {string[]} keys + * @param {function(value,key):?} [transform] + * @returns {{}} + */ export function objectPick(obj, keys, transform) { - return keys::reduce((res, key) => { + const res = {}; + for (const key of keys) { let value = obj?.[key]; - if (transform) value = transform(value); - if (value != null) res[key] = value; - return res; - }, {}); + if (transform) value = transform(value, key); + if (value !== undefined) res[key] = value; + } + return res; } -// invoked as obj::mapEntry(([key, value], i, allEntries) => transformedValue) -export function mapEntry(func) { - return objectEntries(this)::reduce((res, entry, i, allEntries) => { - res[entry[0]] = func(entry, i, allEntries); - return res; - }, {}); +/** + * @param {function} [fnValue] - (value, newKey, obj) => newValue + * @param {function} [fnKey] - (key, val, obj) => newKey (if newKey is falsy the key is skipped) + * @param {Object} [thisObj] - passed as `this` to both functions + * @return {Object} + */ +export function mapEntry(fnValue, fnKey, thisObj) { + const res = {}; + for (let key of Object.keys(this)) { + const val = this[key]; + if (!fnKey || (key = thisObj::fnKey(key, val, this))) { + res[key] = fnValue ? thisObj::fnValue(val, key, this) : val; + } + } + return res; } // invoked as obj::forEachEntry(([key, value], i, allEntries) => {}) -export function forEachEntry(func) { - if (this) objectEntries(this)::forEach(func); +export function forEachEntry(func, thisObj) { + if (this) Object.entries(this).forEach(func, thisObj); } // invoked as obj::forEachKey(key => {}, i, allKeys) -export function forEachKey(func) { - if (this) objectKeys(this)::forEach(func); +export function forEachKey(func, thisObj) { + if (this) Object.keys(this).forEach(func, thisObj); } // invoked as obj::forEachValue(value => {}, i, allValues) -export function forEachValue(func) { - if (this) objectValues(this)::forEach(func); +export function forEachValue(func, thisObj) { + if (this) Object.values(this).forEach(func, thisObj); } -// Needed for Firefox's browser.storage API which fails on Vue observables export function deepCopy(src) { - return src && ( - Array.isArray(src) && src.map(deepCopy) - || typeof src === 'object' && src::mapEntry(([, val]) => deepCopy(val)) - ) || src; + if (!src || typeof src !== 'object') return src; + // Using a literal [] instead of `src.map(deepCopy)` to avoid src `window` leaking. + // Using `concat` instead of `for` loop to preserve holes in the array. + if (Array.isArray(src)) return [].concat(src).map(deepCopy); + return src::mapEntry(deepCopy); } // Simplified deep equality checker @@ -104,13 +99,88 @@ export function deepEqual(a, b) { if (!a || !b || typeof a !== typeof b || typeof a !== 'object') { res = a === b; } else if (Array.isArray(a)) { - res = a.length === b.length - && a.every((item, i) => deepEqual(item, b[i])); + res = a.length === b.length && a.every((item, i) => deepEqual(item, b[i])); } else { const keysA = Object.keys(a); - const keysB = Object.keys(b); - res = keysA.length === keysB.length - && keysA.every(key => keysB.includes(key) && deepEqual(a[key], b[key])); + /* Not checking hasOwnProperty because 1) we only use own properties and + * 2) this can be slow for a large value storage that has thousands of keys */ + res = keysA.length === Object.keys(b).length + && keysA.every(key => deepEqual(a[key], b[key])); } return res; } + +/** @return {?} `undefined` if equal */ +export function deepCopyDiff(src, sample) { + if (src === sample) return; + if (!src || typeof src !== 'object') return src; + if (!sample || typeof sample !== 'object') return deepCopy(src); + deepDiff = false; + src = (Array.isArray(src) ? deepCopyDiffArrays : deepCopyDiffObjects)(src, sample); + if (deepDiff) return src; +} + +function deepCopyDiffArrays(src, sample) { + const res = []; + if (src.length !== sample.length) { + deepDiff = true; + } + for (let i = 0, a, b; i < src.length; i++) { + a = src[i]; + b = sample[i]; + if (a && typeof a === 'object') { + if (b && typeof b === 'object') { + a = (Array.isArray(a) ? deepCopyDiffArrays : deepCopyDiffObjects)(a, b); + } else { + a = deepCopy(a); + deepDiff = true; + } + } else if (a !== b) { + deepDiff = true; + } + res[i] = a; + } + return res; +} + +function deepCopyDiffObjects(src, sample) { + const res = {}; + for (const key in sample) { + if (!hasOwnProperty(src, key)) { + deepDiff = true; + break; + } + } + for (const key in src) { + /* Not using Object.keys and not checking hasOwnProperty because we only use own properties, + * and this can be very slow for a large value storage that has thousands of keys */ + let a = src[key]; + let b = sample[key]; + if (a && typeof a === 'object') { + if (b && typeof b === 'object') { + a = (Array.isArray(a) ? deepCopyDiffArrays : deepCopyDiffObjects)(a, b); + } else { + a = deepCopy(a); + deepDiff = true; + } + } else if (a !== b) { + deepDiff = true; + } + res[key] = a; + } + return res; +} + +export function deepSize(val) { + if (val === undefined) return 0; + if (val === true || val == null) return 4; + if (val === false) return 5; + if (typeof val === 'string') return val.length + 2; // not counting escapes for \n\r\t and so on + if (typeof val !== 'object') return `${val}`.length; // number and whatever + if (Array.isArray(val)) return val.reduce((sum, v) => sum + 1 + deepSize(v), 2); + return Object.keys(val).reduce((sum, k) => sum + k.length + 4 + deepSize(val[k]), 2); +} + +export function nest(obj, key) { + return obj[key] || (obj[key] = {}); +} diff --git a/src/common/options-defaults.js b/src/common/options-defaults.js index 99b3bf7cb6..0c5634681a 100644 --- a/src/common/options-defaults.js +++ b/src/common/options-defaults.js @@ -1,63 +1,97 @@ -import { INJECT_AUTO } from './consts'; +import { BLACKLIST, BLACKLIST_NET, FILE_GLOB_ALL } from '@/common/consts'; + +export const kAutocompleteOnTyping = 'autocompleteOnTyping'; +export const kEditAsString = 'editAsString'; +export const kFiltersPopup = 'filtersPopup'; +export const kKillTrailingSpaceOnSave = 'killTrailingSpaceOnSave'; +export const kPopupWidth = 'popupWidth'; +export const kShowTrailingSpace = 'showTrailingSpace'; +export const kScriptTemplate = 'scriptTemplate'; +export const kUpdateEnabledScriptsOnly = 'updateEnabledScriptsOnly'; +export const kValueEditor = 'valueEditor'; +const defaultsEditorCommon = { + [kAutocompleteOnTyping]: 100, + lineWrapping: false, + indentWithTabs: false, + indentUnit: 2, + tabSize: 2, + undoDepth: 500, +}; +export const defaultsEditor = { + [kKillTrailingSpaceOnSave]: true, + [kShowTrailingSpace]: true, + ...defaultsEditorCommon, +}; export default { - isApplied: true, + [IS_APPLIED]: true, + [BLACKLIST]: FILE_GLOB_ALL, + [BLACKLIST_NET]: FILE_GLOB_ALL, + [kPopupWidth]: 320, + [kUpdateEnabledScriptsOnly]: true, autoUpdate: 1, // days, 0 = disable // ignoreGrant: false, lastUpdate: 0, lastModified: 0, - /** @type 'unique' | 'total' | '' */ + /** @type {VMBadgeMode} */ showBadge: 'unique', + badgeColor: '#880088', + badgeColorBlocked: '#888888', exportValues: true, - expose: { // use percent-encoding for '.' + exportNameTemplate: '[violentmonkey]_YYYY-MM-DD_HH.mm.ss', + [EXPOSE]: { // use percent-encoding for '.' 'greasyfork%2Eorg': true, 'sleazyfork%2Eorg': false, }, closeAfterInstall: false, + editAfterInstall: false, + helpForLocalFile: true, trackLocalFile: false, autoReload: false, features: null, - blacklist: null, syncScriptStatus: true, + syncAutomatically: true, sync: null, - customCSS: null, + customCSS: '', importScriptData: true, importSettings: true, notifyUpdates: false, notifyUpdatesGlobal: false, // `true` ignores script.config.notifyUpdates version: null, - /** @type 'auto' | 'page' | 'content' */ - defaultInjectInto: INJECT_AUTO, + /** @type {VMScriptInjectInto} */ + defaultInjectInto: AUTO, + ffInject: true, + xhrInject: false, filters: { - /** @type 'name' | 'code' | 'all' */ - searchScope: 'name', - /** @type 'exec' | 'alpha' | 'update' */ + /** @type {boolean} */ + showOrder: false, + /** @type {boolean} */ + showVisit: false, + /** @type {'exec'|'exec-' | 'alpha'|'alpha-' | 'update'|'update-' | 'visit'|'visit-'} */ sort: 'exec', - /** @type boolean */ + /** @type {boolean} */ viewSingleColumn: false, - /** @type boolean */ + /** @type {boolean} */ viewTable: false, }, - filtersPopup: { - /** @type 'exec' | 'alpha' */ + [kFiltersPopup]: { + /** @type {'exec' | 'alpha'} */ sort: 'exec', enabledFirst: false, - hideDisabled: false, - }, - editor: { - lineWrapping: false, - indentWithTabs: false, - indentUnit: 2, - tabSize: 2, - undoDepth: 200, + groupRunAt: true, + /** @type {'' | 'hide' | 'group'} where '' = show */ + hideDisabled: '', }, + editor: defaultsEditor, + editorTheme: '', + editorThemeName: null, editorWindow: false, // whether popup opens editor in a new window editorWindowPos: {}, // { left, top, width, height } editorWindowSimple: true, // whether to open a simplified popup or a normal browser window - scriptTemplate: `\ + [kScriptTemplate]: `\ // ==UserScript== // @name New script {{name}} -// @namespace Violentmonkey Scripts +// @namespace ${VIOLENTMONKEY} Scripts // @match {{url}} // @grant none // @version 1.0 @@ -65,7 +99,11 @@ export default { // @description {{date}} // ==/UserScript== `, - // Enables automatic updates to the default template with new versions of VM - /** @type {?Boolean} this must be |null| for template-hook.js upgrade routine */ - scriptTemplateEdited: null, + showAdvanced: true, + [kValueEditor]: { + ...defaultsEditorCommon, + [kEditAsString]: true, + }, + /** @type {'' | 'dark' | 'light'} */ + uiTheme: '', }; diff --git a/src/common/options.js b/src/common/options.js index 78da2c6332..b27cf9bd79 100644 --- a/src/common/options.js +++ b/src/common/options.js @@ -1,40 +1,34 @@ -import defaults from '#/common/options-defaults'; +import defaults from '@/common/options-defaults'; import { initHooks, sendCmdDirectly } from '.'; import { forEachEntry, objectGet, objectSet } from './object'; let options = {}; -const hooks = initHooks(); +const { hook, fire } = initHooks(); const ready = sendCmdDirectly('GetAllOptions', null, { retry: true }) .then((data) => { options = data; - if (data) hooks.fire(data); + if (data) fire(data); }); -function getOption(key) { - return objectGet(options, key) ?? objectGet(defaults, key); -} - -function setOption(key, value) { - // the updated options object will be propagated from the background script after a pause - // so meanwhile the local code should be able to see the new value using options.get() - objectSet(options, key, value); - sendCmdDirectly('SetOptions', { key, value }); -} - -function updateOptions(data) { - // Keys in `data` may be { flattened.like.this: 'foo' } - const expandedData = {}; - data::forEachEntry(([key, value]) => { - objectSet(options, key, value); - objectSet(expandedData, key, value); - }); - hooks.fire(expandedData); -} - export default { ready, - get: getOption, - set: setOption, - update: updateOptions, - hook: hooks.hook, + hook, + get(key) { + return objectGet(options, key) ?? objectGet(defaults, key); + }, + set(key, value) { + // the updated options object will be propagated from the background script after a pause + // so meanwhile the local code should be able to see the new value using options.get() + objectSet(options, key, value); + return sendCmdDirectly('SetOptions', { [key]: value }); + }, + update(data) { + // Keys in `data` may be { flattened.like.this: 'foo' } + const expandedData = {}; + data::forEachEntry(([key, value]) => { + objectSet(options, key, value); + objectSet(expandedData, key, value); + }); + fire(expandedData); + }, }; diff --git a/src/common/router.js b/src/common/router.js index 5f3b07007d..0aeda986df 100644 --- a/src/common/router.js +++ b/src/common/router.js @@ -1,23 +1,10 @@ -import { showConfirmation } from '#/common/ui'; +import { reactive } from 'vue'; +import { loadQuery } from '@/common'; +import { showConfirmation } from '@/common/ui'; import { i18n } from './util'; -function parse(hash) { - const [pathname, search = ''] = hash.split('?'); - const query = search.split('&').reduce((res, seq) => { - if (seq) { - const [key, val] = seq.split('='); - res[decodeURIComponent(key)] = decodeURIComponent(val); - } - return res; - }, {}); - const paths = pathname.split('/'); - return { - hash, pathname, paths, query, - }; -} - const stack = []; -export const route = {}; +export const route = reactive(/** @type {VMRoute} */{}); export const lastRoute = () => stack[stack.length - 1] || {}; updateRoute(); @@ -25,7 +12,20 @@ updateRoute(); function updateRoute(noConfirm) { const hash = window.location.hash.slice(1); if (noConfirm || !route.confirmChange) { - Object.assign(route, parse(hash)); + const [pathname, search = ''] = hash.split('?'); + /** + * @typedef {Object} VMRoute + * @prop {string} hash - entire hash without # e.g. 'path/name?foo=1&bar=2' + * @prop {string} pathname - 'path/name' + * @prop {string[]} paths - ['path', 'name'] + * @prop {StringMap} query - {foo: '1', bar: '2'} + */ + Object.assign(route, { + hash, + pathname, + paths: pathname.split('/'), + query: loadQuery(search), + }); } else if (route.hash !== hash) { // restore the pinned route setRoute(route.hash, false, true); @@ -34,8 +34,8 @@ function updateRoute(noConfirm) { } // popstate should be the first to ensure hashchange listeners see the correct lastRoute -window.addEventListener('popstate', () => stack.pop()); -window.addEventListener('hashchange', () => updateRoute(), false); +addEventListener('popstate', () => stack.pop()); +addEventListener('hashchange', () => updateRoute(), false); export function setRoute(hash, replace, noConfirm) { let hashString = `${hash}`; @@ -51,12 +51,11 @@ export function setRoute(hash, replace, noConfirm) { export function getUnloadSentry(onConfirm, onCancel) { async function confirmPopState(hash) { - try { + if (await showConfirmation(i18n('confirmNotSaved'))) { // popstate cannot be prevented so we pin current `route` and display a confirmation - await showConfirmation(i18n('confirmNotSaved')); setRoute(hash, false, true); onConfirm?.(); - } catch { + } else { onCancel?.(); } } diff --git a/src/common/safe-globals-shared.js b/src/common/safe-globals-shared.js new file mode 100644 index 0000000000..6e4ba99082 --- /dev/null +++ b/src/common/safe-globals-shared.js @@ -0,0 +1,36 @@ +/* eslint-disable no-unused-vars */ + +/** + * This file is used first by the entire `src` including `injected`. + * `global` is used instead of WebPack's polyfill which we disable in webpack.conf.js. + * Not exporting NodeJS built-in globals as this file is imported in the test scripts. + */ + +const global = process.env.TEST ? globalThis : this; // eslint-disable-line no-undef +const { window } = global; // it's unforgeable so we extract it primarily to improve minification +export const VIOLENTMONKEY = 'Violentmonkey'; +export const AUTO = 'auto'; +export const CONTENT = 'content'; +export const ERROR = 'error'; +export const EXPOSE = 'expose'; +export const FORCE_CONTENT = 'forceContent'; +export const IDS = 'ids'; +export const ID_BAD_REALM = -1; +export const ID_INJECTING = 2; +export const INJECT_INTO = 'injectInto'; +export const MORE = 'more'; +export const PAGE = 'page'; +export const RUN_AT = 'runAt'; +export const SCRIPTS = 'scripts'; +export const VALUES = 'values'; +export const kResponse = 'response'; +export const kResponseHeaders = 'responseHeaders'; +export const kResponseText = 'responseText'; +export const kResponseType = 'responseType'; +export const kSessionId = 'sessionId'; +export const kTop = 'top'; +export const kXhrType = 'xhrType'; +export const SKIP_SCRIPTS = 'SkipScripts'; +export const isFunction = val => typeof val === 'function'; +export const isObject = val => val != null && typeof val === 'object'; +export const kFileName = 'fileName'; diff --git a/src/common/safe-globals.js b/src/common/safe-globals.js new file mode 100644 index 0000000000..e253394eec --- /dev/null +++ b/src/common/safe-globals.js @@ -0,0 +1,42 @@ +/* eslint-disable no-unused-vars */ + +/** + * This file is used by entire `src` except `injected`. + * `safeCall` is used by our modified babel-plugin-safe-bind.js. + * Standard globals are extracted for better minification and marginally improved lookup speed. + * Not exporting NodeJS built-in globals as this file is imported in the test scripts. + */ + +const { + Boolean, + Error, + Object, + Promise, + addEventListener, removeEventListener, + chrome, + performance, +} = global; +export const SafePromise = Promise; // alias used by browser.js +export const SafeError = Error; // alias used by browser.js +export const { apply: safeApply } = Reflect; +export const hasOwnProperty = safeApply.call.bind(({}).hasOwnProperty); +export const safeCall = Object.call.bind(Object.call); +export const IS_APPLIED = 'isApplied'; +export const IS_FIREFOX = 'contextualIdentities' in chrome || 'activityLog' in chrome; +export const ROUTE_SCRIPTS = '#' + SCRIPTS; +export const extensionRoot = chrome.runtime.getURL('/'); +export const extensionOrigin = extensionRoot.slice(0, -1); +export const extensionManifest = chrome.runtime.getManifest(); +// Using getURL because in Firefox manifest contains resolved (full) URLs +export const extensionOptionsPage = process.env.TEST ? '' + : chrome.runtime.getURL(extensionManifest.options_ui.page).split('#', 1)[0]; +export const ICON_PREFIX = chrome.runtime.getURL(extensionManifest.icons[16].replace("16.png", "")); +export const TAB_SETTINGS = 'settings'; +export const TAB_ABOUT = 'about'; +export const TAB_RECYCLE = 'recycleBin'; +export const BROWSER_ACTION = 'browser_action'; +export const kDocumentId = 'documentId'; +export const kFrameId = 'frameId'; +export const INJECT = 'inject'; +export const MULTI = 'multi'; +export const kWindowId = 'windowId'; diff --git a/src/common/script.js b/src/common/script.js new file mode 100644 index 0000000000..191fd4b6be --- /dev/null +++ b/src/common/script.js @@ -0,0 +1,98 @@ +import { HOMEPAGE_URL, INFERRED, RUN_AT_RE, SUPPORT_URL } from './consts'; +import { getLocaleString } from './string'; +import { i18n, tryUrl } from './util'; + +/** Will be encoded to avoid splitting the URL in devtools UI */ +const BAD_URL_CHAR = /[#/?]/g; +/** Fullwidth range starts at 0xFF00, normal range starts at space char code 0x20 */ +const replaceWithFullWidthForm = s => String.fromCharCode(s.charCodeAt(0) - 0x20 + 0xFF00); + +/** + * @param {VMScript} script + * @returns {string | undefined} + */ +export function getScriptHome(script) { + let custom, meta; + return (custom = script.custom)[HOMEPAGE_URL] + || (meta = script.meta)[HOMEPAGE_URL] + || script[INFERRED]?.[HOMEPAGE_URL] + || meta.homepage + || meta.website + || meta.source + || custom.from; +} + +/** + * @param {VMScript} script + * @returns {string | undefined} + */ +export function getScriptSupportUrl(script) { + return script.meta[SUPPORT_URL] || script[INFERRED]?.[SUPPORT_URL]; +} + +/** + * @param {VMScript} script + * @returns {string} + */ +export function getScriptIcon(script) { + return script.custom.icon || script.meta.icon; +} + +/** + * @param {VMScript} script + * @returns {string} + */ +export function getScriptName(script) { + return script.custom.name || getLocaleString(script.meta, 'name') + || `#${script.props.id ?? i18n('labelNoName')}`; +} + +/** @returns {VMInjection.RunAt} without "document-" */ +export function getScriptRunAt(script) { + return `${script.custom[RUN_AT] || script.meta[RUN_AT] || ''}`.match(RUN_AT_RE)?.[1] || 'end'; +} + +/** URL that shows the name of the script and opens in devtools sources or in our editor */ +export function getScriptPrettyUrl(script, displayName) { + return `${ + extensionRoot + }${ + // When called from prepareScript, adding a space to group scripts in one block visually + displayName && IS_FIREFOX ? '%20' : '' + }${ + encodeURIComponent((displayName || getScriptName(script)) + .replace(BAD_URL_CHAR, replaceWithFullWidthForm)) + }.user.js#${ + script.props.id + }`; +} + +export function getScriptsTags(scripts) { + const uniq = new Set(); + for (const { custom: { tags } } of scripts) { + if (tags) tags.split(/\s+/).forEach(uniq.add, uniq); + } + return [...uniq].sort(); +} + +/** + * @param {VMScript} script + * @param {Object} [opts] + * @param {boolean} [opts.all] - to return all two urls [checkUrl, downloadUrl] + * @param {boolean} [opts.allowedOnly] - check shouldUpdate + * @param {boolean} [opts.enabledOnly] + * @return {string[] | string} + */ +export function getScriptUpdateUrl(script, { all, allowedOnly, enabledOnly } = {}) { + if ((!allowedOnly || script.config.shouldUpdate) + && (!enabledOnly || script.config.enabled)) { + const { custom, meta } = script; + /* URL in meta may be set to an invalid value to enforce disabling of the automatic updates + * e.g. GreasyFork sets it to `none` when the user installs an old version. + * We'll show such script as non-updatable. */ + const downloadURL = tryUrl(custom.downloadURL || meta.downloadURL || custom.lastInstallURL); + const updateURL = tryUrl(custom.updateURL || meta.updateURL || downloadURL); + const url = downloadURL || updateURL; + if (url) return all ? [downloadURL, updateURL] : url; + } +} diff --git a/src/common/storage.js b/src/common/storage.js deleted file mode 100644 index 3677dfc392..0000000000 --- a/src/common/storage.js +++ /dev/null @@ -1,123 +0,0 @@ -import { browser } from './consts'; -import { buffer2string, ensureArray } from './util'; - -const base = { - prefix: '', - getKey(id) { - return `${this.prefix}${id}`; - }, - getOne(id) { - const key = this.getKey(id); - return browser.storage.local.get(key).then(data => data[key]); - }, - /** - * @param {string[]} ids - * @param {?} def - * @param {function(id:string, val:?):?} transform - * @returns {Promise} - */ - async getMulti(ids, def, transform) { - const data = await browser.storage.local.get(ids.map(this.getKey, this)); - return ids.reduce((res, id) => { - const val = data[this.getKey(id)]; - res[id] = transform ? transform(id, val) : (val || def); - return res; - }, {}); - }, - set(id, value) { - return id - ? browser.storage.local.set({ [this.getKey(id)]: value }) - : Promise.resolve(); - }, - remove(id) { - return id - ? browser.storage.local.remove(this.getKey(id)) - : Promise.resolve(); - }, - removeMulti(ids) { - return ids.length - ? browser.storage.local.remove(ids.map(this.getKey, this)) - : Promise.resolve(); - }, - async dump(data) { - const output = !this.prefix - ? data - : Object.entries(data).reduce((res, [key, value]) => { - res[this.getKey(key)] = value; - return res; - }, {}); - await browser.storage.local.set(output); - return data; - }, -}; - -export default { - - base, - - cache: { - ...base, - prefix: 'cac:', - /** - * @param {Response} response - * @param {boolean} [noJoin] - * @returns {string|string[]} - */ - makeRaw(response, noJoin) { - const type = (response.headers.get('content-type') || '').split(';')[0] || ''; - const body = btoa(buffer2string(response.data)); - return noJoin ? [type, body] : `${type},${body}`; - }, - /** - * @param {string} url - * @param {string} [raw] - raw value in storage.cache - * @returns {?string} - */ - makeDataUri(url, raw) { - if (url.startsWith('data:')) return url; - if (/^(i,|image\/)/.test(raw)) { // workaround for bugs in old VM, see 2e135cf7 - const i = raw.lastIndexOf(','); - const type = raw.startsWith('image/') ? raw.slice(0, i) : 'image/png'; - return `data:${type};base64,${raw.slice(i + 1)}`; - } - return raw; - }, - }, - - code: { - ...base, - prefix: 'code:', - }, - - // last-modified HTTP header value per URL - mod: { - ...base, - prefix: 'mod:', - }, - - require: { - ...base, - prefix: 'req:', - }, - - script: { - ...base, - prefix: 'scr:', - async dump(items) { - items = ensureArray(items).filter(Boolean); - if (!items.length) return; - const data = items.reduce((res, item) => { - res[this.getKey(item.props.id)] = item; - if (this.onDump) this.onDump(item); - return res; - }, {}); - await base.dump(data); - return items; - }, - }, - - value: { - ...base, - prefix: 'val:', - }, -}; diff --git a/src/common/string.js b/src/common/string.js new file mode 100644 index 0000000000..b2be9028b4 --- /dev/null +++ b/src/common/string.js @@ -0,0 +1,108 @@ +import { blob2base64, isDataUri } from './util'; + +export const nullBool2string = v => v ? '1' : v == null ? '' : '0'; + +export function leftpad(input, length, pad = '0') { + let num = input.toString(); + while (num.length < length) num = `${pad}${num}`; + return num; +} + +/** + * @param {string} browserLang Language tags from RFC5646 (`[lang]-[script]-[region]-[variant]`, all parts are optional) + * @param {string} locale ``, `-` + */ +function localeMatch(browserLang, metaLocale) { + const bParts = browserLang.toLowerCase().split('-'); + const mParts = metaLocale.toLowerCase().split('-'); + let bi = 0; + let mi = 0; + while (bi < bParts.length && mi < mParts.length) { + if (bParts[bi] === mParts[mi]) mi += 1; + bi += 1; + } + return mi === mParts.length; +} + +/** + * Get locale attributes such as `@name:zh-CN` + */ +export function getLocaleString(meta, key, languages = navigator.languages) { + // zh, zh-cn, zh-tw + const mls = Object.keys(meta) + .filter(metaKey => metaKey.startsWith(key + ':')) + .map(metaKey => metaKey.slice(key.length + 1)) + .sort((a, b) => b.length - a.length); + let bestLocale; + for (const lang of languages) { + bestLocale = mls.find(ml => localeMatch(lang, ml)); + if (bestLocale) break; + } + return meta[bestLocale ? `${key}:${bestLocale}` : key] || ''; +} + +export function getFullUrl(url, base) { + let obj; + try { + obj = new URL(url, base); + } catch (e) { + return `data:,${e.message} ${url}`; + } + return obj.href; +} + +export function encodeFilename(name) { + // `escape` generated URI has % in it + return name.replace(/[-\\/:*?"<>|%\s]/g, (m) => { + let code = m.charCodeAt(0).toString(16); + if (code.length < 2) code = `0${code}`; + return `-${code}`; + }); +} + +export function decodeFilename(filename) { + return filename.replace(/-([0-9a-f]{2})/g, (_m, g) => String.fromCharCode(parseInt(g, 16))); +} + +export function trueJoin(separator) { + return this.filter(Boolean).join(separator); +} + +/** + * @param {string} raw - raw value in storage.cache + * @param {string} [url] + * @returns {?string} + */ +export function makeDataUri(raw, url) { + if (isDataUri(url)) return url; + if (/^(i,|image\/)/.test(raw)) { // workaround for bugs in old VM, see 2e135cf7 + const i = raw.lastIndexOf(','); + const type = raw.startsWith('image/') ? raw.slice(0, i) : 'image/png'; + return `data:${type};base64,${raw.slice(i + 1)}`; + } + return raw; +} + +/** + * @param {VMReq.Response} response + * @returns {Promise} + */ +export async function makeRaw(response) { + const type = (response.headers.get('content-type') || '').split(';')[0] || ''; + const body = await blob2base64(response.data); + return `${type},${body}`; +} + +export function loadQuery(string) { + const res = {}; + if (string) { + new URLSearchParams(string).forEach((val, key) => { + res[key] = val; + }); + } + return res; +} + +export function dumpQuery(dict) { + return `${new URLSearchParams(dict)}`; +} diff --git a/src/common/tld.js b/src/common/tld.js index 56ffc6a8e4..5c9eb6fd73 100644 --- a/src/common/tld.js +++ b/src/common/tld.js @@ -1,44 +1,10 @@ -import tldjs from 'tldjs'; -// import { fromUserSettings } from 'tldjs'; -// import Trie from 'tldjs/lib/suffix-trie'; -// import { request } from '#/common'; - -// let tldjs; - -// export function initTLD(remote) { -// // TLD rules are too large to be packed, download them at runtime. -// const url = 'https://violentmonkey.top/static/tld-rules.json'; -// const key = 'dat:tldRules'; -// browser.storage.local.get(key) -// .then(({ [key]: tldRules }) => { -// if (tldRules) return tldRules; -// if (!remote) return Promise.reject('ignore TLD'); -// return request(url, { responseType: 'json' }) -// .then(({ data: rules }) => { -// console.info('Downloaded public suffix data'); -// return browser.storage.local.set({ [key]: rules }) -// .then(() => rules); -// }); -// }) -// .then(tldRules => { -// console.info('Initialized TLD'); -// tldjs = fromUserSettings({ rules: Trie.fromJson(tldRules) }); -// }) -// .catch(err => { -// if (process.env.DEBUG) console.error(err); -// console.info('Failed initializing TLD'); -// }); -// } -export function initTLD() {} - -function exportMethod(key) { - return (...args) => tldjs && tldjs[key](...args); -} - -export function isReady() { - return !!tldjs; -} - -export const getDomain = exportMethod('getDomain'); -export const getSubdomain = exportMethod('getSubdomain'); -export const getPublicSuffix = exportMethod('getPublicSuffix'); +import { getDomain as getDomain_, getPublicSuffix as getPublicSuffix_ } from 'tldts'; + +/** + * tldts does not respect the public suffix list by default, but can be opt in manually + * with the option `allowPrivateDomains`. Hoist the `sharedOpts` can also help avoid + * re-creating the object every time. + */ +const sharedOpts = { allowPrivateDomains: true }; +export const getDomain = url => getDomain_(url, sharedOpts); +export const getPublicSuffix = url => getPublicSuffix_(url, sharedOpts); diff --git a/src/common/ua.js b/src/common/ua.js deleted file mode 100644 index 7e41abeb60..0000000000 --- a/src/common/ua.js +++ /dev/null @@ -1,46 +0,0 @@ -import { browser } from './consts'; - -// UA can be overridden by about:config in FF or devtools in Chrome -// so we'll test for window.chrome.app which is only defined in Chrome -// and for browser.runtime.getBrowserInfo in Firefox 51+ - -/** @typedef UA - * @property {Boolean} isChrome - * @property {Boolean | number} isFirefox - boolean initially, Firefox version number when ready - * @property {chrome.runtime.PlatformInfo.arch} arch - * @property {chrome.runtime.PlatformInfo.os} os - * @property {string} browserName - * @property {string} browserVersion - */ -const ua = {}; -export default ua; - -// using non-enumerable properties that won't be sent to content scripts via GetInjected -Object.defineProperties(ua, { - isChrome: { - value: !!window.chrome?.app, - }, - isFirefox: { - // will be replaced with the version number in ready() - value: !!browser.runtime.getBrowserInfo, - configurable: true, - }, - ready: { - value: Promise.all([ - browser.runtime.getPlatformInfo(), - browser.runtime.getBrowserInfo?.(), - ]).then(([{ os, arch }, { name, version } = {}]) => { - Object.assign(ua, { - arch, - os, - browserName: name?.toLowerCase() || 'chrome', - browserVersion: version || navigator.userAgent.match(/Chrom\S+?\/(\S+)|$/)[1], - }); - if (ua.isFirefox) { - Object.defineProperty(ua, 'isFirefox', { - value: parseFloat(version), - }); - } - }), - }, -}); diff --git a/src/common/ui/code-autocomplete.js b/src/common/ui/code-autocomplete.js new file mode 100644 index 0000000000..25b699e39b --- /dev/null +++ b/src/common/ui/code-autocomplete.js @@ -0,0 +1,65 @@ +import CodeMirror from 'codemirror'; +import { defaultsEditor, kAutocompleteOnTyping as ID } from '@/common/options-defaults'; + +const OPTIONS = 'options'; +const STATE = 'state'; +export const HINT_OPTIONS = 'hintOptions'; +const COMPLETE_SINGLE = 'completeSingle'; +const PICKED = 'picked'; +const TIMER = 'timer'; + +// eslint-disable-next-line no-return-assign +const getMyState = ({ [STATE]: state }) => (state[ID] || (state[ID] = {})); + +const delayedComplete = cm => { + const options = cm[OPTIONS]; + const hintOptions = options[HINT_OPTIONS] || (options[HINT_OPTIONS] = {}); + const myState = getMyState(cm); + hintOptions[COMPLETE_SINGLE] = false; + myState[TIMER] = 0; + myState[PICKED] = false; + cm.execCommand('autocomplete'); + setTimeout(() => { + hintOptions[COMPLETE_SINGLE] = true; + }); +}; + +const cancelDelay = myState => { + if (myState[TIMER]) { + clearTimeout(myState[TIMER]); + myState[TIMER] = 0; + } +}; + +const onChanges = (cm, [info]) => { + const myState = getMyState(cm); + const lastTyped = info.text[info.text.length - 1]; + if (cm[STATE].completionActive + || info.origin && !info.origin.includes('input') + || !lastTyped) { + return; + } + if (myState[PICKED]) { + myState[PICKED] = false; + return; + } + if (/[-a-z!]$/i.test(lastTyped)) { + cancelDelay(myState); + myState[TIMER] = setTimeout(delayedComplete, cm[OPTIONS][ID], cm); + } +}; + +const onPicked = cm => { + getMyState(cm)[PICKED] = true; +}; + +CodeMirror.defineOption(ID, defaultsEditor[ID], (cm, value) => { + const myState = getMyState(cm); + const onOff = value ? 'on' : 'off'; + cm[onOff]('changes', onChanges); + cm[onOff]('pick', onPicked); + if (myState && !value) { + cancelDelay(myState); + delete cm[STATE][ID]; + } +}); diff --git a/src/common/ui/code-defaults.js b/src/common/ui/code-defaults.js new file mode 100644 index 0000000000..9076bc04b7 --- /dev/null +++ b/src/common/ui/code-defaults.js @@ -0,0 +1,17 @@ +export default { + continueComments: true, + styleActiveLine: true, + foldGutter: true, + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + theme: 'default', + mode: 'javascript-mixed', + lineNumbers: true, + matchBrackets: true, + autoCloseBrackets: true, + highlightSelectionMatches: true, + keyMap: 'sublime', + /* Limiting the max length to avoid delays while CodeMirror tries to make sense of a long line. + * 100kB is fast enough for the main editor (moreover such long lines are rare in the main script), + * and is big enough to include most of popular minified libraries for the `@resource/@require` viewer. */ + maxDisplayLength: 100_000, +}; diff --git a/src/common/ui/code-js-mixed-mode.js b/src/common/ui/code-js-mixed-mode.js new file mode 100644 index 0000000000..93878dba91 --- /dev/null +++ b/src/common/ui/code-js-mixed-mode.js @@ -0,0 +1,713 @@ +// Javascript mixed mode for CodeMirror +// Distributed under an MIT license +// Mod of the inactive https://github.com/orionlee/codemirror-js-mixed + +import CodeMirror from 'codemirror'; +import 'codemirror/mode/xml/xml'; +import 'codemirror/mode/javascript/javascript'; +import 'codemirror/mode/css/css'; +import 'codemirror/mode/htmlmixed/htmlmixed'; + +CodeMirror.defineMode('javascript-mixed', (config) => { + const STYLE_PASS = 'XXX-PASS'; + const IS_END_BACKTICK_RE = /(^|[^\\])`/y; + const NEXT_QUOTE_RE = { + "'": /.*?'/, + '"': /.*?"/, + '`': /.*?`/, + }; + // Using # to prevent inlining in Terser + const kEnsureProperLocalModeStatePostJsExpr = '#ensureProperLocalModeStatePostJsExpr'; + const kInJsExprInStringTemplate = '#inJsExprInStringTemplate'; + const kIndexOfJsExprStart = '#indexOfJsExprStart'; + const kJsExprDepthInStringTemplate = '#jsExprDepthInStringTemplate'; + const kJsState = '#jsState'; + const kLocalHtmlPlainStringEndPos = '#localHtmlPlainStringEndPos'; + const kLocalMode = '#localMode'; + const kLocalState = '#localState'; + const kMaybeLocalContext = '#maybeLocalContext'; + const kQuoteCharSurroundJsExpr = '#quoteCharSurroundJsExpr'; + const kTokenize = 'tokenize'; + const kTokenizePostJsExpr = '#tokenizePostJsExpr'; + const { StringStream } = CodeMirror; + const cmCopyState = CodeMirror.copyState; + const cmStartState = CodeMirror.startState; + const cmPass = CodeMirror.Pass; + + const jsMode = CodeMirror.getMode(config, { + name: 'javascript', + globalVars: config.globalVars, + }); + const jsTokenQuasi = (() => { + // create a new stream of a non-ending (1st line of a multiline) + // string template to obtain tokenQuasi tokenizer + const dummyStream = new StringStream('`#dummy', 2, {}); + const dummyState = jsMode.startState(); + jsMode.token(dummyStream, dummyState); + return dummyState[kTokenize]; + })(); + + const cssMode = CodeMirror.getMode(config, { name: 'css' }); + + // use htmlmixed to support highlighting css in diff --git a/src/common/ui/favicon.js b/src/common/ui/favicon.js index 207bc35c20..ca37ae54b8 100644 --- a/src/common/ui/favicon.js +++ b/src/common/ui/favicon.js @@ -1,11 +1,8 @@ -import ua from '#/common/ua'; - export const isHiDPI = matchMedia('screen and (min-resolution: 144dpi)').matches; -if (ua.isFirefox) { // Firefox doesn't show favicon - const icons = browser.runtime.getManifest().browser_action.default_icon; +if (IS_FIREFOX) { // Firefox doesn't show favicon const el = document.createElement('link'); el.rel = 'icon'; - el.href = icons[isHiDPI ? 32 : 16]; + el.href = `${ICON_PREFIX}${isHiDPI ? 32 : 16}.png`; document.head.appendChild(el); } diff --git a/src/common/ui/icon.vue b/src/common/ui/icon.vue index 8bc8c19645..f5abebd3a1 100644 --- a/src/common/ui/icon.vue +++ b/src/common/ui/icon.vue @@ -3,10 +3,11 @@ -export default { - props: ['name'], -}; + diff --git a/src/common/ui/index.js b/src/common/ui/index.js index 973fce3c24..eb8a72fc22 100644 --- a/src/common/ui/index.js +++ b/src/common/ui/index.js @@ -1,23 +1,62 @@ -import Modal from 'vueleton/lib/modal/bundle'; -import { i18n } from '#/common/util'; +import { createApp, h } from 'vue'; +import Modal from 'vueleton/lib/modal'; +import { trueJoin } from '@/common'; +import { i18n } from '@/common/util'; import Message from './message'; +import { VM_HOME } from '@/common/consts'; + +/** Showing unexpected errors in UI so that the users can notify us */ +addEventListener(ERROR, e => showUnhandledError(e.error)); +addEventListener('unhandledrejection', e => showUnhandledError(e.reason)); +export function showUnhandledError(err) { + if (!err) return; + const id = 'unhandledError'; + const fontSize = 10; + const el = document.getElementById(id) || document.createElement('textarea'); + const text = el.value = [ + el.value, + isObject(err) + ? `${IS_FIREFOX && err.message || ''}\n${err.stack || ''}` + : `${err}`, + ]::trueJoin('\n\n').trim().split(extensionRoot).join(''); + const height = fontSize * (calcRows(text) + 1) + 'px'; + const parent = document.body || document.documentElement; + el.id = id; + el.readOnly = true; + // using an inline style because we don't know if our CSS is loaded at this stage + el.style.cssText = `\ + position:fixed; + z-index:${1e9}; + left:0; + right:0; + bottom:0; + background:#000; + color:red; + padding: ${fontSize / 2}px; + font-size: ${fontSize}px; + line-height: 1; + box-sizing: border-box; + height: ${height}; + border: none; + resize: none; + `.replace(/;/g, '!important;'); + el.spellcheck = false; + el.onclick = () => el.select(); + parent.style.minHeight = height; + parent.appendChild(el); +} export function showMessage(message) { - const modal = Modal.show(h => h(Message, { - props: { message }, - on: { - dismiss() { - modal.close(); - message.onDismiss?.(); - }, + const modal = Modal.show(() => h(Message, { + message, + onDismiss() { + modal.close(); + message.onDismiss?.(); }, }), { transition: 'in-out', }); - if (message.buttons) { - // TODO: implement proper keyboard navigation, autofocus, and Enter/Esc in Modal module - document.querySelector('.vl-modal button').focus(); - } else { + if (!message.buttons) { const timer = setInterval(() => { if (!document.querySelector('.vl-modal .modal-content:hover')) { clearInterval(timer); @@ -31,34 +70,127 @@ export function showMessage(message) { * @param {string} text - the text to display in the modal * @param {Object} cfg * @param {string | false} [cfg.input=false] if not false, shows a text input with this string - * @param {Object} [cfg.ok] additional props for the Ok button - * @param {Object} [cfg.cancel] additional props for the Cancel button - * @return {Promise} resolves on Ok to `false` or the entered string, rejects otherwise + * @param {?Object|false} [cfg.ok] additional props for the Ok button or `false` to remove it + * @param {?Object|false} [cfg.cancel] same for the Cancel button + * @return {Promise} + * `input` is false: i.e. true on Ok, false otherwise; + * `input` is string: i.e. string on Ok, null otherwise; */ export function showConfirmation(text, { ok, cancel, input = false } = {}) { - return new Promise((resolve, reject) => { + return new Promise(resolve => { + const hasInput = input !== false; + const onCancel = () => resolve(hasInput ? null : false); + const onOk = val => resolve(!hasInput || val); showMessage({ input, text, buttons: [ - { text: i18n('buttonOK'), onClick: resolve, ...ok }, - { text: i18n('buttonCancel'), onClick: reject, ...cancel }, - ], - onBackdropClick: reject, - onDismiss: reject, // Esc key + ok !== false && { text: i18n('buttonOK'), onClick: onOk, ...ok }, + cancel !== false && { text: i18n('buttonCancel'), onClick: onCancel, ...cancel }, + ].filter(Boolean), + onBackdropClick: onCancel, + onDismiss: onCancel, // Esc key }); }); } -export function autofitElementsHeight(elems) { - elems.forEach((el) => { - el.addEventListener('input', autofitSingleElementHeight); - el::autofitSingleElementHeight(); +/** @returns {?number} Number of lines + 1 if the last line is not empty */ +export function calcRows(val) { + return val && ( + val.match(/$/gm).length + + !val.endsWith('\n') + ); +} + +export function render(App, el) { + const app = createApp(App); + Object.assign(app.config.globalProperties, { + i18n, + calcRows, }); + if (!el) { + el = document.createElement('div'); + document.body.append(el); + } + app.mount(el); + return app; +} + +/** + * Focuses the first element with `focusme` attribute or root, which enables keyboard scrolling. + * Not using `autofocus` to avoid warnings in console on page load. + * A child component should use nextTick to change focus, which runs later. + */ +export function focusMe(el) { + el = el.querySelector('[focusme]') || el; + el.tabIndex = -1; + el.focus(); +} + +function vFocusFactory() { + const handle = (el, value, oldValue) => { + if (value === oldValue) return; + if (value == null || value) { + el.tabIndex = -1; + el.focus(); + } + }; + return { + mounted(el, binding) { + handle(el, binding.value, {}); + }, + updated(el, binding) { + handle(el, binding.value, binding.oldValue); + }, + }; } +/** + * Usage: + * + * ```html + * + *
...
+ * + * + *
...
+ * ``` + */ +export const vFocus = vFocusFactory(); +export const isTouch = 'ontouchstart' in document; +export const getActiveElement = () => document.activeElement; +/** @param {MouseEvent|KeyboardEvent} e */ +export const hasKeyModifiers = e => e.shiftKey || e.ctrlKey || e.metaKey || e.altKey; +export const externalEditorInfoUrl = + VM_HOME + 'posts/how-to-edit-scripts-with-your-favorite-editor/'; +export const EXTERNAL_LINK_PROPS = { + target: '_blank', + rel: 'noopener noreferrer', +}; +const { getAsFileSystemHandle } = DataTransferItem.prototype; -/** @this {HTMLTextAreaElement} */ -function autofitSingleElementHeight() { - this.style.height = 0; - this.style.height = `${Math.max(this.scrollHeight, 30) + 16}px`; +if (getAsFileSystemHandle) { + const { find } = []; + /** + * @param {DragEvent} evt + * @return {?DataTransferItem} + */ + const getItem = evt => evt.dataTransfer.items::find(v => v.type === 'text/javascript'); + addEventListener('dragover', evt => { + if (getItem(evt)) evt.preventDefault(); + }, true); + addEventListener('drop', async evt => { + const item = getItem(evt); + if (!item) return; + evt.preventDefault(); + evt.stopPropagation(); + const path = '/confirm/index.html'; + // Some apps provide the file's URL in a text dataTransfer item. + const url = evt.dataTransfer.getData('text'); + const handle = await item::getAsFileSystemHandle(); + const isNewWindow = hasKeyModifiers(evt) || location.pathname !== path; + const wnd = isNewWindow ? window.open(path) : window; + // Transfer the handle to the new window (required in some versions of Chrome) + const structuredClone = isNewWindow && wnd.structuredClone; // Chrome 98+ + (wnd.fsh = structuredClone ? structuredClone(handle) : handle)._url = url; + }, true); } diff --git a/src/common/ui/locale-group.vue b/src/common/ui/locale-group.vue index 3921a2e7b7..d20f9d3e8b 100644 --- a/src/common/ui/locale-group.vue +++ b/src/common/ui/locale-group.vue @@ -1,22 +1,15 @@ - + + - diff --git a/src/common/ui/message.vue b/src/common/ui/message.vue index 86b933328b..836aa3ee49 100644 --- a/src/common/ui/message.vue +++ b/src/common/ui/message.vue @@ -1,15 +1,26 @@ diff --git a/src/common/ui/setting-check.vue b/src/common/ui/setting-check.vue index c4d326db66..2ff4366bc6 100644 --- a/src/common/ui/setting-check.vue +++ b/src/common/ui/setting-check.vue @@ -1,57 +1,56 @@ - diff --git a/src/common/ui/setting-text.vue b/src/common/ui/setting-text.vue index 46dd3dcbf9..8494b74987 100644 --- a/src/common/ui/setting-text.vue +++ b/src/common/ui/setting-text.vue @@ -1,110 +1,164 @@