summaryrefslogtreecommitdiffstats
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/browser/main.js123
-rw-r--r--platform/browser/test.html71
-rw-r--r--platform/chromium/is-webrtc-supported.html9
-rw-r--r--platform/chromium/is-webrtc-supported.js52
-rw-r--r--platform/chromium/manifest.json115
-rw-r--r--platform/chromium/vapi-background-ext.js254
-rw-r--r--platform/chromium/webext.js164
-rw-r--r--platform/common/managed_storage.json73
-rw-r--r--platform/common/vapi-background.js1811
-rw-r--r--platform/common/vapi-client.js251
-rw-r--r--platform/common/vapi-common.js294
-rw-r--r--platform/common/vapi.js89
-rw-r--r--platform/dig/package.json28
-rw-r--r--platform/dig/snfe.js389
-rw-r--r--platform/firefox/manifest.json132
-rw-r--r--platform/firefox/vapi-background-ext.js328
-rw-r--r--platform/firefox/webext.js24
-rw-r--r--platform/mv3/README.md31
-rw-r--r--platform/mv3/chromium/manifest.json46
-rw-r--r--platform/mv3/description/en.md41
-rw-r--r--platform/mv3/description/webstore.ar.txt36
-rw-r--r--platform/mv3/description/webstore.az.txt30
-rw-r--r--platform/mv3/description/webstore.be.txt30
-rw-r--r--platform/mv3/description/webstore.bg.txt30
-rw-r--r--platform/mv3/description/webstore.bn.txt30
-rw-r--r--platform/mv3/description/webstore.br_FR.txt30
-rw-r--r--platform/mv3/description/webstore.bs.txt30
-rw-r--r--platform/mv3/description/webstore.ca.txt32
-rw-r--r--platform/mv3/description/webstore.cs.txt30
-rw-r--r--platform/mv3/description/webstore.cv.txt30
-rw-r--r--platform/mv3/description/webstore.da.txt30
-rw-r--r--platform/mv3/description/webstore.de.txt30
-rw-r--r--platform/mv3/description/webstore.el.txt30
-rw-r--r--platform/mv3/description/webstore.en_GB.txt30
-rw-r--r--platform/mv3/description/webstore.eo.txt30
-rw-r--r--platform/mv3/description/webstore.es.txt30
-rw-r--r--platform/mv3/description/webstore.et.txt30
-rw-r--r--platform/mv3/description/webstore.eu.txt30
-rw-r--r--platform/mv3/description/webstore.fa.txt30
-rw-r--r--platform/mv3/description/webstore.fi.txt30
-rw-r--r--platform/mv3/description/webstore.fil.txt30
-rw-r--r--platform/mv3/description/webstore.fr.txt30
-rw-r--r--platform/mv3/description/webstore.fy.txt30
-rw-r--r--platform/mv3/description/webstore.gl.txt30
-rw-r--r--platform/mv3/description/webstore.gu.txt30
-rw-r--r--platform/mv3/description/webstore.he.txt30
-rw-r--r--platform/mv3/description/webstore.hi.txt30
-rw-r--r--platform/mv3/description/webstore.hr.txt30
-rw-r--r--platform/mv3/description/webstore.hu.txt30
-rw-r--r--platform/mv3/description/webstore.hy.txt34
-rw-r--r--platform/mv3/description/webstore.id.txt30
-rw-r--r--platform/mv3/description/webstore.it.txt30
-rw-r--r--platform/mv3/description/webstore.ja.txt30
-rw-r--r--platform/mv3/description/webstore.ka.txt30
-rw-r--r--platform/mv3/description/webstore.kk.txt30
-rw-r--r--platform/mv3/description/webstore.kn.txt30
-rw-r--r--platform/mv3/description/webstore.ko.txt30
-rw-r--r--platform/mv3/description/webstore.ku.txt30
-rw-r--r--platform/mv3/description/webstore.lt.txt30
-rw-r--r--platform/mv3/description/webstore.lv.txt30
-rw-r--r--platform/mv3/description/webstore.mk.txt30
-rw-r--r--platform/mv3/description/webstore.ml.txt30
-rw-r--r--platform/mv3/description/webstore.mr.txt30
-rw-r--r--platform/mv3/description/webstore.ms.txt30
-rw-r--r--platform/mv3/description/webstore.nb.txt30
-rw-r--r--platform/mv3/description/webstore.nl.txt30
-rw-r--r--platform/mv3/description/webstore.oc.txt30
-rw-r--r--platform/mv3/description/webstore.pa.txt30
-rw-r--r--platform/mv3/description/webstore.pl.txt30
-rw-r--r--platform/mv3/description/webstore.pt_BR.txt30
-rw-r--r--platform/mv3/description/webstore.pt_PT.txt30
-rw-r--r--platform/mv3/description/webstore.ro.txt30
-rw-r--r--platform/mv3/description/webstore.ru.txt30
-rw-r--r--platform/mv3/description/webstore.si.txt30
-rw-r--r--platform/mv3/description/webstore.sk.txt30
-rw-r--r--platform/mv3/description/webstore.sl.txt30
-rw-r--r--platform/mv3/description/webstore.so.txt30
-rw-r--r--platform/mv3/description/webstore.sq.txt30
-rw-r--r--platform/mv3/description/webstore.sr.txt30
-rw-r--r--platform/mv3/description/webstore.sv.txt30
-rw-r--r--platform/mv3/description/webstore.sw.txt30
-rw-r--r--platform/mv3/description/webstore.ta.txt30
-rw-r--r--platform/mv3/description/webstore.te.txt30
-rw-r--r--platform/mv3/description/webstore.th.txt30
-rw-r--r--platform/mv3/description/webstore.tr.txt30
-rw-r--r--platform/mv3/description/webstore.txt30
-rw-r--r--platform/mv3/description/webstore.uk.txt30
-rw-r--r--platform/mv3/description/webstore.ur.txt30
-rw-r--r--platform/mv3/description/webstore.vi.txt30
-rw-r--r--platform/mv3/description/webstore.zh_CN.txt30
-rw-r--r--platform/mv3/description/webstore.zh_TW.txt30
-rw-r--r--platform/mv3/extension/_locales/ar/messages.json158
-rw-r--r--platform/mv3/extension/_locales/az/messages.json158
-rw-r--r--platform/mv3/extension/_locales/be/messages.json158
-rw-r--r--platform/mv3/extension/_locales/bg/messages.json158
-rw-r--r--platform/mv3/extension/_locales/bn/messages.json158
-rw-r--r--platform/mv3/extension/_locales/br_FR/messages.json158
-rw-r--r--platform/mv3/extension/_locales/bs/messages.json158
-rw-r--r--platform/mv3/extension/_locales/ca/messages.json158
-rw-r--r--platform/mv3/extension/_locales/cs/messages.json158
-rw-r--r--platform/mv3/extension/_locales/cv/messages.json158
-rw-r--r--platform/mv3/extension/_locales/da/messages.json158
-rw-r--r--platform/mv3/extension/_locales/de/messages.json158
-rw-r--r--platform/mv3/extension/_locales/el/messages.json158
-rw-r--r--platform/mv3/extension/_locales/en/messages.json158
-rw-r--r--platform/mv3/extension/_locales/en_GB/messages.json158
-rw-r--r--platform/mv3/extension/_locales/eo/messages.json158
-rw-r--r--platform/mv3/extension/_locales/es/messages.json158
-rw-r--r--platform/mv3/extension/_locales/et/messages.json158
-rw-r--r--platform/mv3/extension/_locales/eu/messages.json158
-rw-r--r--platform/mv3/extension/_locales/fa/messages.json158
-rw-r--r--platform/mv3/extension/_locales/fi/messages.json158
-rw-r--r--platform/mv3/extension/_locales/fil/messages.json158
-rw-r--r--platform/mv3/extension/_locales/fr/messages.json158
-rw-r--r--platform/mv3/extension/_locales/fy/messages.json158
-rw-r--r--platform/mv3/extension/_locales/gl/messages.json158
-rw-r--r--platform/mv3/extension/_locales/gu/messages.json158
-rw-r--r--platform/mv3/extension/_locales/he/messages.json158
-rw-r--r--platform/mv3/extension/_locales/hi/messages.json158
-rw-r--r--platform/mv3/extension/_locales/hr/messages.json158
-rw-r--r--platform/mv3/extension/_locales/hu/messages.json158
-rw-r--r--platform/mv3/extension/_locales/hy/messages.json158
-rw-r--r--platform/mv3/extension/_locales/id/messages.json158
-rw-r--r--platform/mv3/extension/_locales/it/messages.json158
-rw-r--r--platform/mv3/extension/_locales/ja/messages.json158
-rw-r--r--platform/mv3/extension/_locales/ka/messages.json158
-rw-r--r--platform/mv3/extension/_locales/kk/messages.json158
-rw-r--r--platform/mv3/extension/_locales/kn/messages.json158
-rw-r--r--platform/mv3/extension/_locales/ko/messages.json158
-rw-r--r--platform/mv3/extension/_locales/lt/messages.json158
-rw-r--r--platform/mv3/extension/_locales/lv/messages.json158
-rw-r--r--platform/mv3/extension/_locales/mk/messages.json158
-rw-r--r--platform/mv3/extension/_locales/ml/messages.json158
-rw-r--r--platform/mv3/extension/_locales/mr/messages.json158
-rw-r--r--platform/mv3/extension/_locales/ms/messages.json158
-rw-r--r--platform/mv3/extension/_locales/nb/messages.json158
-rw-r--r--platform/mv3/extension/_locales/nl/messages.json158
-rw-r--r--platform/mv3/extension/_locales/oc/messages.json158
-rw-r--r--platform/mv3/extension/_locales/pa/messages.json158
-rw-r--r--platform/mv3/extension/_locales/pl/messages.json158
-rw-r--r--platform/mv3/extension/_locales/pt_BR/messages.json158
-rw-r--r--platform/mv3/extension/_locales/pt_PT/messages.json158
-rw-r--r--platform/mv3/extension/_locales/ro/messages.json158
-rw-r--r--platform/mv3/extension/_locales/ru/messages.json158
-rw-r--r--platform/mv3/extension/_locales/si/messages.json158
-rw-r--r--platform/mv3/extension/_locales/sk/messages.json158
-rw-r--r--platform/mv3/extension/_locales/sl/messages.json158
-rw-r--r--platform/mv3/extension/_locales/so/messages.json158
-rw-r--r--platform/mv3/extension/_locales/sq/messages.json158
-rw-r--r--platform/mv3/extension/_locales/sr/messages.json158
-rw-r--r--platform/mv3/extension/_locales/sv/messages.json158
-rw-r--r--platform/mv3/extension/_locales/sw/messages.json158
-rw-r--r--platform/mv3/extension/_locales/ta/messages.json158
-rw-r--r--platform/mv3/extension/_locales/te/messages.json158
-rw-r--r--platform/mv3/extension/_locales/th/messages.json158
-rw-r--r--platform/mv3/extension/_locales/tr/messages.json158
-rw-r--r--platform/mv3/extension/_locales/uk/messages.json158
-rw-r--r--platform/mv3/extension/_locales/ur/messages.json158
-rw-r--r--platform/mv3/extension/_locales/vi/messages.json158
-rw-r--r--platform/mv3/extension/_locales/zh_CN/messages.json158
-rw-r--r--platform/mv3/extension/_locales/zh_TW/messages.json158
-rw-r--r--platform/mv3/extension/css/dashboard-common.css52
-rw-r--r--platform/mv3/extension/css/dashboard.css74
-rw-r--r--platform/mv3/extension/css/filtering-mode.css92
-rw-r--r--platform/mv3/extension/css/popup.css276
-rw-r--r--platform/mv3/extension/css/settings.css192
-rw-r--r--platform/mv3/extension/dashboard.html155
-rw-r--r--platform/mv3/extension/img/icon_128.pngbin0 -> 4015 bytes
-rw-r--r--platform/mv3/extension/img/icon_16.pngbin0 -> 534 bytes
-rw-r--r--platform/mv3/extension/img/icon_32.pngbin0 -> 971 bytes
-rw-r--r--platform/mv3/extension/img/icon_64.pngbin0 -> 1922 bytes
-rw-r--r--platform/mv3/extension/img/ublock.svg69
-rw-r--r--platform/mv3/extension/js/background.js354
-rw-r--r--platform/mv3/extension/js/dashboard.js40
-rw-r--r--platform/mv3/extension/js/ext.js119
-rw-r--r--platform/mv3/extension/js/fetch.js38
-rw-r--r--platform/mv3/extension/js/mode-manager.js426
-rw-r--r--platform/mv3/extension/js/popup.js351
-rw-r--r--platform/mv3/extension/js/ruleset-manager.js539
-rw-r--r--platform/mv3/extension/js/scripting-manager.js563
-rw-r--r--platform/mv3/extension/js/scripting/css-declarative.js157
-rw-r--r--platform/mv3/extension/js/scripting/css-generic.js239
-rw-r--r--platform/mv3/extension/js/scripting/css-procedural.js762
-rw-r--r--platform/mv3/extension/js/scripting/css-specific.js120
-rw-r--r--platform/mv3/extension/js/settings.js488
-rw-r--r--platform/mv3/extension/js/theme.js35
-rw-r--r--platform/mv3/extension/js/utils.js151
-rw-r--r--platform/mv3/extension/managed_storage.json15
-rw-r--r--platform/mv3/extension/popup.html61
-rw-r--r--platform/mv3/firefox/background.html10
-rw-r--r--platform/mv3/firefox/manifest.json54
-rw-r--r--platform/mv3/make-rulesets.js1344
-rw-r--r--platform/mv3/make-scriptlets.js193
-rw-r--r--platform/mv3/package.json6
-rw-r--r--platform/mv3/safe-replace.js41
-rw-r--r--platform/mv3/scriptlets/css-declarative.template.js51
-rw-r--r--platform/mv3/scriptlets/css-generic.template.js61
-rw-r--r--platform/mv3/scriptlets/css-generichigh.template.css26
-rw-r--r--platform/mv3/scriptlets/css-procedural.template.js51
-rw-r--r--platform/mv3/scriptlets/css-specific.template.js51
-rw-r--r--platform/mv3/scriptlets/scriptlet.template.js175
-rw-r--r--platform/mv3/ubo-version1
-rw-r--r--platform/nodejs/README.md158
-rw-r--r--platform/nodejs/build.js34
-rw-r--r--platform/nodejs/index.js281
-rw-r--r--platform/npm/.eslintrc.json38
-rw-r--r--platform/npm/.npmignore5
-rw-r--r--platform/npm/package-lock.json3038
-rw-r--r--platform/npm/package.json44
-rw-r--r--platform/npm/test.js59
-rw-r--r--platform/npm/tests/.eslintrc.json5
-rw-r--r--platform/npm/tests/_common.js34
-rw-r--r--platform/npm/tests/data/bundle.tgzbin0 -> 1225836 bytes
-rw-r--r--platform/npm/tests/leaks.js30
-rw-r--r--platform/npm/tests/request-data.js117
-rw-r--r--platform/npm/tests/snfe.js372
-rw-r--r--platform/npm/tests/wasm.js53
-rw-r--r--platform/opera/manifest.json116
-rw-r--r--platform/safari/README.md16
-rw-r--r--platform/thunderbird/manifest.json94
220 files changed, 29453 insertions, 0 deletions
diff --git a/platform/browser/main.js b/platform/browser/main.js
new file mode 100644
index 0000000..d6f6acb
--- /dev/null
+++ b/platform/browser/main.js
@@ -0,0 +1,123 @@
+/*******************************************************************************
+
+ uBlock Origin - a browser extension to block requests.
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+/******************************************************************************/
+
+import publicSuffixList from './lib/publicsuffixlist/publicsuffixlist.js';
+import punycode from './lib/punycode.js';
+
+import staticNetFilteringEngine from './js/static-net-filtering.js';
+import { FilteringContext } from './js/filtering-context.js';
+import { LineIterator } from './js/text-utils.js';
+import * as sfp from './js/static-filtering-parser.js';
+
+import {
+ CompiledListReader,
+ CompiledListWriter
+} from './js/static-filtering-io.js';
+
+/******************************************************************************/
+
+function compileList(rawText, writer) {
+ const lineIter = new LineIterator(rawText);
+ const parser = new sfp.AstFilterParser({
+ interactive: true,
+ maxTokenLength: staticNetFilteringEngine.MAX_TOKEN_LENGTH,
+ });
+ const compiler = staticNetFilteringEngine.createCompiler();
+
+ while ( lineIter.eot() === false ) {
+ let line = lineIter.next();
+
+ while ( line.endsWith(' \\') ) {
+ if ( lineIter.peek(4) !== ' ' ) { break; }
+ line = line.slice(0, -2).trim() + lineIter.next().trim();
+ }
+ parser.parse(line);
+
+ if ( parser.isFilter() === false ) { continue; }
+ if ( parser.isNetworkFilter() === false ) { continue; }
+ if ( compiler.compile(parser, writer) ) { continue; }
+ if ( compiler.error !== undefined ) {
+ console.info(JSON.stringify({
+ realm: 'message',
+ type: 'error',
+ text: compiler.error
+ }));
+ }
+ }
+
+ return writer.toString();
+}
+
+function applyList(name, raw) {
+ const writer = new CompiledListWriter();
+ writer.properties.set('name', name);
+ const compiled = compileList(raw, writer);
+ const reader = new CompiledListReader(compiled);
+ staticNetFilteringEngine.fromCompiled(reader);
+}
+
+function enableWASM(path) {
+ return Promise.all([
+ publicSuffixList.enableWASM(`${path}/lib/publicsuffixlist`),
+ staticNetFilteringEngine.enableWASM(`${path}/js`),
+ ]);
+}
+
+function pslInit(raw) {
+ if ( typeof raw !== 'string' || raw.trim() === '' ) {
+ console.info('Unable to populate public suffix list');
+ return;
+ }
+ publicSuffixList.parse(raw, punycode.toASCII);
+ console.info('Public suffix list populated');
+}
+
+function restart(lists) {
+ // Remove all filters
+ reset();
+
+ if ( Array.isArray(lists) && lists.length !== 0 ) {
+ // Populate filtering engine with filter lists
+ for ( const { name, raw } of lists ) {
+ applyList(name, raw);
+ }
+ // Commit changes
+ staticNetFilteringEngine.freeze();
+ staticNetFilteringEngine.optimize();
+ }
+
+ return staticNetFilteringEngine;
+}
+
+function reset() {
+ staticNetFilteringEngine.reset();
+}
+
+export {
+ FilteringContext,
+ enableWASM,
+ pslInit,
+ restart,
+};
diff --git a/platform/browser/test.html b/platform/browser/test.html
new file mode 100644
index 0000000..32b1aba
--- /dev/null
+++ b/platform/browser/test.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>uBO Static Network Filtering Engine</title>
+</head>
+<body>
+<script type="module">
+ import {
+ FilteringContext,
+ enableWASM,
+ pslInit,
+ restart,
+ } from './main.js';
+
+ (async ( ) => {
+ await enableWASM('.');
+
+ await fetch('./data/effective_tld_names.dat').then(response => {
+ return response.text();
+ }).then(pslRaw => {
+ pslInit(pslRaw);
+ });
+
+ const snfe = await Promise.all([
+ fetch('./data/easylist.txt').then(response => {
+ return response.text();
+ }),
+ fetch('./data/easyprivacy.txt').then(response => {
+ return response.text();
+ }),
+ ]).then(rawLists => {
+ return restart([
+ { name: 'easylist', raw: rawLists[0] },
+ { name: 'easyprivacy', raw: rawLists[1] },
+ ]);
+ });
+
+ // Reuse filtering context: it's what uBO does
+ const fctxt = new FilteringContext();
+
+ // Tests
+ // Not blocked
+ fctxt.setDocOriginFromURL('https://www.bloomberg.com/');
+ fctxt.setURL('https://www.bloomberg.com/tophat/assets/v2.6.1/that.css');
+ fctxt.setType('stylesheet');
+ if ( snfe.matchRequest(fctxt) !== 0 ) {
+ console.log(snfe.toLogData());
+ }
+
+ // Blocked
+ fctxt.setDocOriginFromURL('https://www.bloomberg.com/');
+ fctxt.setURL('https://securepubads.g.doubleclick.net/tag/js/gpt.js');
+ fctxt.setType('script');
+ if ( snfe.matchRequest(fctxt) !== 0 ) {
+ console.log(snfe.toLogData());
+ }
+
+ // Unblocked
+ fctxt.setDocOriginFromURL('https://www.bloomberg.com/');
+ fctxt.setURL('https://sourcepointcmp.bloomberg.com/ccpa.js');
+ fctxt.setType('script');
+ if ( snfe.matchRequest(fctxt) !== 0 ) {
+ console.log(snfe.toLogData());
+ }
+
+ restart();
+ })();
+</script>
+</body>
+</html>
diff --git a/platform/chromium/is-webrtc-supported.html b/platform/chromium/is-webrtc-supported.html
new file mode 100644
index 0000000..d30b674
--- /dev/null
+++ b/platform/chromium/is-webrtc-supported.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title></title>
+<script async src="js/is-webrtc-supported.js"></script>
+</head>
+<body></body>
+</html>
diff --git a/platform/chromium/is-webrtc-supported.js b/platform/chromium/is-webrtc-supported.js
new file mode 100644
index 0000000..8841370
--- /dev/null
+++ b/platform/chromium/is-webrtc-supported.js
@@ -0,0 +1,52 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2015 Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+// https://github.com/gorhill/uBlock/issues/533#issuecomment-164292868
+// If WebRTC is supported, there won't be an exception if we
+// try to instantiate a peer connection object.
+
+// https://github.com/gorhill/uBlock/issues/533#issuecomment-168097594
+// Because Chromium leaks WebRTC connections after they have been closed
+// and forgotten, we need to test for WebRTC support inside an iframe, this
+// way the closed and forgottetn WebRTC connections are properly garbage
+// collected.
+
+(function() {
+ 'use strict';
+
+ var pc = null;
+ try {
+ var PC = self.RTCPeerConnection || self.webkitRTCPeerConnection;
+ if ( PC ) {
+ pc = new PC(null);
+ }
+ } catch (ex) {
+ console.error(ex);
+ }
+ if ( pc !== null ) {
+ pc.close();
+ }
+
+ window.top.postMessage(
+ pc !== null ? 'webRTCSupported' : 'webRTCNotSupported',
+ window.location.origin
+ );
+})();
diff --git a/platform/chromium/manifest.json b/platform/chromium/manifest.json
new file mode 100644
index 0000000..28ceec5
--- /dev/null
+++ b/platform/chromium/manifest.json
@@ -0,0 +1,115 @@
+{
+ "author": "Raymond Hill & contributors",
+ "background": {
+ "page": "background.html"
+ },
+ "browser_action": {
+ "default_icon": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png",
+ "64": "img/icon_64.png"
+ },
+ "default_title": "uBlock Origin",
+ "default_popup": "popup-fenix.html"
+ },
+ "commands": {
+ "launch-element-zapper": {
+ "description": "__MSG_popupTipZapper__"
+ },
+ "launch-element-picker": {
+ "description": "__MSG_popupTipPicker__"
+ },
+ "launch-logger": {
+ "description": "__MSG_popupTipLog__"
+ },
+ "open-dashboard": {
+ "description": "__MSG_popupTipDashboard__"
+ },
+ "relax-blocking-mode": {
+ "description": "__MSG_relaxBlockingMode__"
+ },
+ "toggle-cosmetic-filtering": {
+ "description": "__MSG_toggleCosmeticFiltering__"
+ }
+ },
+ "content_scripts": [
+ {
+ "matches": [
+ "http://*/*",
+ "https://*/*"
+ ],
+ "js": [
+ "/js/vapi.js",
+ "/js/vapi-client.js",
+ "/js/contentscript.js"
+ ],
+ "all_frames": true,
+ "match_about_blank": true,
+ "run_at": "document_start"
+ },
+ {
+ "matches": [
+ "https://easylist.to/*",
+ "https://*.fanboy.co.nz/*",
+ "https://filterlists.com/*",
+ "https://forums.lanik.us/*",
+ "https://github.com/*",
+ "https://*.github.io/*",
+ "https://*.letsblock.it/*"
+ ],
+ "js": [
+ "/js/scriptlets/subscriber.js"
+ ],
+ "run_at": "document_idle",
+ "all_frames": false
+ },
+ {
+ "matches": [
+ "https://github.com/uBlockOrigin/*",
+ "https://ublockorigin.github.io/*",
+ "https://*.reddit.com/r/uBlockOrigin/*"
+ ],
+ "js": [
+ "/js/scriptlets/updater.js"
+ ],
+ "run_at": "document_idle",
+ "all_frames": false
+ }
+ ],
+ "content_security_policy": "script-src 'self'; object-src 'self'",
+ "default_locale": "en",
+ "description": "__MSG_extShortDesc__",
+ "icons": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png",
+ "64": "img/icon_64.png",
+ "128": "img/icon_128.png"
+ },
+ "incognito": "split",
+ "manifest_version": 2,
+ "minimum_chrome_version": "73.0",
+ "name": "uBlock Origin",
+ "options_ui": {
+ "page": "dashboard.html",
+ "open_in_tab": true
+ },
+ "permissions": [
+ "contextMenus",
+ "privacy",
+ "storage",
+ "tabs",
+ "unlimitedStorage",
+ "webNavigation",
+ "webRequest",
+ "webRequestBlocking",
+ "<all_urls>"
+ ],
+ "short_name": "uBlock₀",
+ "storage": {
+ "managed_schema": "managed_storage.json"
+ },
+ "version": "1.15.11.0",
+ "web_accessible_resources": [
+ "/web_accessible_resources/*"
+ ]
+}
diff --git a/platform/chromium/vapi-background-ext.js b/platform/chromium/vapi-background-ext.js
new file mode 100644
index 0000000..29de305
--- /dev/null
+++ b/platform/chromium/vapi-background-ext.js
@@ -0,0 +1,254 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2017-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* globals browser */
+
+'use strict';
+
+/******************************************************************************/
+
+// https://github.com/uBlockOrigin/uBlock-issues/issues/1659
+// Chromium fails to dispatch onCreatedNavigationTarget() events sometimes,
+// so we synthetize these missing events when this happens.
+// https://github.com/uBlockOrigin/uAssets/issues/10323
+// Also mind whether the new tab is launched from an external application.
+
+vAPI.Tabs = class extends vAPI.Tabs {
+ constructor() {
+ super();
+ this.tabIds = new Set();
+ browser.tabs.onCreated.addListener(tab => {
+ this.onCreatedHandler(tab);
+ });
+ }
+
+ onCreatedHandler(tab) {
+ if ( typeof tab.openerTabId === 'number' ) { return; }
+ if ( tab.index !== 0 ) { return; }
+ if ( tab.url !== '' ) { return; }
+ this.tabIds.add(tab.id);
+ }
+
+ onCreatedNavigationTargetHandler(details) {
+ this.tabIds.delete(details.tabId);
+ super.onCreatedNavigationTargetHandler(details);
+ }
+
+ onCommittedHandler(details) {
+ if ( details.frameId === 0 ) {
+ this.synthesizeNavigationTargetEvent(details);
+ }
+ super.onCommittedHandler(details);
+ }
+
+ onRemovedHandler(tabId, details) {
+ this.tabIds.delete(tabId);
+ super.onRemovedHandler(tabId, details);
+ }
+
+ synthesizeNavigationTargetEvent(details) {
+ if ( this.tabIds.has(details.tabId) === false ) { return; }
+ this.tabIds.delete(details.tabId);
+ const isClientRedirect =
+ Array.isArray(details.transitionQualifiers) &&
+ details.transitionQualifiers.includes('client_redirect');
+ const isStartPage = details.transitionType === 'start_page';
+ if ( isClientRedirect === false && isStartPage === false ) { return; }
+ this.onCreatedNavigationTargetHandler({
+ tabId: details.tabId,
+ sourceTabId: details.tabId,
+ sourceFrameId: 0,
+ url: details.url,
+ });
+ }
+};
+
+/******************************************************************************/
+
+{
+ const extToTypeMap = new Map([
+ ['eot','font'],['otf','font'],['svg','font'],['ttf','font'],['woff','font'],['woff2','font'],
+ ['mp3','media'],['mp4','media'],['webm','media'],
+ ['gif','image'],['ico','image'],['jpeg','image'],['jpg','image'],['png','image'],['webp','image']
+ ]);
+
+ const headerValue = (headers, name) => {
+ let i = headers.length;
+ while ( i-- ) {
+ if ( headers[i].name.toLowerCase() === name ) {
+ return headers[i].value.trim();
+ }
+ }
+ return '';
+ };
+
+ const parsedURL = new URL('https://www.example.org/');
+
+ // Extend base class to normalize as per platform.
+
+ vAPI.Net = class extends vAPI.Net {
+ normalizeDetails(details) {
+ // Chromium 63+ supports the `initiator` property, which contains
+ // the URL of the origin from which the network request was made.
+ if (
+ typeof details.initiator === 'string' &&
+ details.initiator !== 'null'
+ ) {
+ details.documentUrl = details.initiator;
+ }
+
+ let type = details.type;
+
+ if ( type === 'imageset' ) {
+ details.type = 'image';
+ return;
+ }
+
+ // The rest of the function code is to normalize type
+ if ( type !== 'other' ) { return; }
+
+ // Try to map known "extension" part of URL to request type.
+ parsedURL.href = details.url;
+ const path = parsedURL.pathname,
+ pos = path.indexOf('.', path.length - 6);
+ if ( pos !== -1 && (type = extToTypeMap.get(path.slice(pos + 1))) ) {
+ details.type = type;
+ return;
+ }
+
+ // Try to extract type from response headers if present.
+ if ( details.responseHeaders ) {
+ type = headerValue(details.responseHeaders, 'content-type');
+ if ( type.startsWith('font/') ) {
+ details.type = 'font';
+ return;
+ }
+ if ( type.startsWith('image/') ) {
+ details.type = 'image';
+ return;
+ }
+ if ( type.startsWith('audio/') || type.startsWith('video/') ) {
+ details.type = 'media';
+ return;
+ }
+ }
+ }
+
+ // https://www.reddit.com/r/uBlockOrigin/comments/9vcrk3/
+ // Some types can be mapped from 'other', thus include 'other' if and
+ // only if the caller is interested in at least one of those types.
+ denormalizeTypes(types) {
+ if ( types.length === 0 ) {
+ return Array.from(this.validTypes);
+ }
+ const out = new Set();
+ for ( const type of types ) {
+ if ( this.validTypes.has(type) ) {
+ out.add(type);
+ }
+ }
+ if ( out.has('other') === false ) {
+ for ( const type of extToTypeMap.values() ) {
+ if ( out.has(type) ) {
+ out.add('other');
+ break;
+ }
+ }
+ }
+ return Array.from(out);
+ }
+
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/2063
+ // Do not interfere with root document
+ suspendOneRequest(details) {
+ this.onBeforeSuspendableRequest(details);
+ if ( details.type === 'main_frame' ) { return; }
+ return { cancel: true };
+ }
+
+ unsuspendAllRequests(discard = false) {
+ if ( discard === true ) { return; }
+ const toReload = [];
+ for ( const tabId of this.unprocessedTabs.keys() ) {
+ toReload.push(tabId);
+ }
+ this.removeUnprocessedRequest();
+ for ( const tabId of toReload ) {
+ vAPI.tabs.reload(tabId);
+ }
+ }
+ };
+}
+
+/******************************************************************************/
+
+// https://github.com/uBlockOrigin/uBlock-issues/issues/548
+// Use `X-DNS-Prefetch-Control` to workaround Chromium's disregard of the
+// setting "Predict network actions to improve page load performance".
+
+vAPI.prefetching = (( ) => {
+ let listening = false;
+
+ const onHeadersReceived = function(details) {
+ details.responseHeaders.push({
+ name: 'X-DNS-Prefetch-Control',
+ value: 'off'
+ });
+ return { responseHeaders: details.responseHeaders };
+ };
+
+ return state => {
+ const wr = chrome.webRequest;
+ if ( state && listening ) {
+ wr.onHeadersReceived.removeListener(onHeadersReceived);
+ listening = false;
+ } else if ( !state && !listening ) {
+ wr.onHeadersReceived.addListener(
+ onHeadersReceived,
+ {
+ urls: [ 'http://*/*', 'https://*/*' ],
+ types: [ 'main_frame', 'sub_frame' ]
+ },
+ [ 'blocking', 'responseHeaders' ]
+ );
+ listening = true;
+ }
+ };
+})();
+
+/******************************************************************************/
+
+vAPI.scriptletsInjector = ((doc, details) => {
+ let script;
+ try {
+ script = doc.createElement('script');
+ script.appendChild(doc.createTextNode(details.scriptlets));
+ (doc.head || doc.documentElement).appendChild(script);
+ self.uBO_scriptletsInjected = details.filters;
+ } catch (ex) {
+ }
+ if ( script ) {
+ script.remove();
+ script.textContent = '';
+ }
+}).toString();
+
+/******************************************************************************/
diff --git a/platform/chromium/webext.js b/platform/chromium/webext.js
new file mode 100644
index 0000000..851b653
--- /dev/null
+++ b/platform/chromium/webext.js
@@ -0,0 +1,164 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2019-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+// `webext` is a promisified api of `chrome`. Entries are added as
+// the promisification of uBO progress.
+
+const promisifyNoFail = function(thisArg, fnName, outFn = r => r) {
+ const fn = thisArg[fnName];
+ return function() {
+ return new Promise(resolve => {
+ fn.call(thisArg, ...arguments, function() {
+ if ( chrome.runtime.lastError instanceof Object ) {
+ void chrome.runtime.lastError.message;
+ }
+ resolve(outFn(...arguments));
+ });
+ });
+ };
+};
+
+const promisify = function(thisArg, fnName) {
+ const fn = thisArg[fnName];
+ return function() {
+ return new Promise((resolve, reject) => {
+ fn.call(thisArg, ...arguments, function() {
+ const lastError = chrome.runtime.lastError;
+ if ( lastError instanceof Object ) {
+ return reject(lastError.message);
+ }
+ resolve(...arguments);
+ });
+ });
+ };
+};
+
+const webext = {
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/browserAction
+ browserAction: {
+ setBadgeBackgroundColor: promisifyNoFail(chrome.browserAction, 'setBadgeBackgroundColor'),
+ setBadgeText: promisifyNoFail(chrome.browserAction, 'setBadgeText'),
+ setIcon: promisifyNoFail(chrome.browserAction, 'setIcon'),
+ setTitle: promisifyNoFail(chrome.browserAction, 'setTitle'),
+ },
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/menus
+ menus: {
+ create: function() {
+ return chrome.contextMenus.create(...arguments, ( ) => {
+ void chrome.runtime.lastError;
+ });
+ },
+ onClicked: chrome.contextMenus.onClicked,
+ remove: promisifyNoFail(chrome.contextMenus, 'remove'),
+ removeAll: promisifyNoFail(chrome.contextMenus, 'removeAll'),
+ },
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/privacy
+ privacy: {
+ },
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage
+ storage: {
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/local
+ local: {
+ clear: promisify(chrome.storage.local, 'clear'),
+ get: promisify(chrome.storage.local, 'get'),
+ getBytesInUse: promisify(chrome.storage.local, 'getBytesInUse'),
+ remove: promisify(chrome.storage.local, 'remove'),
+ set: promisify(chrome.storage.local, 'set'),
+ },
+ },
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs
+ tabs: {
+ get: promisifyNoFail(chrome.tabs, 'get', tab => tab instanceof Object ? tab : null),
+ executeScript: promisifyNoFail(chrome.tabs, 'executeScript'),
+ insertCSS: promisifyNoFail(chrome.tabs, 'insertCSS'),
+ removeCSS: promisifyNoFail(chrome.tabs, 'removeCSS'),
+ query: promisifyNoFail(chrome.tabs, 'query', tabs => Array.isArray(tabs) ? tabs : []),
+ reload: promisifyNoFail(chrome.tabs, 'reload'),
+ remove: promisifyNoFail(chrome.tabs, 'remove'),
+ update: promisifyNoFail(chrome.tabs, 'update', tab => tab instanceof Object ? tab : null),
+ },
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webNavigation
+ webNavigation: {
+ getFrame: promisify(chrome.webNavigation, 'getFrame'),
+ getAllFrames: promisify(chrome.webNavigation, 'getAllFrames'),
+ },
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/windows
+ windows: {
+ get: promisifyNoFail(chrome.windows, 'get', win => win instanceof Object ? win : null),
+ create: promisifyNoFail(chrome.windows, 'create', win => win instanceof Object ? win : null),
+ update: promisifyNoFail(chrome.windows, 'update', win => win instanceof Object ? win : null),
+ },
+};
+
+// browser.privacy entries
+{
+ const settings = [
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/privacy/network
+ [ 'network', 'networkPredictionEnabled' ],
+ [ 'network', 'webRTCIPHandlingPolicy' ],
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/privacy/websites
+ [ 'websites', 'hyperlinkAuditingEnabled' ],
+ ];
+ for ( const [ category, setting ] of settings ) {
+ let categoryEntry = webext.privacy[category];
+ if ( categoryEntry instanceof Object === false ) {
+ categoryEntry = webext.privacy[category] = {};
+ }
+ const settingEntry = categoryEntry[setting] = {};
+ const thisArg = chrome.privacy[category][setting];
+ settingEntry.clear = promisifyNoFail(thisArg, 'clear');
+ settingEntry.get = promisifyNoFail(thisArg, 'get');
+ settingEntry.set = promisifyNoFail(thisArg, 'set');
+ }
+}
+
+// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/managed
+if ( chrome.storage.managed instanceof Object ) {
+ webext.storage.managed = {
+ get: promisify(chrome.storage.managed, 'get'),
+ };
+}
+
+// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/sync
+if ( chrome.storage.sync instanceof Object ) {
+ webext.storage.sync = {
+ QUOTA_BYTES: chrome.storage.sync.QUOTA_BYTES,
+ QUOTA_BYTES_PER_ITEM: chrome.storage.sync.QUOTA_BYTES_PER_ITEM,
+ MAX_ITEMS: chrome.storage.sync.MAX_ITEMS,
+ MAX_WRITE_OPERATIONS_PER_HOUR: chrome.storage.sync.MAX_WRITE_OPERATIONS_PER_HOUR,
+ MAX_WRITE_OPERATIONS_PER_MINUTE: chrome.storage.sync.MAX_WRITE_OPERATIONS_PER_MINUTE,
+
+ clear: promisify(chrome.storage.sync, 'clear'),
+ get: promisify(chrome.storage.sync, 'get'),
+ getBytesInUse: promisify(chrome.storage.sync, 'getBytesInUse'),
+ remove: promisify(chrome.storage.sync, 'remove'),
+ set: promisify(chrome.storage.sync, 'set'),
+ };
+}
+
+// https://bugs.chromium.org/p/chromium/issues/detail?id=608854
+if ( chrome.tabs.removeCSS instanceof Function ) {
+ webext.tabs.removeCSS = promisifyNoFail(chrome.tabs, 'removeCSS');
+}
+
+export default webext;
diff --git a/platform/common/managed_storage.json b/platform/common/managed_storage.json
new file mode 100644
index 0000000..25614dd
--- /dev/null
+++ b/platform/common/managed_storage.json
@@ -0,0 +1,73 @@
+{
+ "$schema": "http://json-schema.org/draft-03/schema#",
+ "type": "object",
+ "properties": {
+ "adminSettings": {
+ "title": "A valid JSON string compliant with uBO's backup format",
+ "description": "All entries present will overwrite local settings.",
+ "type": "string"
+ },
+ "advancedSettings": {
+ "title": "A list of [name,value] pairs to populate advanced settings",
+ "type": "array",
+ "items": {
+ "title": "A [name,value] pair",
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ },
+ "userSettings": {
+ "title": "A list of [name,value] pairs to populate user settings",
+ "type": "array",
+ "items": {
+ "title": "A [name,value] pair",
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ },
+ "disableDashboard": {
+ "title": "Set to true to prevent access to configuration options",
+ "type": "boolean"
+ },
+ "disabledPopupPanelParts": {
+ "title": "An array of strings used to remove parts of the popup panel",
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "toAdd": {
+ "title": "Settings to add at launch time",
+ "type": "object",
+ "properties": {
+ "trustedSiteDirectives": {
+ "title": "A list of trusted-site directives",
+ "description": "Trusted-site directives to always add at launch time.",
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ }
+ },
+ "toOverwrite": {
+ "title": "Settings to overwrite at launch time",
+ "type": "object",
+ "properties": {
+ "filters": {
+ "title": "A collection of filters",
+ "description": "The set of user filters to use at launch time -- where each entry is a distinct line.",
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "filterLists": {
+ "title": "A collection of list identifiers and/or list URLs",
+ "description": "The set of filter lists to use at launch time.",
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "trustedSiteDirectives": {
+ "title": "A list of trusted-site directives",
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ }
+ }
+ }
+}
diff --git a/platform/common/vapi-background.js b/platform/common/vapi-background.js
new file mode 100644
index 0000000..0d6fcdd
--- /dev/null
+++ b/platform/common/vapi-background.js
@@ -0,0 +1,1811 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-2015 The uBlock Origin authors
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+// For background page
+
+/* globals browser */
+
+'use strict';
+
+/******************************************************************************/
+
+import webext from './webext.js';
+import { ubolog } from './console.js';
+
+/******************************************************************************/
+
+const manifest = browser.runtime.getManifest();
+
+vAPI.cantWebsocket =
+ browser.webRequest.ResourceType instanceof Object === false ||
+ browser.webRequest.ResourceType.WEBSOCKET !== 'websocket';
+
+vAPI.canWASM = vAPI.webextFlavor.soup.has('chromium') === false;
+if ( vAPI.canWASM === false ) {
+ const csp = manifest.content_security_policy;
+ vAPI.canWASM = csp !== undefined && csp.indexOf("'wasm-unsafe-eval'") !== -1;
+}
+
+vAPI.supportsUserStylesheets = vAPI.webextFlavor.soup.has('user_stylesheet');
+
+/******************************************************************************/
+
+vAPI.app = {
+ name: manifest.name.replace(/ dev\w+ build/, ''),
+ version: (( ) => {
+ let version = manifest.version;
+ const match = /(\d+\.\d+\.\d+)(?:\.(\d+))?/.exec(version);
+ if ( match && match[2] ) {
+ const v = parseInt(match[2], 10);
+ version = match[1] + (v < 100 ? 'b' + v : 'rc' + (v - 100));
+ }
+ return version;
+ })(),
+
+ intFromVersion: function(s) {
+ const parts = s.match(/(?:^|\.|b|rc)\d+/g);
+ if ( parts === null ) { return 0; }
+ let vint = 0;
+ for ( let i = 0; i < 4; i++ ) {
+ const pstr = parts[i] || '';
+ let pint;
+ if ( pstr === '' ) {
+ pint = 0;
+ } else if ( pstr.startsWith('.') || pstr.startsWith('b') ) {
+ pint = parseInt(pstr.slice(1), 10);
+ } else if ( pstr.startsWith('rc') ) {
+ pint = parseInt(pstr.slice(2), 10) + 100;
+ } else {
+ pint = parseInt(pstr, 10);
+ }
+ vint = vint * 1000 + pint;
+ }
+ return vint;
+ },
+
+ restart: function() {
+ browser.runtime.reload();
+ },
+};
+
+/*******************************************************************************
+ *
+ * https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/storage/session
+ *
+ * Session (in-memory) storage is promise-based in all browsers, no need for
+ * a webext polyfill. However, not all browsers supports it in MV2.
+ *
+ * */
+
+vAPI.sessionStorage = {
+ get() {
+ return Promise.resolve({});
+ },
+ set() {
+ return Promise.resolve();
+ },
+ remove() {
+ return Promise.resolve();
+ },
+ clear() {
+ return Promise.resolve();
+ },
+ implemented: false,
+};
+
+/*******************************************************************************
+ *
+ * Data written to and read from storage.local will be mirrored to in-memory
+ * storage.session.
+ *
+ * Data read from storage.local will be first fetched from storage.session,
+ * then if not available, read from storage.local.
+ *
+ * */
+
+vAPI.storage = {
+ get(key, ...args) {
+ if ( vAPI.sessionStorage.implemented !== true ) {
+ return webext.storage.local.get(key, ...args).catch(reason => {
+ console.log(reason);
+ });
+ }
+ return vAPI.sessionStorage.get(key, ...args).then(bin => {
+ const size = Object.keys(bin).length;
+ if ( size === 1 && typeof key === 'string' && bin[key] === null ) {
+ return {};
+ }
+ if ( size !== 0 ) { return bin; }
+ return webext.storage.local.get(key, ...args).then(bin => {
+ if ( bin instanceof Object === false ) { return bin; }
+ // Mirror empty result as null value in order to prevent
+ // from falling back to storage.local when there is no need.
+ const tomirror = Object.assign({}, bin);
+ if ( typeof key === 'string' && Object.keys(bin).length === 0 ) {
+ Object.assign(tomirror, { [key]: null });
+ }
+ vAPI.sessionStorage.set(tomirror);
+ return bin;
+ }).catch(reason => {
+ console.log(reason);
+ });
+ });
+ },
+ set(...args) {
+ vAPI.sessionStorage.set(...args);
+ return webext.storage.local.set(...args).catch(reason => {
+ console.log(reason);
+ });
+ },
+ remove(...args) {
+ vAPI.sessionStorage.remove(...args);
+ return webext.storage.local.remove(...args).catch(reason => {
+ console.log(reason);
+ });
+ },
+ clear(...args) {
+ vAPI.sessionStorage.clear(...args);
+ return webext.storage.local.clear(...args).catch(reason => {
+ console.log(reason);
+ });
+ },
+ QUOTA_BYTES: browser.storage.local.QUOTA_BYTES,
+};
+
+// Not all platforms support getBytesInUse
+if ( webext.storage.local.getBytesInUse instanceof Function ) {
+ vAPI.storage.getBytesInUse = function(...args) {
+ return webext.storage.local.getBytesInUse(...args).catch(reason => {
+ console.log(reason);
+ });
+ };
+}
+
+/******************************************************************************/
+/******************************************************************************/
+
+// https://github.com/gorhill/uMatrix/issues/234
+// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/privacy/network
+
+// https://github.com/gorhill/uBlock/issues/2048
+// Do not mess up with existing settings if not assigning them stricter
+// values.
+
+vAPI.browserSettings = (( ) => {
+ // Not all platforms support `browser.privacy`.
+ const bp = webext.privacy;
+ if ( bp instanceof Object === false ) { return; }
+
+ return {
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/1723#issuecomment-919913361
+ canLeakLocalIPAddresses:
+ vAPI.webextFlavor.soup.has('firefox') &&
+ vAPI.webextFlavor.soup.has('mobile'),
+
+ set: function(details) {
+ for ( const setting in details ) {
+ if ( details.hasOwnProperty(setting) === false ) { continue; }
+ switch ( setting ) {
+ case 'prefetching':
+ const enabled = !!details[setting];
+ if ( enabled ) {
+ bp.network.networkPredictionEnabled.clear({
+ scope: 'regular',
+ });
+ } else {
+ bp.network.networkPredictionEnabled.set({
+ value: false,
+ scope: 'regular',
+ });
+ }
+ if ( vAPI.prefetching instanceof Function ) {
+ vAPI.prefetching(enabled);
+ }
+ break;
+
+ case 'hyperlinkAuditing':
+ if ( !!details[setting] ) {
+ bp.websites.hyperlinkAuditingEnabled.clear({
+ scope: 'regular',
+ });
+ } else {
+ bp.websites.hyperlinkAuditingEnabled.set({
+ value: false,
+ scope: 'regular',
+ });
+ }
+ break;
+
+ case 'webrtcIPAddress': {
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/1928
+ // https://www.reddit.com/r/uBlockOrigin/comments/sl7p74/
+ // Hypothetical: some browsers _think_ uBO is still using
+ // the setting possibly based on cached state from the
+ // past, and making an explicit API call that uBO is not
+ // using the setting appears to solve those unexpected
+ // reported occurrences of uBO interfering despite never
+ // using the API.
+ const mustEnable = !details[setting];
+ if ( this.canLeakLocalIPAddresses === false ) {
+ if ( mustEnable && vAPI.webextFlavor.soup.has('chromium') ) {
+ bp.network.webRTCIPHandlingPolicy.clear({
+ scope: 'regular',
+ });
+ }
+ continue;
+ }
+ if ( mustEnable ) {
+ bp.network.webRTCIPHandlingPolicy.set({
+ value: 'default_public_interface_only',
+ scope: 'regular'
+ });
+ } else {
+ bp.network.webRTCIPHandlingPolicy.clear({
+ scope: 'regular',
+ });
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ };
+})();
+
+/******************************************************************************/
+/******************************************************************************/
+
+vAPI.isBehindTheSceneTabId = function(tabId) {
+ return tabId < 0;
+};
+
+vAPI.unsetTabId = 0;
+vAPI.noTabId = -1; // definitely not any existing tab
+
+// To ensure we always use a good tab id
+const toTabId = function(tabId) {
+ return typeof tabId === 'number' && isNaN(tabId) === false
+ ? tabId
+ : 0;
+};
+
+// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webNavigation
+// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs
+
+vAPI.Tabs = class {
+ constructor() {
+ browser.webNavigation.onCreatedNavigationTarget.addListener(details => {
+ this.onCreatedNavigationTargetHandler(details);
+ });
+ browser.webNavigation.onCommitted.addListener(details => {
+ const { frameId, tabId } = details;
+ if ( frameId === 0 && tabId > 0 && details.transitionType === 'reload' ) {
+ if ( vAPI.net && vAPI.net.hasUnprocessedRequest(tabId) ) {
+ vAPI.net.removeUnprocessedRequest(tabId);
+ }
+ }
+ this.onCommittedHandler(details);
+ });
+ browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
+ this.onUpdatedHandler(tabId, changeInfo, tab);
+ });
+ browser.tabs.onActivated.addListener(details => {
+ this.onActivated(details);
+ });
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/151
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/680#issuecomment-515215220
+ if ( browser.windows instanceof Object ) {
+ browser.windows.onFocusChanged.addListener(windowId => {
+ this.onFocusChangedHandler(windowId);
+ });
+ }
+ browser.tabs.onRemoved.addListener((tabId, details) => {
+ if ( vAPI.net && vAPI.net.hasUnprocessedRequest(tabId) ) {
+ vAPI.net.removeUnprocessedRequest(tabId);
+ }
+ this.onRemovedHandler(tabId, details);
+ });
+ }
+
+ async executeScript() {
+ let result;
+ try {
+ result = await webext.tabs.executeScript(...arguments);
+ }
+ catch(reason) {
+ }
+ return Array.isArray(result) ? result : [];
+ }
+
+ async get(tabId) {
+ if ( tabId === null ) {
+ return this.getCurrent();
+ }
+ if ( tabId <= 0 ) { return null; }
+ let tab;
+ try {
+ tab = await webext.tabs.get(tabId);
+ }
+ catch(reason) {
+ }
+ return tab instanceof Object ? tab : null;
+ }
+
+ async getCurrent() {
+ const tabs = await this.query({ active: true, currentWindow: true });
+ return tabs.length !== 0 ? tabs[0] : null;
+ }
+
+ async insertCSS(tabId, details) {
+ if ( vAPI.supportsUserStylesheets ) {
+ details.cssOrigin = 'user';
+ }
+ try {
+ await webext.tabs.insertCSS(...arguments);
+ }
+ catch(reason) {
+ }
+ }
+
+ async query(queryInfo) {
+ let tabs;
+ try {
+ tabs = await webext.tabs.query(queryInfo);
+ }
+ catch(reason) {
+ }
+ return Array.isArray(tabs) ? tabs : [];
+ }
+
+ async removeCSS(tabId, details) {
+ if ( vAPI.supportsUserStylesheets ) {
+ details.cssOrigin = 'user';
+ }
+ try {
+ await webext.tabs.removeCSS(...arguments);
+ }
+ catch(reason) {
+ }
+ }
+
+ // Properties of the details object:
+ // - url: 'URL', => the address that will be opened
+ // - index: -1, => undefined: end of the list, -1: following tab,
+ // or after index
+ // - active: false, => opens the tab... in background: true,
+ // foreground: undefined
+ // - popup: 'popup' => open in a new window
+
+ async create(url, details) {
+ if ( details.active === undefined ) {
+ details.active = true;
+ }
+
+ const subWrapper = async ( ) => {
+ const updateDetails = {
+ url: url,
+ active: !!details.active
+ };
+
+ // Opening a tab from incognito window won't focus the window
+ // in which the tab was opened
+ const focusWindow = tab => {
+ if ( tab.active && vAPI.windows instanceof Object ) {
+ vAPI.windows.update(tab.windowId, { focused: true });
+ }
+ };
+
+ if ( !details.tabId ) {
+ if ( details.index !== undefined ) {
+ updateDetails.index = details.index;
+ }
+ browser.tabs.create(updateDetails, focusWindow);
+ return;
+ }
+
+ // update doesn't accept index, must use move
+ const tab = await vAPI.tabs.update(
+ toTabId(details.tabId),
+ updateDetails
+ );
+ // if the tab doesn't exist
+ if ( tab === null ) {
+ browser.tabs.create(updateDetails, focusWindow);
+ } else if ( details.index !== undefined ) {
+ browser.tabs.move(tab.id, { index: details.index });
+ }
+ };
+
+ // Open in a standalone window
+ //
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/168#issuecomment-413038191
+ // Not all platforms support vAPI.windows.
+ //
+ // For some reasons, some platforms do not honor the left,top
+ // position when specified. I found that further calling
+ // windows.update again with the same position _may_ help.
+ if ( details.popup !== undefined && vAPI.windows instanceof Object ) {
+ const createDetails = {
+ url: details.url,
+ type: details.popup,
+ };
+ if ( details.box instanceof Object ) {
+ Object.assign(createDetails, details.box);
+ }
+ const win = await vAPI.windows.create(createDetails);
+ if ( win === null ) { return; }
+ if ( details.box instanceof Object === false ) { return; }
+ if (
+ win.left === details.box.left &&
+ win.top === details.box.top
+ ) {
+ return;
+ }
+ vAPI.windows.update(win.id, {
+ left: details.box.left,
+ top: details.box.top
+ });
+ return;
+ }
+
+ if ( details.index !== -1 ) {
+ subWrapper();
+ return;
+ }
+
+ const tab = await vAPI.tabs.getCurrent();
+ if ( tab !== null ) {
+ details.index = tab.index + 1;
+ } else {
+ details.index = undefined;
+ }
+ subWrapper();
+ }
+
+ // Properties of the details object:
+ // - url: 'URL', => the address that will be opened
+ // - tabId: 1, => the tab is used if set, instead of creating a new one
+ // - index: -1, => undefined: end of the list, -1: following tab, or
+ // after index
+ // - active: false, => opens the tab in background - true and undefined:
+ // foreground
+ // - select: true, => if a tab is already opened with that url, then select
+ // it instead of opening a new one
+ // - popup: true => open in a new window
+
+ async open(details) {
+ let targetURL = details.url;
+ if ( typeof targetURL !== 'string' || targetURL === '' ) {
+ return null;
+ }
+
+ // extension pages
+ if ( /^[\w-]{2,}:/.test(targetURL) !== true ) {
+ targetURL = vAPI.getURL(targetURL);
+ }
+
+ if ( !details.select ) {
+ this.create(targetURL, details);
+ return;
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/query#Parameters
+ // "Note that fragment identifiers are not matched."
+ // Fragment identifiers ARE matched -- we need to remove the fragment.
+ const pos = targetURL.indexOf('#');
+ const targetURLWithoutHash = pos === -1
+ ? targetURL
+ : targetURL.slice(0, pos);
+
+ const tabs = await vAPI.tabs.query({ url: targetURLWithoutHash });
+ if ( tabs.length === 0 ) {
+ this.create(targetURL, details);
+ return;
+ }
+ let tab = tabs[0];
+ const updateDetails = { active: true };
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/592
+ if ( tab.url.startsWith(targetURL) === false ) {
+ updateDetails.url = targetURL;
+ }
+ tab = await vAPI.tabs.update(tab.id, updateDetails);
+ if ( vAPI.windows instanceof Object === false ) { return; }
+ vAPI.windows.update(tab.windowId, { focused: true });
+ }
+
+ async update() {
+ let tab;
+ try {
+ tab = await webext.tabs.update(...arguments);
+ }
+ catch (reason) {
+ }
+ return tab instanceof Object ? tab : null;
+ }
+
+ // Replace the URL of a tab. Noop if the tab does not exist.
+ replace(tabId, url) {
+ tabId = toTabId(tabId);
+ if ( tabId === 0 ) { return; }
+
+ let targetURL = url;
+
+ // extension pages
+ if ( /^[\w-]{2,}:/.test(targetURL) !== true ) {
+ targetURL = vAPI.getURL(targetURL);
+ }
+
+ vAPI.tabs.update(tabId, { url: targetURL });
+ }
+
+ async remove(tabId) {
+ tabId = toTabId(tabId);
+ if ( tabId === 0 ) { return; }
+ try {
+ await webext.tabs.remove(tabId);
+ }
+ catch (reason) {
+ }
+ }
+
+ async reload(tabId, bypassCache = false) {
+ tabId = toTabId(tabId);
+ if ( tabId === 0 ) { return; }
+ try {
+ await webext.tabs.reload(
+ tabId,
+ { bypassCache: bypassCache === true }
+ );
+ }
+ catch (reason) {
+ }
+ }
+
+ async select(tabId) {
+ tabId = toTabId(tabId);
+ if ( tabId === 0 ) { return; }
+ const tab = await vAPI.tabs.update(tabId, { active: true });
+ if ( tab === null ) { return; }
+ if ( vAPI.windows instanceof Object === false ) { return; }
+ vAPI.windows.update(tab.windowId, { focused: true });
+ }
+
+ // https://forums.lanik.us/viewtopic.php?f=62&t=32826
+ // Chromium-based browsers: sanitize target URL. I've seen data: URI with
+ // newline characters in standard fields, possibly as a way of evading
+ // filters. As per spec, there should be no whitespaces in a data: URI's
+ // standard fields.
+
+ sanitizeURL(url) {
+ if ( url.startsWith('data:') === false ) { return url; }
+ const pos = url.indexOf(',');
+ if ( pos === -1 ) { return url; }
+ const s = url.slice(0, pos);
+ if ( s.search(/\s/) === -1 ) { return url; }
+ return s.replace(/\s+/, '') + url.slice(pos);
+ }
+
+ onCreatedNavigationTargetHandler(details) {
+ if ( typeof details.url !== 'string' ) {
+ details.url = '';
+ }
+ if ( /^https?:\/\//.test(details.url) === false ) {
+ details.frameId = 0;
+ details.url = this.sanitizeURL(details.url);
+ this.onNavigation(details);
+ }
+ this.onCreated(details);
+ }
+
+ onCommittedHandler(details) {
+ details.url = this.sanitizeURL(details.url);
+ this.onNavigation(details);
+ }
+
+ onUpdatedHandler(tabId, changeInfo, tab) {
+ // Ignore uninteresting update events
+ const { status = '', title = '', url = '' } = changeInfo;
+ if ( status === '' && title === '' && url === '' ) { return; }
+ // https://github.com/gorhill/uBlock/issues/3073
+ // Fall back to `tab.url` when `changeInfo.url` is not set.
+ if ( url === '' ) {
+ changeInfo.url = tab && tab.url;
+ }
+ if ( changeInfo.url ) {
+ changeInfo.url = this.sanitizeURL(changeInfo.url);
+ }
+ this.onUpdated(tabId, changeInfo, tab);
+ }
+
+ onRemovedHandler(tabId, details) {
+ this.onClosed(tabId, details);
+ }
+
+ onFocusChangedHandler(windowId) {
+ if ( windowId === browser.windows.WINDOW_ID_NONE ) { return; }
+ vAPI.tabs.query({ active: true, windowId }).then(tabs => {
+ if ( tabs.length === 0 ) { return; }
+ const tab = tabs[0];
+ this.onActivated({ tabId: tab.id, windowId: tab.windowId });
+ });
+ }
+
+ onActivated(/* details */) {
+ }
+
+ onClosed(/* tabId, details */) {
+ }
+
+ onCreated(/* details */) {
+ }
+
+ onNavigation(/* details */) {
+ }
+
+ onUpdated(/* tabId, changeInfo, tab */) {
+ }
+};
+
+/******************************************************************************/
+/******************************************************************************/
+
+if ( webext.windows instanceof Object ) {
+ vAPI.windows = {
+ get: async function() {
+ let win;
+ try {
+ win = await webext.windows.get(...arguments);
+ }
+ catch (reason) {
+ }
+ return win instanceof Object ? win : null;
+ },
+ create: async function() {
+ let win;
+ try {
+ win = await webext.windows.create(...arguments);
+ }
+ catch (reason) {
+ }
+ return win instanceof Object ? win : null;
+ },
+ update: async function() {
+ let win;
+ try {
+ win = await webext.windows.update(...arguments);
+ }
+ catch (reason) {
+ }
+ return win instanceof Object ? win : null;
+ },
+ };
+}
+
+/******************************************************************************/
+/******************************************************************************/
+
+if ( webext.browserAction instanceof Object ) {
+ vAPI.browserAction = {
+ setTitle: async function() {
+ try {
+ await webext.browserAction.setTitle(...arguments);
+ }
+ catch (reason) {
+ }
+ },
+ };
+ // Not supported on Firefox for Android
+ if ( webext.browserAction.setIcon ) {
+ vAPI.browserAction.setBadgeTextColor = async function() {
+ try {
+ await webext.browserAction.setBadgeTextColor(...arguments);
+ }
+ catch (reason) {
+ }
+ };
+ vAPI.browserAction.setBadgeBackgroundColor = async function() {
+ try {
+ await webext.browserAction.setBadgeBackgroundColor(...arguments);
+ }
+ catch (reason) {
+ }
+ };
+ vAPI.browserAction.setBadgeText = async function() {
+ try {
+ await webext.browserAction.setBadgeText(...arguments);
+ }
+ catch (reason) {
+ }
+ };
+ vAPI.browserAction.setIcon = async function() {
+ try {
+ await webext.browserAction.setIcon(...arguments);
+ }
+ catch (reason) {
+ }
+ };
+ }
+}
+
+/******************************************************************************/
+/******************************************************************************/
+
+// Must read: https://code.google.com/p/chromium/issues/detail?id=410868#c8
+
+// https://github.com/chrisaljoudi/uBlock/issues/19
+// https://github.com/chrisaljoudi/uBlock/issues/207
+// Since we may be called asynchronously, the tab id may not exist
+// anymore, so this ensures it does still exist.
+
+// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/browserAction#Browser_compatibility
+// Firefox for Android does no support browser.browserAction.setIcon().
+// Performance: use ImageData for platforms supporting it.
+
+// https://github.com/uBlockOrigin/uBlock-issues/issues/32
+// Ensure ImageData for toolbar icon is valid before use.
+
+{
+ const browserAction = vAPI.browserAction;
+ const titleTemplate = `${browser.runtime.getManifest().browser_action.default_title} ({badge})`;
+ const icons = [
+ { path: {
+ '16': 'img/icon_16-off.png',
+ '32': 'img/icon_32-off.png',
+ '64': 'img/icon_64-off.png',
+ } },
+ { path: {
+ '16': 'img/icon_16.png',
+ '32': 'img/icon_32.png',
+ '64': 'img/icon_64.png',
+ } },
+ ];
+
+ (( ) => {
+ if ( browserAction.setIcon === undefined ) { return; }
+
+ // The global badge text and background color.
+ if ( browserAction.setBadgeBackgroundColor !== undefined ) {
+ browserAction.setBadgeBackgroundColor({ color: '#666666' });
+ }
+ if ( browserAction.setBadgeTextColor !== undefined ) {
+ browserAction.setBadgeTextColor({ color: '#FFFFFF' });
+ }
+
+ // As of 2018-05, benchmarks show that only Chromium benefits for sure
+ // from using ImageData.
+ //
+ // Chromium creates a new ImageData instance every call to setIcon
+ // with paths:
+ // https://cs.chromium.org/chromium/src/extensions/renderer/resources/set_icon.js?l=56&rcl=99be185c25738437ecfa0dafba72a26114196631
+ //
+ // Firefox uses an internal cache for each setIcon's paths:
+ // https://searchfox.org/mozilla-central/rev/5ff2d7683078c96e4b11b8a13674daded935aa44/browser/components/extensions/parent/ext-browserAction.js#631
+ if ( vAPI.webextFlavor.soup.has('chromium') === false ) { return; }
+
+ const imgs = [];
+ for ( let i = 0; i < icons.length; i++ ) {
+ for ( const key of Object.keys(icons[i].path) ) {
+ if ( parseInt(key, 10) >= 64 ) { continue; }
+ imgs.push({ i: i, p: key, cached: false });
+ }
+ }
+
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/296
+ const safeGetImageData = function(ctx, w, h) {
+ let data;
+ try {
+ data = ctx.getImageData(0, 0, w, h);
+ } catch(ex) {
+ }
+ return data;
+ };
+
+ const onLoaded = function() {
+ for ( const img of imgs ) {
+ if ( img.r.complete === false ) { return; }
+ }
+ const ctx = document.createElement('canvas')
+ .getContext('2d', { willReadFrequently: true });
+ const iconData = [ null, null ];
+ for ( const img of imgs ) {
+ if ( img.cached ) { continue; }
+ const w = img.r.naturalWidth, h = img.r.naturalHeight;
+ ctx.width = w; ctx.height = h;
+ ctx.clearRect(0, 0, w, h);
+ ctx.drawImage(img.r, 0, 0);
+ if ( iconData[img.i] === null ) { iconData[img.i] = {}; }
+ const imgData = safeGetImageData(ctx, w, h);
+ if (
+ imgData instanceof Object === false ||
+ imgData.data instanceof Uint8ClampedArray === false ||
+ imgData.data[0] !== 0 ||
+ imgData.data[1] !== 0 ||
+ imgData.data[2] !== 0 ||
+ imgData.data[3] !== 0
+ ) {
+ return;
+ }
+ iconData[img.i][img.p] = imgData;
+ img.cached = true;
+ }
+ for ( let i = 0; i < iconData.length; i++ ) {
+ if ( iconData[i] ) {
+ icons[i] = { imageData: iconData[i] };
+ }
+ }
+ };
+ for ( const img of imgs ) {
+ img.r = new Image();
+ img.r.addEventListener('load', onLoaded, { once: true });
+ img.r.src = icons[img.i].path[img.p];
+ }
+ })();
+
+ // parts: bit 0 = icon
+ // bit 1 = badge text
+ // bit 2 = badge color
+ // bit 3 = hide badge
+
+ vAPI.setIcon = async function(tabId, details) {
+ tabId = toTabId(tabId);
+ if ( tabId === 0 ) { return; }
+
+ const tab = await vAPI.tabs.get(tabId);
+ if ( tab === null ) { return; }
+
+ const hasUnprocessedRequest = vAPI.net && vAPI.net.hasUnprocessedRequest(tabId);
+ const { parts, state } = details;
+ const { badge, color } = hasUnprocessedRequest
+ ? { badge: '!', color: '#FC0' }
+ : details;
+
+ if ( browserAction.setIcon !== undefined ) {
+ if ( parts === undefined || (parts & 0b0001) !== 0 ) {
+ browserAction.setIcon(
+ Object.assign({ tabId: tab.id }, icons[state])
+ );
+ }
+ if ( (parts & 0b0010) !== 0 ) {
+ browserAction.setBadgeText({
+ tabId: tab.id,
+ text: (parts & 0b1000) === 0 ? badge : ''
+ });
+ }
+ if ( (parts & 0b0100) !== 0 ) {
+ browserAction.setBadgeBackgroundColor({ tabId: tab.id, color });
+ }
+ }
+
+ // Insert the badge text in the title if:
+ // - the platform does not support browserAction.setIcon(); OR
+ // - the rendering of the badge is disabled
+ if ( browserAction.setTitle !== undefined ) {
+ const title = titleTemplate.replace('{badge}',
+ state === 1 ? (badge !== '' ? badge : '0') : 'off'
+ );
+ browserAction.setTitle({ tabId: tab.id, title });
+ }
+
+ if ( vAPI.contextMenu instanceof Object ) {
+ vAPI.contextMenu.onMustUpdate(tabId);
+ }
+ };
+
+ vAPI.setDefaultIcon = function(flavor, text) {
+ if ( browserAction.setIcon === undefined ) { return; }
+ browserAction.setIcon({
+ path: {
+ '16': `img/icon_16${flavor}.png`,
+ '32': `img/icon_32${flavor}.png`,
+ '64': `img/icon_64${flavor}.png`,
+ }
+ });
+ browserAction.setBadgeText({ text });
+ browserAction.setBadgeBackgroundColor({
+ color: text === '!' ? '#FC0' : '#666'
+ });
+ };
+}
+
+browser.browserAction.onClicked.addListener(function(tab) {
+ vAPI.tabs.open({
+ select: true,
+ url: `popup-fenix.html?tabId=${tab.id}&intab=1`,
+ });
+});
+
+/******************************************************************************/
+/******************************************************************************/
+
+// https://github.com/uBlockOrigin/uBlock-issues/issues/710
+// uBO uses only ports to communicate with its auxiliary pages and
+// content scripts. Whether a message can trigger a privileged operation is
+// decided based on whether the port from which a message is received is
+// privileged, which is a status evaluated once, at port connection time.
+//
+// https://github.com/uBlockOrigin/uBlock-issues/issues/1992
+// If present, use MessageSender.origin to determine whether the port is
+// from a privileged page, otherwise use MessageSender.url.
+// MessageSender.origin is more reliable as it is not spoofable by a
+// compromised renderer.
+
+vAPI.messaging = {
+ ports: new Map(),
+ listeners: new Map(),
+ defaultHandler: null,
+ PRIVILEGED_ORIGIN: vAPI.getURL('').slice(0, -1),
+ NOOPFUNC: function(){},
+ UNHANDLED: 'vAPI.messaging.notHandled',
+
+ listen: function(details) {
+ this.listeners.set(details.name, {
+ fn: details.listener,
+ privileged: details.privileged === true
+ });
+ },
+
+ onPortDisconnect: function(port) {
+ this.ports.delete(port.name);
+ },
+
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/Port
+ // port.sender is always present for onConnect() listeners.
+ onPortConnect: function(port) {
+ port.onDisconnect.addListener(port =>
+ this.onPortDisconnect(port)
+ );
+ port.onMessage.addListener((request, port) =>
+ this.onPortMessage(request, port)
+ );
+ const portDetails = { port };
+ const sender = port.sender;
+ const { origin, tab, url } = sender;
+ portDetails.frameId = sender.frameId;
+ portDetails.frameURL = url;
+ portDetails.privileged = origin !== undefined
+ ? origin === this.PRIVILEGED_ORIGIN
+ : url.startsWith(this.PRIVILEGED_ORIGIN);
+ if ( tab ) {
+ portDetails.tabId = tab.id;
+ portDetails.tabURL = tab.url;
+ }
+ this.ports.set(port.name, portDetails);
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1652925#c24
+ port.sender = undefined;
+ },
+
+ setup: function(defaultHandler) {
+ if ( this.defaultHandler !== null ) { return; }
+
+ if ( typeof defaultHandler !== 'function' ) {
+ defaultHandler = function() {
+ return this.UNHANDLED;
+ };
+ }
+ this.defaultHandler = defaultHandler;
+
+ browser.runtime.onConnect.addListener(
+ port => this.onPortConnect(port)
+ );
+
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1392067
+ // Workaround: manually remove ports matching removed tab.
+ if (
+ vAPI.webextFlavor.soup.has('firefox') &&
+ vAPI.webextFlavor.major < 61
+ ) {
+ browser.tabs.onRemoved.addListener(tabId => {
+ for ( const { port, tabId: portTabId } of this.ports.values() ) {
+ if ( portTabId !== tabId ) { continue; }
+ this.onPortDisconnect(port);
+ }
+ });
+ }
+ },
+
+ onFrameworkMessage: function(request, port, callback) {
+ const portDetails = this.ports.get(port.name) || {};
+ const tabId = portDetails.tabId;
+ const msg = request.msg;
+ switch ( msg.what ) {
+ case 'localStorage': {
+ if ( portDetails.privileged !== true ) { break; }
+ const args = msg.args || [];
+ vAPI.localStorage[msg.fn](...args).then(result => {
+ callback(result);
+ });
+ break;
+ }
+ case 'userCSS':
+ if ( tabId === undefined ) { break; }
+ const promises = [];
+ if ( msg.add ) {
+ const details = {
+ code: undefined,
+ frameId: portDetails.frameId,
+ matchAboutBlank: true,
+ runAt: 'document_start',
+ };
+ for ( const cssText of msg.add ) {
+ details.code = cssText;
+ promises.push(vAPI.tabs.insertCSS(tabId, details));
+ }
+ }
+ if ( msg.remove ) {
+ const details = {
+ code: undefined,
+ frameId: portDetails.frameId,
+ matchAboutBlank: true,
+ };
+ for ( const cssText of msg.remove ) {
+ details.code = cssText;
+ promises.push(vAPI.tabs.removeCSS(tabId, details));
+ }
+ }
+ Promise.all(promises).then(( ) => {
+ callback();
+ });
+ break;
+ }
+ },
+
+ // Use a wrapper to avoid closure and to allow reuse.
+ CallbackWrapper: class {
+ constructor(messaging, port, msgId) {
+ this.messaging = messaging;
+ this.callback = this.proxy.bind(this); // bind once
+ this.init(port, msgId);
+ }
+ init(port, msgId) {
+ this.port = port;
+ this.msgId = msgId;
+ return this;
+ }
+ proxy(response) {
+ // https://github.com/chrisaljoudi/uBlock/issues/383
+ try {
+ this.port.postMessage({
+ msgId: this.msgId,
+ msg: response !== undefined ? response : null,
+ });
+ } catch (ex) {
+ this.messaging.onPortDisconnect(this.port);
+ }
+ // Store for reuse
+ this.port = null;
+ this.messaging.callbackWrapperJunkyard.push(this);
+ }
+ },
+
+ callbackWrapperJunkyard: [],
+
+ callbackWrapperFactory: function(port, msgId) {
+ return this.callbackWrapperJunkyard.length !== 0
+ ? this.callbackWrapperJunkyard.pop().init(port, msgId)
+ : new this.CallbackWrapper(this, port, msgId);
+ },
+
+ onPortMessage: function(request, port) {
+ // prepare response
+ let callback = this.NOOPFUNC;
+ if ( request.msgId !== undefined ) {
+ callback = this.callbackWrapperFactory(port, request.msgId).callback;
+ }
+
+ // Content process to main process: framework handler.
+ if ( request.channel === 'vapi' ) {
+ this.onFrameworkMessage(request, port, callback);
+ return;
+ }
+
+ // Auxiliary process to main process: specific handler
+ const portDetails = this.ports.get(port.name);
+ if ( portDetails === undefined ) { return; }
+
+ const listenerDetails = this.listeners.get(request.channel);
+ let r = this.UNHANDLED;
+ if (
+ (listenerDetails !== undefined) &&
+ (listenerDetails.privileged === false || portDetails.privileged)
+
+ ) {
+ r = listenerDetails.fn(request.msg, portDetails, callback);
+ }
+ if ( r !== this.UNHANDLED ) { return; }
+
+ // Auxiliary process to main process: default handler
+ if ( portDetails.privileged ) {
+ r = this.defaultHandler(request.msg, portDetails, callback);
+ if ( r !== this.UNHANDLED ) { return; }
+ }
+
+ // Auxiliary process to main process: no handler
+ ubolog(
+ `vAPI.messaging.onPortMessage > unhandled request: ${JSON.stringify(request.msg)}`,
+ request
+ );
+
+ // Need to callback anyways in case caller expected an answer, or
+ // else there is a memory leak on caller's side
+ callback();
+ },
+};
+
+/******************************************************************************/
+/******************************************************************************/
+
+// https://github.com/gorhill/uBlock/issues/3474
+// https://github.com/gorhill/uBlock/issues/2823
+// Foil ability of web pages to identify uBO through
+// its web accessible resources.
+// https://github.com/gorhill/uBlock/issues/3497
+// Prevent web pages from interfering with uBO's element picker
+// https://github.com/uBlockOrigin/uBlock-issues/issues/550
+// Support using a new secret for every network request.
+
+{
+ // Generate a 6-character alphanumeric string, thus one random value out
+ // of 36^6 = over 2x10^9 values.
+ const generateSecret = ( ) =>
+ (Math.floor(Math.random() * 2176782336) + 2176782336).toString(36).slice(1);
+
+ const root = vAPI.getURL('/');
+ const reSecret = /\?secret=(\w+)/;
+ const shortSecrets = [];
+ let lastShortSecretTime = 0;
+
+ // Long secrets are valid until revoked or uBO restarts. The realm is one
+ // value out of 36^18 = over 10^28 values.
+ const longSecrets = new Set();
+
+ const guard = details => {
+ const match = reSecret.exec(details.url);
+ if ( match === null ) { return { cancel: true }; }
+ const secret = match[1];
+ if ( longSecrets.has(secret) ) { return; }
+ const pos = shortSecrets.indexOf(secret);
+ if ( pos === -1 ) { return { cancel: true }; }
+ shortSecrets.splice(pos, 1);
+ };
+
+ browser.webRequest.onBeforeRequest.addListener(
+ guard,
+ {
+ urls: [ root + 'web_accessible_resources/*' ]
+ },
+ [ 'blocking' ]
+ );
+
+ vAPI.warSecret = {
+ short: ( ) => {
+ if ( shortSecrets.length !== 0 ) {
+ if ( (Date.now() - lastShortSecretTime) > 5000 ) {
+ shortSecrets.splice(0);
+ } else if ( shortSecrets.length > 256 ) {
+ shortSecrets.splice(0, shortSecrets.length - 192);
+ }
+ }
+ lastShortSecretTime = Date.now();
+ const secret = generateSecret();
+ shortSecrets.push(secret);
+ return secret;
+ },
+ long: previous => {
+ if ( previous !== undefined ) {
+ longSecrets.delete(previous);
+ }
+ const secret = `${generateSecret()}${generateSecret()}${generateSecret()}`;
+ longSecrets.add(secret);
+ return secret;
+ },
+ };
+}
+
+/******************************************************************************/
+
+vAPI.Net = class {
+ constructor() {
+ this.validTypes = new Set();
+ {
+ const wrrt = browser.webRequest.ResourceType;
+ for ( const typeKey in wrrt ) {
+ if ( wrrt.hasOwnProperty(typeKey) ) {
+ this.validTypes.add(wrrt[typeKey]);
+ }
+ }
+ }
+ this.suspendableListener = undefined;
+ this.deferredSuspendableListener = undefined;
+ this.listenerMap = new WeakMap();
+ this.suspendDepth = 0;
+ this.unprocessedTabs = new Map();
+
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ this.normalizeDetails(details);
+ if ( this.suspendDepth !== 0 && details.tabId >= 0 ) {
+ return this.suspendOneRequest(details);
+ }
+ return this.onBeforeSuspendableRequest(details);
+ },
+ this.denormalizeFilters({ urls: [ 'http://*/*', 'https://*/*' ] }),
+ [ 'blocking' ]
+ );
+
+ vAPI.setDefaultIcon('-loading', '');
+ }
+ setOptions(/* options */) {
+ }
+ normalizeDetails(/* details */) {
+ }
+ denormalizeFilters(filters) {
+ const urls = filters.urls || [ '<all_urls>' ];
+ let types = filters.types;
+ if ( Array.isArray(types) ) {
+ types = this.denormalizeTypes(types);
+ }
+ if (
+ (this.validTypes.has('websocket')) &&
+ (types === undefined || types.indexOf('websocket') !== -1) &&
+ (urls.indexOf('<all_urls>') === -1)
+ ) {
+ if ( urls.indexOf('ws://*/*') === -1 ) {
+ urls.push('ws://*/*');
+ }
+ if ( urls.indexOf('wss://*/*') === -1 ) {
+ urls.push('wss://*/*');
+ }
+ }
+ return { types, urls };
+ }
+ denormalizeTypes(types) {
+ return types;
+ }
+ canonicalNameFromHostname(/* hn */) {
+ }
+ addListener(which, clientListener, filters, options) {
+ const actualFilters = this.denormalizeFilters(filters);
+ const actualListener = this.makeNewListenerProxy(clientListener);
+ browser.webRequest[which].addListener(
+ actualListener,
+ actualFilters,
+ options
+ );
+ }
+ onBeforeSuspendableRequest(details) {
+ if ( this.suspendableListener !== undefined ) {
+ return this.suspendableListener(details);
+ }
+ this.onUnprocessedRequest(details);
+ }
+ setSuspendableListener(listener) {
+ for ( const [ tabId, requests ] of this.unprocessedTabs ) {
+ let i = requests.length;
+ while ( i-- ) {
+ const r = listener(requests[i]);
+ if ( r === undefined || r.cancel !== true ) {
+ requests.splice(i, 1);
+ }
+ }
+ if ( requests.length !== 0 ) { continue; }
+ this.unprocessedTabs.delete(tabId);
+ }
+ if ( this.unprocessedTabs.size !== 0 ) {
+ this.deferredSuspendableListener = listener;
+ listener = details => {
+ const { tabId, type } = details;
+ if ( type === 'main_frame' && this.unprocessedTabs.has(tabId) ) {
+ if ( this.removeUnprocessedRequest(tabId) ) {
+ return this.suspendableListener(details);
+ }
+ }
+ return this.deferredSuspendableListener(details);
+ };
+ }
+ this.suspendableListener = listener;
+ vAPI.setDefaultIcon('', '');
+ }
+ removeListener(which, clientListener) {
+ const actualListener = this.listenerMap.get(clientListener);
+ if ( actualListener === undefined ) { return; }
+ this.listenerMap.delete(clientListener);
+ browser.webRequest[which].removeListener(actualListener);
+ }
+ makeNewListenerProxy(clientListener) {
+ const actualListener = details => {
+ this.normalizeDetails(details);
+ return clientListener(details);
+ };
+ this.listenerMap.set(clientListener, actualListener);
+ return actualListener;
+ }
+ handlerBehaviorChanged() {
+ browser.webRequest.handlerBehaviorChanged();
+ }
+ onUnprocessedRequest(details) {
+ const { tabId } = details;
+ if ( tabId === -1 ) { return; }
+ if ( this.unprocessedTabs.size === 0 ) {
+ vAPI.setDefaultIcon('-loading', '!');
+ }
+ let requests = this.unprocessedTabs.get(tabId);
+ if ( requests === undefined ) {
+ this.unprocessedTabs.set(tabId, (requests = []));
+ }
+ requests.push(Object.assign({}, details));
+ }
+ hasUnprocessedRequest(tabId) {
+ if ( this.unprocessedTabs.size === 0 ) { return false; }
+ if ( tabId === undefined ) { return true; }
+ return this.unprocessedTabs.has(tabId);
+ }
+ removeUnprocessedRequest(tabId) {
+ if ( this.deferredSuspendableListener === undefined ) {
+ this.unprocessedTabs.clear();
+ return true;
+ }
+ if ( tabId !== undefined ) {
+ this.unprocessedTabs.delete(tabId);
+ } else {
+ this.unprocessedTabs.clear();
+ }
+ if ( this.unprocessedTabs.size !== 0 ) { return false; }
+ this.suspendableListener = this.deferredSuspendableListener;
+ this.deferredSuspendableListener = undefined;
+ return true;
+ }
+ suspendOneRequest() {
+ }
+ unsuspendAllRequests() {
+ }
+ suspend() {
+ this.suspendDepth += 1;
+ }
+ unsuspend({ all = false, discard = false } = {}) {
+ if ( this.suspendDepth === 0 ) { return; }
+ if ( all ) {
+ this.suspendDepth = 0;
+ } else {
+ this.suspendDepth -= 1;
+ }
+ if ( this.suspendDepth !== 0 ) { return; }
+ this.unsuspendAllRequests(discard);
+ }
+ static canSuspend() {
+ return false;
+ }
+};
+
+/******************************************************************************/
+/******************************************************************************/
+
+// To be defined by platform-specific code.
+
+vAPI.scriptletsInjector = (( ) => {
+ self.uBO_scriptletsInjected = '';
+}).toString();
+
+/******************************************************************************/
+/******************************************************************************/
+
+// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/contextMenus#Browser_compatibility
+// Firefox for Android does no support browser.contextMenus.
+
+vAPI.contextMenu = webext.menus && {
+ _callback: null,
+ _hash: '',
+ onMustUpdate: function() {},
+ setEntries: function(entries, callback) {
+ entries = entries || [];
+ const hash = entries.map(v => v.id).join();
+ if ( hash === this._hash ) { return; }
+ this._hash = hash;
+ webext.menus.removeAll();
+ for ( const entry of entries ) {
+ webext.menus.create(JSON.parse(JSON.stringify(entry)));
+ }
+ const n = entries.length;
+ callback = callback || null;
+ if ( callback === this._callback ) { return; }
+ if ( n !== 0 && callback !== null ) {
+ webext.menus.onClicked.addListener(callback);
+ this._callback = callback;
+ } else if ( n === 0 && this._callback !== null ) {
+ webext.menus.onClicked.removeListener(this._callback);
+ this._callback = null;
+ }
+ }
+};
+
+/******************************************************************************/
+/******************************************************************************/
+
+vAPI.commands = browser.commands;
+
+/******************************************************************************/
+/******************************************************************************/
+
+// https://github.com/gorhill/uBlock/issues/531
+// Storage area dedicated to admin settings. Read-only.
+
+// https://github.com/gorhill/uBlock/commit/43a5ed735b95a575a9339b6e71a1fcb27a99663b#commitcomment-13965030
+// Not all Chromium-based browsers support managed storage. Merely testing or
+// exception handling in this case does NOT work: I don't know why. The
+// extension on Opera ends up in a non-sensical state, whereas vAPI become
+// undefined out of nowhere. So only solution left is to test explicitly for
+// Opera.
+// https://github.com/gorhill/uBlock/issues/900
+// Also, UC Browser: http://www.upsieutoc.com/image/WXuH
+
+// https://github.com/uBlockOrigin/uAssets/discussions/16939
+// Use a cached version of admin settings, such that there is no blocking
+// call on `storage.managed`. The side effect is that any changes to admin
+// settings will require an extra extension restart to take effect.
+
+vAPI.adminStorage = (( ) => {
+ if ( webext.storage.managed instanceof Object === false ) {
+ return {
+ get: function() {
+ return Promise.resolve();
+ },
+ };
+ }
+ const cacheManagedStorage = async ( ) => {
+ let store;
+ try {
+ store = await webext.storage.managed.get();
+ } catch(ex) {
+ }
+ vAPI.storage.set({ cachedManagedStorage: store || {} });
+ };
+
+ return {
+ get: async function(key) {
+ let bin;
+ try {
+ bin = await vAPI.storage.get('cachedManagedStorage') || {};
+ if ( Object.keys(bin).length === 0 ) {
+ bin = await webext.storage.managed.get() || {};
+ } else {
+ bin = bin.cachedManagedStorage;
+ }
+ } catch(ex) {
+ bin = {};
+ }
+ cacheManagedStorage();
+ if ( key === undefined || key === null ) {
+ return bin;
+ }
+ if ( typeof key === 'string' && bin instanceof Object ) {
+ return bin[key];
+ }
+ const out = {};
+ if ( Array.isArray(key) ) {
+ for ( const k of key ) {
+ if ( bin[k] === undefined ) { continue; }
+ out[k] = bin[k];
+ }
+ return out;
+ }
+ for ( const [ k, v ] of Object.entries(key) ) {
+ out[k] = bin[k] !== undefined ? bin[k] : v;
+ }
+ return out;
+ }
+ };
+})();
+
+/******************************************************************************/
+/******************************************************************************/
+
+// A localStorage-like object which should be accessible from the
+// background page or auxiliary pages.
+//
+// https://github.com/uBlockOrigin/uBlock-issues/issues/899
+// Convert into asynchronous access API.
+//
+// Note: vAPI.localStorage should already be defined with the client-side
+// implementation at this point, but we override with the
+// background-side implementation.
+vAPI.localStorage = {
+ start: async function() {
+ if ( this.cache instanceof Promise ) { return this.cache; }
+ if ( this.cache instanceof Object ) { return this.cache; }
+ this.cache = vAPI.storage.get('localStorage').then(bin => {
+ this.cache = bin instanceof Object &&
+ bin.localStorage instanceof Object
+ ? bin.localStorage
+ : {};
+ });
+ return this.cache;
+ },
+ clear: function() {
+ this.cache = {};
+ return vAPI.storage.set({ localStorage: this.cache });
+ },
+ getItem: function(key) {
+ if ( this.cache instanceof Object === false ) {
+ console.info(`localStorage.getItem('${key}') not ready`);
+ return null;
+ }
+ const value = this.cache[key];
+ return value !== undefined ? value : null;
+ },
+ getItemAsync: async function(key) {
+ await this.start();
+ const value = this.cache[key];
+ return value !== undefined ? value : null;
+ },
+ removeItem: async function(key) {
+ this.setItem(key);
+ },
+ setItem: async function(key, value = undefined) {
+ await this.start();
+ if ( value === this.cache[key] ) { return; }
+ this.cache[key] = value;
+ return vAPI.storage.set({ localStorage: this.cache });
+ },
+ cache: undefined,
+};
+
+vAPI.localStorage.start();
+
+/******************************************************************************/
+/******************************************************************************/
+
+// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/sync
+
+vAPI.cloud = (( ) => {
+ // Not all platforms support `webext.storage.sync`.
+ if ( webext.storage.sync instanceof Object === false ) { return; }
+
+ // Currently, only Chromium supports the following constants -- these
+ // values will be assumed for platforms which do not define them.
+ // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage/sync
+ // > You can store up to 100KB of data using this API
+ const MAX_ITEMS =
+ webext.storage.sync.MAX_ITEMS || 512;
+ const QUOTA_BYTES =
+ webext.storage.sync.QUOTA_BYTES || 102400;
+ const QUOTA_BYTES_PER_ITEM =
+ webext.storage.sync.QUOTA_BYTES_PER_ITEM || 8192;
+
+ const chunkCountPerFetch = 16; // Must be a power of 2
+ const maxChunkCountPerItem = Math.floor(MAX_ITEMS * 0.75) & ~(chunkCountPerFetch - 1);
+
+ // https://github.com/gorhill/uBlock/issues/3006
+ // For Firefox, we will use a lower ratio to allow for more overhead for
+ // the infrastructure. Unfortunately this leads to less usable space for
+ // actual data, but all of this is provided for free by browser vendors,
+ // so we need to accept and deal with these limitations.
+ const evalMaxChunkSize = function() {
+ return Math.floor(
+ QUOTA_BYTES_PER_ITEM *
+ (vAPI.webextFlavor.soup.has('firefox') ? 0.6 : 0.75)
+ );
+ };
+
+ let maxChunkSize = evalMaxChunkSize();
+
+ // The real actual webextFlavor value may not be set in stone, so listen
+ // for possible future changes.
+ window.addEventListener('webextFlavor', function() {
+ maxChunkSize = evalMaxChunkSize();
+ }, { once: true });
+
+ const options = {
+ defaultDeviceName: window.navigator.platform,
+ deviceName: undefined,
+ };
+
+ vAPI.localStorage.getItemAsync('deviceName').then(value => {
+ options.deviceName = value;
+ });
+
+ // This is used to find out a rough count of how many chunks exists:
+ // We "poll" at specific index in order to get a rough idea of how
+ // large is the stored string.
+ // This allows reading a single item with only 2 sync operations -- a
+ // good thing given chrome.storage.sync.MAX_WRITE_OPERATIONS_PER_MINUTE
+ // and chrome.storage.sync.MAX_WRITE_OPERATIONS_PER_HOUR.
+
+ const getCoarseChunkCount = async function(datakey) {
+ const keys = {};
+ for ( let i = 0; i < maxChunkCountPerItem; i += 16 ) {
+ keys[datakey + i.toString()] = '';
+ }
+ let bin;
+ try {
+ bin = await webext.storage.sync.get(keys);
+ } catch (reason) {
+ return String(reason);
+ }
+ let chunkCount = 0;
+ for ( let i = 0; i < maxChunkCountPerItem; i += 16 ) {
+ if ( bin[datakey + i.toString()] === '' ) { break; }
+ chunkCount = i + 16;
+ }
+ return chunkCount;
+ };
+
+ const deleteChunks = async function(datakey, start) {
+ const keys = [];
+
+ const n = await getCoarseChunkCount(datakey);
+ for ( let i = start; i < n; i++ ) {
+ keys.push(datakey + i.toString());
+ }
+ if ( keys.length !== 0 ) {
+ webext.storage.sync.remove(keys);
+ }
+ };
+
+ const push = async function(details) {
+ const { datakey, data, encode } = details;
+ if (
+ data === undefined ||
+ typeof data === 'string' && data === ''
+ ) {
+ return deleteChunks(datakey, 0);
+ }
+ const item = {
+ source: options.deviceName || options.defaultDeviceName,
+ tstamp: Date.now(),
+ data,
+ };
+ const json = JSON.stringify(item);
+ const encoded = encode instanceof Function
+ ? await encode(json)
+ : json;
+
+ // Chunkify taking into account QUOTA_BYTES_PER_ITEM:
+ // https://developer.chrome.com/extensions/storage#property-sync
+ // "The maximum size (in bytes) of each individual item in sync
+ // "storage, as measured by the JSON stringification of its value
+ // "plus its key length."
+ const bin = {};
+ const chunkCount = Math.ceil(encoded.length / maxChunkSize);
+ for ( let i = 0; i < chunkCount; i++ ) {
+ bin[datakey + i.toString()]
+ = encoded.substr(i * maxChunkSize, maxChunkSize);
+ }
+ bin[datakey + chunkCount.toString()] = ''; // Sentinel
+
+ // Remove potentially unused trailing chunks before storing the data,
+ // this will free storage space which could otherwise cause the push
+ // operation to fail.
+ try {
+ await deleteChunks(datakey, chunkCount + 1);
+ } catch (reason) {
+ }
+
+ // Push the data to browser-provided cloud storage.
+ try {
+ await webext.storage.sync.set(bin);
+ } catch (reason) {
+ return String(reason);
+ }
+ };
+
+ const pull = async function(details) {
+ const { datakey, decode } = details;
+
+ const result = await getCoarseChunkCount(datakey);
+ if ( typeof result !== 'number' ) {
+ return result;
+ }
+ const chunkKeys = {};
+ for ( let i = 0; i < result; i++ ) {
+ chunkKeys[datakey + i.toString()] = '';
+ }
+
+ let bin;
+ try {
+ bin = await webext.storage.sync.get(chunkKeys);
+ } catch (reason) {
+ return String(reason);
+ }
+
+ // Assemble chunks into a single string.
+ // https://www.reddit.com/r/uMatrix/comments/8lc9ia/my_rules_tab_hangs_with_cloud_storage_support/
+ // Explicit sentinel is not necessarily present: this can
+ // happen when the number of chunks is a multiple of
+ // chunkCountPerFetch. Hence why we must also test against
+ // undefined.
+ let encoded = [];
+ let i = 0;
+ for (;;) {
+ const slice = bin[datakey + i.toString()];
+ if ( slice === '' || slice === undefined ) { break; }
+ encoded.push(slice);
+ i += 1;
+ }
+ encoded = encoded.join('');
+ const json = decode instanceof Function
+ ? await decode(encoded)
+ : encoded;
+ let entry = null;
+ try {
+ entry = JSON.parse(json);
+ } catch(ex) {
+ }
+ return entry;
+ };
+
+ const used = async function(datakey) {
+ if ( webext.storage.sync.getBytesInUse instanceof Function === false ) {
+ return;
+ }
+ const coarseCount = await getCoarseChunkCount(datakey);
+ if ( typeof coarseCount !== 'number' ) { return; }
+ const keys = [];
+ for ( let i = 0; i < coarseCount; i++ ) {
+ keys.push(`${datakey}${i}`);
+ }
+ let results;
+ try {
+ results = await Promise.all([
+ webext.storage.sync.getBytesInUse(keys),
+ webext.storage.sync.getBytesInUse(null),
+ ]);
+ } catch(ex) {
+ }
+ if ( Array.isArray(results) === false ) { return; }
+ return { used: results[0], total: results[1], max: QUOTA_BYTES };
+ };
+
+ const getOptions = function(callback) {
+ if ( typeof callback !== 'function' ) { return; }
+ callback(options);
+ };
+
+ const setOptions = function(details, callback) {
+ if ( typeof details !== 'object' || details === null ) { return; }
+
+ if ( typeof details.deviceName === 'string' ) {
+ vAPI.localStorage.setItem('deviceName', details.deviceName);
+ options.deviceName = details.deviceName;
+ }
+
+ getOptions(callback);
+ };
+
+ return { push, pull, used, getOptions, setOptions };
+})();
+
+/******************************************************************************/
+/******************************************************************************/
+
+vAPI.alarms = browser.alarms || {
+ create() {
+ },
+ clear() {
+ },
+ onAlarm: {
+ addListener() {
+ }
+ }
+};
+
+/******************************************************************************/
diff --git a/platform/common/vapi-client.js b/platform/common/vapi-client.js
new file mode 100644
index 0000000..9375e88
--- /dev/null
+++ b/platform/common/vapi-client.js
@@ -0,0 +1,251 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-2015 The uBlock Origin authors
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+// For non-background page
+
+/* globals browser */
+
+'use strict';
+
+/******************************************************************************/
+
+// https://github.com/chrisaljoudi/uBlock/issues/456
+// Skip if already injected.
+
+// >>>>>>>> start of HUGE-IF-BLOCK
+if (
+ typeof vAPI === 'object' &&
+ vAPI.randomToken instanceof Function === false
+) {
+
+/******************************************************************************/
+/******************************************************************************/
+
+vAPI.randomToken = function() {
+ const n = Math.random();
+ return String.fromCharCode(n * 25 + 97) +
+ Math.floor(
+ (0.25 + n * 0.75) * Number.MAX_SAFE_INTEGER
+ ).toString(36).slice(-8);
+};
+
+vAPI.sessionId = vAPI.randomToken();
+vAPI.setTimeout = vAPI.setTimeout || self.setTimeout.bind(self);
+
+/******************************************************************************/
+
+vAPI.shutdown = {
+ jobs: [],
+ add: function(job) {
+ this.jobs.push(job);
+ },
+ exec: function() {
+ // Shutdown asynchronously, to ensure shutdown jobs are called from
+ // the top context.
+ self.requestIdleCallback(( ) => {
+ const jobs = this.jobs.slice();
+ this.jobs.length = 0;
+ while ( jobs.length !== 0 ) {
+ (jobs.pop())();
+ }
+ });
+ },
+ remove: function(job) {
+ let pos;
+ while ( (pos = this.jobs.indexOf(job)) !== -1 ) {
+ this.jobs.splice(pos, 1);
+ }
+ }
+};
+
+/******************************************************************************/
+
+vAPI.messaging = {
+ port: null,
+ portTimer: null,
+ portTimerDelay: 10000,
+ msgIdGenerator: 1,
+ pending: new Map(),
+ shuttingDown: false,
+
+ shutdown: function() {
+ this.shuttingDown = true;
+ this.destroyPort();
+ },
+
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/403
+ // Spurious disconnection can happen, so do not consider such events
+ // as world-ending, i.e. stay around. Except for embedded frames.
+
+ disconnectListener: function() {
+ void browser.runtime.lastError;
+ this.port = null;
+ if ( window !== window.top ) {
+ vAPI.shutdown.exec();
+ } else {
+ this.destroyPort();
+ }
+ },
+ disconnectListenerBound: null,
+
+ // 2020-09-01:
+ // In Firefox, `details instanceof Object` resolves to `false` despite
+ // `details` being a valid object. Consequently, falling back to use
+ // `typeof details`.
+ // This is an issue which surfaced when the element picker code was
+ // revisited to isolate the picker dialog DOM from the page DOM.
+ messageListener: function(details) {
+ if ( typeof details !== 'object' || details === null ) { return; }
+
+ // Response to specific message previously sent
+ if ( details.msgId !== undefined ) {
+ const resolver = this.pending.get(details.msgId);
+ if ( resolver !== undefined ) {
+ this.pending.delete(details.msgId);
+ resolver(details.msg);
+ return;
+ }
+ }
+ },
+ messageListenerBound: null,
+
+ canDestroyPort: function() {
+ return this.pending.size === 0;
+ },
+
+ portPoller: function() {
+ this.portTimer = null;
+ if ( this.port !== null && this.canDestroyPort() ) {
+ return this.destroyPort();
+ }
+ this.portTimer = vAPI.setTimeout(this.portPollerBound, this.portTimerDelay);
+ this.portTimerDelay = Math.min(this.portTimerDelay * 2, 60 * 60 * 1000);
+ },
+ portPollerBound: null,
+
+ destroyPort: function() {
+ if ( this.portTimer !== null ) {
+ clearTimeout(this.portTimer);
+ this.portTimer = null;
+ }
+ const port = this.port;
+ if ( port !== null ) {
+ port.disconnect();
+ port.onMessage.removeListener(this.messageListenerBound);
+ port.onDisconnect.removeListener(this.disconnectListenerBound);
+ this.port = null;
+ }
+ // service pending callbacks
+ if ( this.pending.size !== 0 ) {
+ const pending = this.pending;
+ this.pending = new Map();
+ for ( const resolver of pending.values() ) {
+ resolver();
+ }
+ }
+ },
+
+ createPort: function() {
+ if ( this.shuttingDown ) { return null; }
+ if ( this.messageListenerBound === null ) {
+ this.messageListenerBound = this.messageListener.bind(this);
+ this.disconnectListenerBound = this.disconnectListener.bind(this);
+ this.portPollerBound = this.portPoller.bind(this);
+ }
+ try {
+ this.port = browser.runtime.connect({name: vAPI.sessionId}) || null;
+ } catch (ex) {
+ this.port = null;
+ }
+ // Not having a valid port at this point means the main process is
+ // not available: no point keeping the content scripts alive.
+ if ( this.port === null ) {
+ vAPI.shutdown.exec();
+ return null;
+ }
+ this.port.onMessage.addListener(this.messageListenerBound);
+ this.port.onDisconnect.addListener(this.disconnectListenerBound);
+ this.portTimerDelay = 10000;
+ if ( this.portTimer === null ) {
+ this.portTimer = vAPI.setTimeout(
+ this.portPollerBound,
+ this.portTimerDelay
+ );
+ }
+ return this.port;
+ },
+
+ getPort: function() {
+ return this.port !== null ? this.port : this.createPort();
+ },
+
+ send: function(channel, msg) {
+ // Too large a gap between the last request and the last response means
+ // the main process is no longer reachable: memory leaks and bad
+ // performance become a risk -- especially for long-lived, dynamic
+ // pages. Guard against this.
+ if ( this.pending.size > 50 ) {
+ vAPI.shutdown.exec();
+ }
+ const port = this.getPort();
+ if ( port === null ) {
+ return Promise.resolve();
+ }
+ const msgId = this.msgIdGenerator++;
+ const promise = new Promise(resolve => {
+ this.pending.set(msgId, resolve);
+ });
+ port.postMessage({ channel, msgId, msg });
+ return promise;
+ },
+};
+
+vAPI.shutdown.add(( ) => {
+ vAPI.messaging.shutdown();
+ window.vAPI = undefined;
+});
+
+/******************************************************************************/
+/******************************************************************************/
+
+}
+// <<<<<<<< end of HUGE-IF-BLOCK
+
+
+
+
+
+
+
+
+/*******************************************************************************
+
+ DO NOT:
+ - Remove the following code
+ - Add code beyond the following code
+ Reason:
+ - https://github.com/gorhill/uBlock/pull/3721
+ - uBO never uses the return value from injected content scripts
+
+**/
+
+void 0;
diff --git a/platform/common/vapi-common.js b/platform/common/vapi-common.js
new file mode 100644
index 0000000..3b74820
--- /dev/null
+++ b/platform/common/vapi-common.js
@@ -0,0 +1,294 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-2015 The uBlock Origin authors
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+// For background page or non-background pages
+
+/* global browser */
+
+'use strict';
+
+/******************************************************************************/
+/******************************************************************************/
+
+vAPI.T0 = Date.now();
+
+/******************************************************************************/
+
+vAPI.setTimeout = vAPI.setTimeout || self.setTimeout.bind(self);
+
+vAPI.defer = {
+ create(callback) {
+ return new this.Client(callback);
+ },
+ once(delay, ...args) {
+ const delayInMs = vAPI.defer.normalizeDelay(delay);
+ return new Promise(resolve => {
+ vAPI.setTimeout(
+ (...args) => { resolve(...args); },
+ delayInMs,
+ ...args
+ );
+ });
+ },
+ Client: class {
+ constructor(callback) {
+ this.timer = null;
+ this.type = 0;
+ this.callback = callback;
+ }
+ on(delay, ...args) {
+ if ( this.timer !== null ) { return; }
+ const delayInMs = vAPI.defer.normalizeDelay(delay);
+ this.type = 0;
+ this.timer = vAPI.setTimeout(( ) => {
+ this.timer = null;
+ this.callback(...args);
+ }, delayInMs || 1);
+ }
+ offon(delay, ...args) {
+ this.off();
+ this.on(delay, ...args);
+ }
+ onvsync(delay, ...args) {
+ if ( this.timer !== null ) { return; }
+ const delayInMs = vAPI.defer.normalizeDelay(delay);
+ if ( delayInMs !== 0 ) {
+ this.type = 0;
+ this.timer = vAPI.setTimeout(( ) => {
+ this.timer = null;
+ this.onraf(...args);
+ }, delayInMs);
+ } else {
+ this.onraf(...args);
+ }
+ }
+ onidle(delay, options, ...args) {
+ if ( this.timer !== null ) { return; }
+ const delayInMs = vAPI.defer.normalizeDelay(delay);
+ if ( delayInMs !== 0 ) {
+ this.type = 0;
+ this.timer = vAPI.setTimeout(( ) => {
+ this.timer = null;
+ this.onric(options, ...args);
+ }, delayInMs);
+ } else {
+ this.onric(options, ...args);
+ }
+ }
+ off() {
+ if ( this.timer === null ) { return; }
+ switch ( this.type ) {
+ case 0:
+ self.clearTimeout(this.timer);
+ break;
+ case 1:
+ self.cancelAnimationFrame(this.timer);
+ break;
+ case 2:
+ self.cancelIdleCallback(this.timer);
+ break;
+ default:
+ break;
+ }
+ this.timer = null;
+ }
+ onraf(...args) {
+ if ( this.timer !== null ) { return; }
+ this.type = 1;
+ this.timer = requestAnimationFrame(( ) => {
+ this.timer = null;
+ this.callback(...args);
+ });
+ }
+ onric(options, ...args) {
+ if ( this.timer !== null ) { return; }
+ this.type = 2;
+ this.timer = self.requestIdleCallback(deadline => {
+ this.timer = null;
+ this.callback(deadline, ...args);
+ }, options);
+ }
+ ongoing() {
+ return this.timer !== null;
+ }
+ },
+ normalizeDelay(delay = 0) {
+ if ( typeof delay === 'object' ) {
+ if ( delay.sec !== undefined ) {
+ return delay.sec * 1000;
+ } else if ( delay.min !== undefined ) {
+ return delay.min * 60000;
+ } else if ( delay.hr !== undefined ) {
+ return delay.hr * 3600000;
+ }
+ }
+ return delay;
+ }
+};
+
+/******************************************************************************/
+
+vAPI.webextFlavor = {
+ major: 0,
+ soup: new Set(),
+ get env() {
+ return Array.from(this.soup);
+ }
+};
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1858743
+// Add support for native `:has()` for Firefox 121+
+
+(( ) => {
+ const ua = navigator.userAgent;
+ const flavor = vAPI.webextFlavor;
+ const soup = flavor.soup;
+ const dispatch = function() {
+ window.dispatchEvent(new CustomEvent('webextFlavor'));
+ };
+
+ // This is always true.
+ soup.add('ublock').add('webext');
+
+ // Whether this is a dev build.
+ if ( /^\d+\.\d+\.\d+\D/.test(browser.runtime.getManifest().version) ) {
+ soup.add('devbuild');
+ }
+
+ if ( /\bMobile\b/.test(ua) ) {
+ soup.add('mobile');
+ }
+
+ if ( CSS.supports('selector(a:has(b))') ) {
+ soup.add('native_css_has');
+ }
+
+ // Order of tests is important
+ if ( browser.runtime.getURL('').startsWith('moz-extension://') ) {
+ soup.add('firefox')
+ .add('user_stylesheet')
+ .add('html_filtering');
+ const match = /Firefox\/(\d+)/.exec(ua);
+ flavor.major = match && parseInt(match[1], 10) || 115;
+ } else {
+ const match = /\bChrom(?:e|ium)\/(\d+)/.exec(ua);
+ if ( match !== null ) {
+ soup.add('chromium')
+ .add('user_stylesheet');
+ }
+ flavor.major = match && parseInt(match[1], 10) || 120;
+ }
+
+ // Don't starve potential listeners
+ vAPI.setTimeout(dispatch, 97);
+})();
+
+/******************************************************************************/
+
+vAPI.download = function(details) {
+ if ( !details.url ) { return; }
+ const a = document.createElement('a');
+ a.href = details.url;
+ a.setAttribute('download', details.filename || '');
+ a.setAttribute('type', 'text/plain');
+ a.dispatchEvent(new MouseEvent('click'));
+};
+
+/******************************************************************************/
+
+vAPI.getURL = browser.runtime.getURL;
+
+/******************************************************************************/
+
+// https://github.com/gorhill/uBlock/issues/3057
+// - webNavigation.onCreatedNavigationTarget become broken on Firefox when we
+// try to make the popup panel close itself using the original
+// `window.open('', '_self').close()`.
+
+vAPI.closePopup = function() {
+ if ( vAPI.webextFlavor.soup.has('firefox') ) {
+ window.close();
+ return;
+ }
+
+ // TODO: try to figure why this was used instead of a plain window.close().
+ // https://github.com/gorhill/uBlock/commit/b301ac031e0c2e9a99cb6f8953319d44e22f33d2#diff-bc664f26b9c453e0d43a9379e8135c6a
+ window.open('', '_self').close();
+};
+
+/******************************************************************************/
+
+// A localStorage-like object which should be accessible from the
+// background page or auxiliary pages.
+//
+// https://github.com/uBlockOrigin/uBlock-issues/issues/899
+// Convert into asynchronous access API.
+
+vAPI.localStorage = {
+ clear: function() {
+ vAPI.messaging.send('vapi', {
+ what: 'localStorage',
+ fn: 'clear',
+ });
+ },
+ getItemAsync: function(key) {
+ return vAPI.messaging.send('vapi', {
+ what: 'localStorage',
+ fn: 'getItemAsync',
+ args: [ key ],
+ });
+ },
+ removeItem: function(key) {
+ return vAPI.messaging.send('vapi', {
+ what: 'localStorage',
+ fn: 'removeItem',
+ args: [ key ],
+ });
+ },
+ setItem: function(key, value = undefined) {
+ return vAPI.messaging.send('vapi', {
+ what: 'localStorage',
+ fn: 'setItem',
+ args: [ key, value ]
+ });
+ },
+};
+
+
+
+
+
+
+
+
+/*******************************************************************************
+
+ DO NOT:
+ - Remove the following code
+ - Add code beyond the following code
+ Reason:
+ - https://github.com/gorhill/uBlock/pull/3721
+ - uBO never uses the return value from injected content scripts
+
+**/
+
+void 0;
diff --git a/platform/common/vapi.js b/platform/common/vapi.js
new file mode 100644
index 0000000..d10ae66
--- /dev/null
+++ b/platform/common/vapi.js
@@ -0,0 +1,89 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2017-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+/* global HTMLDocument, XMLDocument */
+
+// For background page, auxiliary pages, and content scripts.
+
+/******************************************************************************/
+
+if (
+ self.browser instanceof Object &&
+ self.browser instanceof Element === false
+) {
+ self.chrome = self.browser;
+} else {
+ self.browser = self.chrome;
+}
+
+/******************************************************************************/
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1408996#c9
+var vAPI = self.vAPI; // jshint ignore:line
+
+// https://github.com/chrisaljoudi/uBlock/issues/464
+// https://github.com/chrisaljoudi/uBlock/issues/1528
+// A XMLDocument can be a valid HTML document.
+
+// https://github.com/gorhill/uBlock/issues/1124
+// Looks like `contentType` is on track to be standardized:
+// https://dom.spec.whatwg.org/#concept-document-content-type
+
+// https://forums.lanik.us/viewtopic.php?f=64&t=31522
+// Skip text/plain documents.
+
+if (
+ (
+ document instanceof HTMLDocument ||
+ document instanceof XMLDocument &&
+ document.createElement('div') instanceof HTMLDivElement
+ ) &&
+ (
+ /^image\/|^text\/plain/.test(document.contentType || '') === false
+ ) &&
+ (
+ self.vAPI instanceof Object === false || vAPI.uBO !== true
+ )
+) {
+ vAPI = self.vAPI = { uBO: true };
+}
+
+
+
+
+
+
+
+
+/*******************************************************************************
+
+ DO NOT:
+ - Remove the following code
+ - Add code beyond the following code
+ Reason:
+ - https://github.com/gorhill/uBlock/pull/3721
+ - uBO never uses the return value from injected content scripts
+
+**/
+
+void 0;
diff --git a/platform/dig/package.json b/platform/dig/package.json
new file mode 100644
index 0000000..d15b78c
--- /dev/null
+++ b/platform/dig/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "@gorhill/ubo-dig",
+ "version": "0.1.0",
+ "description": "To investigate code changes (not for publication)",
+ "type": "module",
+ "main": "index.js",
+ "scripts": {
+ "build": "node build.js",
+ "snfe": "node snfe.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/gorhill/uBlock.git"
+ },
+ "author": "Raymond Hill",
+ "license": "GPL-3.0-or-later",
+ "bugs": {
+ "url": "https://github.com/gorhill/uBlock/issues"
+ },
+ "homepage": "https://github.com/gorhill/uBlock#readme",
+ "engines": {
+ "node": ">=14.0.0",
+ "npm": ">=6.14.4"
+ },
+ "devDependencies": {
+ "scaling-palm-tree": "github:mjethani/scaling-palm-tree#15cf1ab"
+ }
+}
diff --git a/platform/dig/snfe.js b/platform/dig/snfe.js
new file mode 100644
index 0000000..7090828
--- /dev/null
+++ b/platform/dig/snfe.js
@@ -0,0 +1,389 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* eslint-disable-next-line no-redeclare */
+/* globals process */
+
+'use strict';
+
+/******************************************************************************/
+
+import { strict as assert } from 'assert';
+import { createRequire } from 'module';
+import { readFile, writeFile, mkdir } from 'fs/promises';
+import { dirname } from 'path';
+import { fileURLToPath } from 'url';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+import { enableWASM, StaticNetFilteringEngine } from './index.js';
+
+/******************************************************************************/
+
+const FLAGS = process.argv.slice(2);
+const COMPARE = FLAGS.includes('compare');
+const MAXCOST = FLAGS.includes('maxcost');
+const MEDCOST = FLAGS.includes('medcost');
+const MINCOST = FLAGS.includes('mincost');
+const MODIFIERS = FLAGS.includes('modifiers');
+const RECORD = FLAGS.includes('record');
+const WASM = FLAGS.includes('wasm');
+const NEED_RESULTS = COMPARE || MAXCOST || MEDCOST || MINCOST || RECORD;
+
+// This maps puppeteer types to WebRequest types
+const WEBREQUEST_OPTIONS = {
+ // Consider document requests as sub_document. This is because the request
+ // dataset does not contain sub_frame or main_frame but only 'document' and
+ // different blockers have different behaviours.
+ document: 'sub_frame',
+ stylesheet: 'stylesheet',
+ image: 'image',
+ media: 'media',
+ font: 'font',
+ script: 'script',
+ xhr: 'xmlhttprequest',
+ fetch: 'xmlhttprequest',
+ websocket: 'websocket',
+ ping: 'ping',
+ // other
+ other: 'other',
+ eventsource: 'other',
+ manifest: 'other',
+ texttrack: 'other',
+};
+
+/******************************************************************************/
+
+function nanoToMilli(bigint) {
+ return (Number(bigint) / 1000000).toFixed(2) + ' ms';
+}
+
+function nanoToMicro(bigint) {
+ return (Number(bigint) / 1000).toFixed(2) + ' µs';
+}
+
+async function read(path) {
+ return readFile(path, 'utf8');
+}
+
+async function write(path, data) {
+ await mkdir(dirname(path), { recursive: true });
+ return writeFile(path, data, 'utf8');
+}
+
+/******************************************************************************/
+
+async function matchRequests(engine, requests) {
+ const results = [];
+ const details = {
+ r: 0,
+ f: undefined,
+ type: '',
+ url: '',
+ originURL: '',
+ t: 0,
+ };
+
+ let notBlockedCount = 0;
+ let blockedCount = 0;
+ let unblockedCount = 0;
+
+ const start = process.hrtime.bigint();
+
+ for ( let i = 0; i < requests.length; i++ ) {
+ const request = requests[i];
+ const reqstart = process.hrtime.bigint();
+ details.type = WEBREQUEST_OPTIONS[request.cpt];
+ details.url = request.url;
+ details.originURL = request.frameUrl;
+ const r = engine.matchRequest(details);
+ if ( r === 0 ) {
+ notBlockedCount += 1;
+ } else if ( r === 1 ) {
+ blockedCount += 1;
+ } else {
+ unblockedCount += 1;
+ }
+ if ( NEED_RESULTS !== true ) { continue; }
+ const reqstop = process.hrtime.bigint();
+ details.r = r;
+ details.f = r !== 0 ? engine.toLogData().raw : undefined;
+ details.t = Math.round(Number(reqstop - reqstart) / 10) / 100;
+ results.push([ i, Object.assign({}, details) ]);
+ }
+
+ const stop = process.hrtime.bigint();
+
+ console.log(`Matched ${requests.length} requests in ${nanoToMilli(stop - start)}`);
+ console.log(`\tNot blocked: ${notBlockedCount} requests`);
+ console.log(`\tBlocked: ${blockedCount} requests`);
+ console.log(`\tUnblocked: ${unblockedCount} requests`);
+ console.log(`\tAverage: ${nanoToMicro((stop - start) / BigInt(requests.length))} per request`);
+
+ if ( RECORD ) {
+ write('data/snfe.json', JSON.stringify(results, null, 2));
+ }
+
+ if ( COMPARE ) {
+ const diffs = await compare(results);
+ if ( Array.isArray(diffs) ) {
+ write('data/snfe.diffs.json', JSON.stringify(diffs, null, 2));
+ }
+ console.log(`Compare: ${diffs.length} requests differ`);
+ }
+
+ if ( MAXCOST ) {
+ const costly = results.slice().sort((a,b) => b[1].t - a[1].t).slice(0, 1000);
+ write('data/snfe.maxcost.json', JSON.stringify(costly, null, 2));
+ }
+
+ if ( MEDCOST ) {
+ const median = results.length >>> 1;
+ const costly = results.slice().sort((a,b) => b[1].t - a[1].t).slice(median - 500, median + 500);
+ write('data/snfe.medcost.json', JSON.stringify(costly, null, 2));
+ }
+
+ if ( MINCOST ) {
+ const costly = results.slice().sort((a,b) => b[1].t - a[1].t).slice(-1000);
+ write('data/snfe.mincost.json', JSON.stringify(costly, null, 2));
+ }
+}
+
+async function compare(results) {
+ let before;
+ try {
+ const raw = await read('data/snfe.json');
+ before = new Map(JSON.parse(raw));
+ } catch(ex) {
+ console.log(ex);
+ console.log('Nothing to compare');
+ return;
+ }
+ const after = new Map(results);
+ const diffs = [];
+ for ( let i = 0; i < results.length; i++ ) {
+ const a = before.get(i);
+ const b = after.get(i);
+ if ( a.r === b.r ) { continue; }
+ diffs.push([ i, {
+ type: a.type,
+ url: a.url,
+ originURL: a.originURL,
+ before: { r: a.r, f: a.f, t: a.t },
+ after: { r: b.r, f: b.f, t: b.t },
+ }]);
+ }
+ return diffs;
+}
+
+/******************************************************************************/
+
+async function matchRequestModifiers(engine, requests) {
+ const results = {
+ 'csp': [],
+ 'redirect-rule': [],
+ 'removeparam': [],
+ };
+
+ const details = {
+ f: undefined,
+ type: '',
+ url: '',
+ originURL: '',
+ t: 0,
+ };
+
+ let modifiedCount = 0;
+
+ const start = process.hrtime.bigint();
+ for ( let i = 0; i < requests.length; i++ ) {
+ const request = requests[i];
+ details.type = WEBREQUEST_OPTIONS[request.cpt];
+ details.url = request.url;
+ details.originURL = request.frameUrl;
+ const r = engine.matchRequest(details);
+ let modified = false;
+ if ( r !== 1 && details.type === 'sub_frame' ) {
+ const reqstart = process.hrtime.bigint();
+ const directives = engine.matchAndFetchModifiers(details, 'csp');
+ if ( directives !== undefined ) {
+ modified = true;
+ if ( NEED_RESULTS ) {
+ const reqstop = process.hrtime.bigint();
+ details.f = directives.map(a => `${a.result}:${a.logData().raw}`).sort();
+ details.t = Math.round(Number(reqstop - reqstart) / 10) / 100;
+ results['csp'].push([ i, Object.assign({}, details) ]);
+ }
+ }
+ }
+ if ( r === 1 ) {
+ const reqstart = process.hrtime.bigint();
+ const directives = engine.matchAndFetchModifiers(details, 'redirect-rule');
+ if ( directives !== undefined ) {
+ modified = true;
+ if ( NEED_RESULTS ) {
+ const reqstop = process.hrtime.bigint();
+ details.f = directives.map(a => `${a.result}:${a.logData().raw}`).sort();
+ details.t = Math.round(Number(reqstop - reqstart) / 10) / 100;
+ results['redirect-rule'].push([ i, Object.assign({}, details) ]);
+ }
+ }
+ }
+ if ( r !== 1 && engine.hasQuery(details) ) {
+ const reqstart = process.hrtime.bigint();
+ const directives = engine.matchAndFetchModifiers(details, 'removeparam');
+ if ( directives !== undefined ) {
+ modified = true;
+ if ( NEED_RESULTS ) {
+ const reqstop = process.hrtime.bigint();
+ details.f = directives.map(a => `${a.result}:${a.logData().raw}`).sort();
+ details.t = Math.round(Number(reqstop - reqstart) / 10) / 100;
+ results['removeparam'].push([ i, Object.assign({}, details) ]);
+ }
+ }
+ }
+ if ( modified ) {
+ modifiedCount += 1;
+ }
+ }
+ const stop = process.hrtime.bigint();
+
+ console.log(`Matched-modified ${requests.length} requests in ${nanoToMilli(stop - start)}`);
+ console.log(`\t${modifiedCount} modifiers found`);
+ console.log(`\tAverage: ${nanoToMicro((stop - start) / BigInt(requests.length))} per request`);
+
+ if ( RECORD ) {
+ write('data/snfe.modifiers.json', JSON.stringify(results, null, 2));
+ }
+
+ if ( COMPARE ) {
+ const diffs = await compareModifiers(results);
+ if ( Array.isArray(diffs) ) {
+ write('data/snfe.modifiers.diffs.json', JSON.stringify(diffs, null, 2));
+ }
+ console.log(`Compare: ${diffs.length} modified requests differ`);
+ }
+}
+
+async function compareModifiers(afterResults) {
+ let beforeResults;
+ try {
+ const raw = await read('data/snfe.modifiers.json');
+ beforeResults = JSON.parse(raw);
+ } catch(ex) {
+ console.log(ex);
+ console.log('Nothing to compare');
+ return;
+ }
+ const diffs = [];
+ for ( const modifier of [ 'csp', 'redirect-rule', 'removeparam' ] ) {
+ const before = new Map(beforeResults[modifier]);
+ const after = new Map(afterResults[modifier]);
+ for ( const [ i, b ] of before ) {
+ const a = after.get(i);
+ if ( a !== undefined && JSON.stringify(a.f) === JSON.stringify(b.f) ) { continue; }
+ diffs.push([ i, {
+ type: b.type,
+ url: b.url,
+ originURL: b.originURL,
+ before: { f: b.f, t: b.t },
+ after: a !== undefined ? { f: a.f, t: a.t } : null,
+ }]);
+ }
+ for ( const [ i, a ] of after ) {
+ const b = before.get(i);
+ if ( b !== undefined ) { continue; }
+ diffs.push([ i, {
+ type: a.type,
+ url: a.url,
+ originURL: a.originURL,
+ before: null,
+ after: { f: a.f, t: a.t },
+ }]);
+ }
+ }
+ return diffs;
+}
+
+/******************************************************************************/
+
+async function bench() {
+ if ( WASM ) {
+ try {
+ const result = await enableWASM();
+ if ( result === true ) {
+ console.log('WASM code paths enabled');
+ }
+ } catch(ex) {
+ console.log(ex);
+ }
+ }
+
+ const require = createRequire(import.meta.url); // jshint ignore:line
+ const requests = await require('./node_modules/scaling-palm-tree/requests.json');
+ const engine = await StaticNetFilteringEngine.create();
+
+ let start = process.hrtime.bigint();
+ await engine.useLists([
+ read('assets/ublock/filters.min.txt')
+ .then(raw => ({ name: 'filters', raw })),
+ read('assets/ublock/badware.txt')
+ .then(raw => ({ name: 'badware', raw })),
+ read('assets/ublock/privacy.min.txt')
+ .then(raw => ({ name: 'privacy', raw })),
+ read('assets/ublock/quick-fixes.txt')
+ .then(raw => ({ name: 'quick-fixes.txt', raw })),
+ read('assets/ublock/unbreak.txt')
+ .then(raw => ({ name: 'unbreak.txt', raw })),
+ read('assets/thirdparties/easylist/easylist.txt')
+ .then(raw => ({ name: 'easylist', raw })),
+ read('assets/thirdparties/easylist/easyprivacy.txt')
+ .then(raw => ({ name: 'easyprivacy', raw })),
+ read('assets/thirdparties/pgl.yoyo.org/as/serverlist')
+ .then(raw => ({ name: 'PGL', raw })),
+ read('assets/thirdparties/urlhaus-filter/urlhaus-filter-online.txt')
+ .then(raw => ({ name: 'urlhaus', raw })),
+ ]);
+ let stop = process.hrtime.bigint();
+ console.log(`Filter lists parsed-compiled-loaded in ${nanoToMilli(stop - start)}`);
+
+ // Dry run to let JS engine optimize hot JS code paths
+ for ( let i = 0; i < requests.length; i += 8 ) {
+ const request = requests[i];
+ void engine.matchRequest({
+ type: WEBREQUEST_OPTIONS[request.cpt],
+ url: request.url,
+ originURL: request.frameUrl,
+ });
+ }
+
+ if ( MODIFIERS === false ) {
+ matchRequests(engine, requests);
+ } else {
+ matchRequestModifiers(engine, requests);
+ }
+
+ StaticNetFilteringEngine.release();
+}
+
+bench();
+
+/******************************************************************************/
diff --git a/platform/firefox/manifest.json b/platform/firefox/manifest.json
new file mode 100644
index 0000000..4ee0b56
--- /dev/null
+++ b/platform/firefox/manifest.json
@@ -0,0 +1,132 @@
+{
+ "author": "Raymond Hill & contributors",
+ "background": {
+ "page": "background.html"
+ },
+ "browser_action": {
+ "browser_style": false,
+ "default_area": "navbar",
+ "default_icon": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png",
+ "64": "img/icon_64.png"
+ },
+ "default_title": "uBlock Origin",
+ "default_popup": "popup-fenix.html"
+ },
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "uBlock0@raymondhill.net",
+ "strict_min_version": "78.0"
+ },
+ "gecko_android": {
+ "strict_min_version": "79.0"
+ }
+ },
+ "commands": {
+ "_execute_browser_action": {
+ },
+ "launch-element-zapper": {
+ "description": "__MSG_popupTipZapper__"
+ },
+ "launch-element-picker": {
+ "description": "__MSG_popupTipPicker__"
+ },
+ "launch-logger": {
+ "description": "__MSG_popupTipLog__"
+ },
+ "open-dashboard": {
+ "description": "__MSG_popupTipDashboard__"
+ },
+ "relax-blocking-mode": {
+ "description": "__MSG_relaxBlockingMode__"
+ },
+ "toggle-cosmetic-filtering": {
+ "description": "__MSG_toggleCosmeticFiltering__"
+ }
+ },
+ "content_scripts": [
+ {
+ "matches": [
+ "http://*/*",
+ "https://*/*",
+ "file://*/*"
+ ],
+ "js": [
+ "/js/vapi.js",
+ "/js/vapi-client.js",
+ "/js/contentscript.js"
+ ],
+ "all_frames": true,
+ "match_about_blank": true,
+ "run_at": "document_start"
+ },
+ {
+ "matches": [
+ "https://easylist.to/*",
+ "https://*.fanboy.co.nz/*",
+ "https://filterlists.com/*",
+ "https://forums.lanik.us/*",
+ "https://github.com/*",
+ "https://*.github.io/*",
+ "https://*.letsblock.it/*"
+ ],
+ "js": [
+ "/js/scriptlets/subscriber.js"
+ ],
+ "run_at": "document_idle",
+ "all_frames": false
+ },
+ {
+ "matches": [
+ "https://github.com/uBlockOrigin/*",
+ "https://ublockorigin.github.io/*",
+ "https://*.reddit.com/r/uBlockOrigin/*"
+ ],
+ "js": [
+ "/js/scriptlets/updater.js"
+ ],
+ "run_at": "document_idle",
+ "all_frames": false
+ }
+ ],
+ "default_locale": "en",
+ "description": "__MSG_extShortDesc__",
+ "icons": {
+ "16": "img/ublock.svg",
+ "32": "img/ublock.svg",
+ "48": "img/ublock.svg",
+ "64": "img/ublock.svg",
+ "96": "img/ublock.svg"
+ },
+ "manifest_version": 2,
+ "name": "uBlock Origin",
+ "options_ui": {
+ "page": "dashboard.html",
+ "open_in_tab": true
+ },
+ "permissions": [
+ "alarms",
+ "dns",
+ "menus",
+ "privacy",
+ "storage",
+ "tabs",
+ "unlimitedStorage",
+ "webNavigation",
+ "webRequest",
+ "webRequestBlocking",
+ "<all_urls>"
+ ],
+ "short_name": "uBlock₀",
+ "sidebar_action": {
+ "default_title": "__MSG_statsPageName__",
+ "default_panel": "logger-ui.html",
+ "default_icon": "img/ublock.svg",
+ "open_at_install": false
+ },
+ "version": "1.9.15.101",
+ "web_accessible_resources": [
+ "/web_accessible_resources/*"
+ ]
+}
diff --git a/platform/firefox/vapi-background-ext.js b/platform/firefox/vapi-background-ext.js
new file mode 100644
index 0000000..8ecefc9
--- /dev/null
+++ b/platform/firefox/vapi-background-ext.js
@@ -0,0 +1,328 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2017-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* globals browser */
+
+'use strict';
+
+/******************************************************************************/
+
+import {
+ domainFromHostname,
+ hostnameFromNetworkURL,
+} from './uri-utils.js';
+
+/******************************************************************************/
+
+// Canonical name-uncloaking feature.
+let cnameUncloakEnabled = browser.dns instanceof Object;
+let cnameUncloakProxied = false;
+
+// https://github.com/uBlockOrigin/uBlock-issues/issues/911
+// We detect here whether network requests are proxied, and if so,
+// de-aliasing of hostnames will be disabled to avoid possible
+// DNS leaks.
+const proxyDetector = function(details) {
+ if ( details.proxyInfo instanceof Object ) {
+ cnameUncloakEnabled = false;
+ proxyDetectorTryCount = 0;
+ }
+ if ( proxyDetectorTryCount === 0 ) {
+ browser.webRequest.onHeadersReceived.removeListener(proxyDetector);
+ return;
+ }
+ proxyDetectorTryCount -= 1;
+};
+let proxyDetectorTryCount = 0;
+
+// Related issues:
+// - https://github.com/gorhill/uBlock/issues/1327
+// - https://github.com/uBlockOrigin/uBlock-issues/issues/128
+// - https://bugzilla.mozilla.org/show_bug.cgi?id=1503721
+
+// Extend base class to normalize as per platform.
+
+vAPI.Net = class extends vAPI.Net {
+ constructor() {
+ super();
+ this.pendingRequests = [];
+ this.canUncloakCnames = browser.dns instanceof Object;
+ this.cnames = new Map([ [ '', null ] ]);
+ this.cnameIgnoreList = null;
+ this.cnameIgnore1stParty = true;
+ this.cnameIgnoreExceptions = true;
+ this.cnameIgnoreRootDocument = true;
+ this.cnameMaxTTL = 120;
+ this.cnameReplayFullURL = false;
+ this.cnameFlushTime = Date.now() + this.cnameMaxTTL * 60000;
+ }
+ setOptions(options) {
+ super.setOptions(options);
+ if ( 'cnameUncloakEnabled' in options ) {
+ cnameUncloakEnabled =
+ this.canUncloakCnames &&
+ options.cnameUncloakEnabled !== false;
+ }
+ if ( 'cnameUncloakProxied' in options ) {
+ cnameUncloakProxied = options.cnameUncloakProxied === true;
+ }
+ if ( 'cnameIgnoreList' in options ) {
+ this.cnameIgnoreList =
+ this.regexFromStrList(options.cnameIgnoreList);
+ }
+ if ( 'cnameIgnore1stParty' in options ) {
+ this.cnameIgnore1stParty =
+ options.cnameIgnore1stParty !== false;
+ }
+ if ( 'cnameIgnoreExceptions' in options ) {
+ this.cnameIgnoreExceptions =
+ options.cnameIgnoreExceptions !== false;
+ }
+ if ( 'cnameIgnoreRootDocument' in options ) {
+ this.cnameIgnoreRootDocument =
+ options.cnameIgnoreRootDocument !== false;
+ }
+ if ( 'cnameMaxTTL' in options ) {
+ this.cnameMaxTTL = options.cnameMaxTTL || 120;
+ }
+ if ( 'cnameReplayFullURL' in options ) {
+ this.cnameReplayFullURL = options.cnameReplayFullURL === true;
+ }
+ this.cnames.clear(); this.cnames.set('', null);
+ this.cnameFlushTime = Date.now() + this.cnameMaxTTL * 60000;
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/911
+ // Install/remove proxy detector.
+ if ( vAPI.webextFlavor.major < 80 ) {
+ const wrohr = browser.webRequest.onHeadersReceived;
+ if ( cnameUncloakEnabled === false || cnameUncloakProxied ) {
+ if ( wrohr.hasListener(proxyDetector) ) {
+ wrohr.removeListener(proxyDetector);
+ }
+ } else if ( wrohr.hasListener(proxyDetector) === false ) {
+ wrohr.addListener(
+ proxyDetector,
+ { urls: [ '*://*/*' ] },
+ [ 'blocking' ]
+ );
+ }
+ proxyDetectorTryCount = 32;
+ }
+ }
+ normalizeDetails(details) {
+ const type = details.type;
+
+ if ( type === 'imageset' ) {
+ details.type = 'image';
+ return;
+ }
+
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/345
+ // Re-categorize an embedded object as a `sub_frame` if its
+ // content type is that of a HTML document.
+ if ( type === 'object' && Array.isArray(details.responseHeaders) ) {
+ for ( const header of details.responseHeaders ) {
+ if ( header.name.toLowerCase() === 'content-type' ) {
+ if ( header.value.startsWith('text/html') ) {
+ details.type = 'sub_frame';
+ }
+ break;
+ }
+ }
+ }
+ }
+ denormalizeTypes(types) {
+ if ( types.length === 0 ) {
+ return Array.from(this.validTypes);
+ }
+ const out = new Set();
+ for ( const type of types ) {
+ if ( this.validTypes.has(type) ) {
+ out.add(type);
+ }
+ if ( type === 'image' && this.validTypes.has('imageset') ) {
+ out.add('imageset');
+ }
+ if ( type === 'sub_frame' ) {
+ out.add('object');
+ }
+ }
+ return Array.from(out);
+ }
+ canonicalNameFromHostname(hn) {
+ const cnRecord = this.cnames.get(hn);
+ if ( cnRecord !== undefined && cnRecord !== null ) {
+ return cnRecord.cname;
+ }
+ }
+ processCanonicalName(hn, cnRecord, details) {
+ if ( cnRecord === null ) { return; }
+ if ( cnRecord.isRootDocument ) { return; }
+ const hnBeg = details.url.indexOf(hn);
+ if ( hnBeg === -1 ) { return; }
+ const oldURL = details.url;
+ let newURL = oldURL.slice(0, hnBeg) + cnRecord.cname;
+ const hnEnd = hnBeg + hn.length;
+ if ( this.cnameReplayFullURL ) {
+ newURL += oldURL.slice(hnEnd);
+ } else {
+ const pathBeg = oldURL.indexOf('/', hnEnd);
+ if ( pathBeg !== -1 ) {
+ newURL += oldURL.slice(hnEnd, pathBeg + 1);
+ }
+ }
+ details.url = newURL;
+ details.aliasURL = oldURL;
+ return super.onBeforeSuspendableRequest(details);
+ }
+ recordCanonicalName(hn, record, isRootDocument) {
+ if ( (this.cnames.size & 0b111111) === 0 ) {
+ const now = Date.now();
+ if ( now >= this.cnameFlushTime ) {
+ this.cnames.clear(); this.cnames.set('', null);
+ this.cnameFlushTime = now + this.cnameMaxTTL * 60000;
+ }
+ }
+ let cname =
+ typeof record.canonicalName === 'string' &&
+ record.canonicalName !== hn
+ ? record.canonicalName
+ : '';
+ if (
+ cname !== '' &&
+ this.cnameIgnore1stParty &&
+ domainFromHostname(cname) === domainFromHostname(hn)
+ ) {
+ cname = '';
+ }
+ if (
+ cname !== '' &&
+ this.cnameIgnoreList !== null &&
+ this.cnameIgnoreList.test(cname)
+ ) {
+ cname = '';
+ }
+ const cnRecord = cname !== '' ? { cname, isRootDocument } : null;
+ this.cnames.set(hn, cnRecord);
+ return cnRecord;
+ }
+ regexFromStrList(list) {
+ if (
+ typeof list !== 'string' ||
+ list.length === 0 ||
+ list === 'unset' ||
+ browser.dns instanceof Object === false
+ ) {
+ return null;
+ }
+ if ( list === '*' ) {
+ return /^./;
+ }
+ return new RegExp(
+ '(?:^|\.)(?:' +
+ list.trim()
+ .split(/\s+/)
+ .map(a => a.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
+ .join('|') +
+ ')$'
+ );
+ }
+ onBeforeSuspendableRequest(details) {
+ const r = super.onBeforeSuspendableRequest(details);
+ if ( cnameUncloakEnabled === false ) { return r; }
+ if ( r !== undefined ) {
+ if (
+ r.cancel === true ||
+ r.redirectUrl !== undefined ||
+ this.cnameIgnoreExceptions
+ ) {
+ return r;
+ }
+ }
+ const hn = hostnameFromNetworkURL(details.url);
+ const cnRecord = this.cnames.get(hn);
+ if ( cnRecord !== undefined ) {
+ return this.processCanonicalName(hn, cnRecord, details);
+ }
+ const documentUrl = details.documentUrl || details.url;
+ const isRootDocument = this.cnameIgnoreRootDocument &&
+ hn === hostnameFromNetworkURL(documentUrl);
+ return browser.dns.resolve(hn, [ 'canonical_name' ]).then(
+ rec => {
+ const cnRecord = this.recordCanonicalName(hn, rec, isRootDocument);
+ return this.processCanonicalName(hn, cnRecord, details);
+ },
+ ( ) => {
+ this.cnames.set(hn, null);
+ }
+ );
+ }
+ suspendOneRequest(details) {
+ const pending = {
+ details: Object.assign({}, details),
+ resolve: undefined,
+ promise: undefined
+ };
+ pending.promise = new Promise(resolve => {
+ pending.resolve = resolve;
+ });
+ this.pendingRequests.push(pending);
+ return pending.promise;
+ }
+ unsuspendAllRequests(discard = false) {
+ const pendingRequests = this.pendingRequests;
+ this.pendingRequests = [];
+ for ( const entry of pendingRequests ) {
+ entry.resolve(
+ discard !== true
+ ? this.onBeforeSuspendableRequest(entry.details)
+ : undefined
+ );
+ }
+ }
+ static canSuspend() {
+ return true;
+ }
+};
+
+/******************************************************************************/
+
+vAPI.scriptletsInjector = ((doc, details) => {
+ let script, url;
+ try {
+ const blob = new self.Blob(
+ [ details.scriptlets ],
+ { type: 'text/javascript; charset=utf-8' }
+ );
+ url = self.URL.createObjectURL(blob);
+ script = doc.createElement('script');
+ script.async = false;
+ script.src = url;
+ (doc.head || doc.documentElement || doc).append(script);
+ self.uBO_scriptletsInjected = details.filters;
+ } catch (ex) {
+ }
+ if ( url ) {
+ if ( script ) { script.remove(); }
+ self.URL.revokeObjectURL(url);
+ }
+}).toString();
+
+/******************************************************************************/
diff --git a/platform/firefox/webext.js b/platform/firefox/webext.js
new file mode 100644
index 0000000..e9bff76
--- /dev/null
+++ b/platform/firefox/webext.js
@@ -0,0 +1,24 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2019-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+export default browser;
diff --git a/platform/mv3/README.md b/platform/mv3/README.md
new file mode 100644
index 0000000..2400bfe
--- /dev/null
+++ b/platform/mv3/README.md
@@ -0,0 +1,31 @@
+# How to build MV3 uBO Lite
+
+Instructions for reviewers.
+
+The following assumes a linux environment.
+
+1. Open Bash console
+2. `git clone https://github.com/gorhill/uBlock.git`
+3. `cd uBlock`
+4. `make mv3-[platform]`, where `[platform]` is either `chromium` or `firefox`
+5. This will fully build uBO Lite, and during the process filter lists will be downloaded from their respective remote servers
+
+Upon completion of the script, the resulting extension package will become present in:
+
+- Chromium: `dist/build/uBOLite.chromium`
+- Firefox: `dist/build/uBOLite.firefox`
+
+The folder `dist/build/mv3-data` will cache data fetched from remote server, so as to avoid fetching repeatedly from remote server with repeated build commands. Remove `dist/build/mv3-data` if you want to build with latest versions of filter lists.
+
+The file `dist/build/mv3-data/log.txt` will contain information about what happened during the build process.
+
+The entry in the `Makefile` which implement the build process is `tools/make-mv3.sh [platform]`.[1] This Bash script copy various files from uBlock Origin branch and MV3-specific branch into a single folder which will be the final extension package.
+
+Notably, `tools/make-mv3.sh [platform]` calls a Nodejs script which purpose is to convert the filter lists into various rulesets to be used in a declarative way. The Nodejs version required is 17.5.0 or above.
+
+All the final rulesets are present in the `dist/build/uBOLite.[platform]/rulesets` in the final extension package.
+
+---
+
+[1] https://github.com/gorhill/uBlock/blob/c4d324362fdb95ff8ef20f0b18f42f0eec955433/tools/make-mv3.sh
+[2] https://github.com/gorhill/uBlock/blob/c4d324362fdb95ff8ef20f0b18f42f0eec955433/tools/make-mv3.sh#L103
diff --git a/platform/mv3/chromium/manifest.json b/platform/mv3/chromium/manifest.json
new file mode 100644
index 0000000..036dc2d
--- /dev/null
+++ b/platform/mv3/chromium/manifest.json
@@ -0,0 +1,46 @@
+{
+ "action": {
+ "default_icon": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png",
+ "64": "img/icon_64.png"
+ },
+ "default_popup": "popup.html"
+ },
+ "author": "Raymond Hill",
+ "background": {
+ "service_worker": "/js/background.js",
+ "type": "module"
+ },
+ "declarative_net_request": {
+ "rule_resources": [
+ ]
+ },
+ "default_locale": "en",
+ "description": "__MSG_extShortDesc__",
+ "icons": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png",
+ "64": "img/icon_64.png",
+ "128": "img/icon_128.png"
+ },
+ "manifest_version": 3,
+ "minimum_chrome_version": "105.0",
+ "name": "__MSG_extName__",
+ "options_page": "dashboard.html",
+ "optional_host_permissions": [
+ "<all_urls>"
+ ],
+ "permissions": [
+ "activeTab",
+ "declarativeNetRequest",
+ "scripting",
+ "storage"
+ ],
+ "short_name": "uBO Lite",
+ "storage": {
+ "managed_schema": "managed_storage.json"
+ },
+ "version": "1.0",
+ "web_accessible_resources": []
+}
diff --git a/platform/mv3/description/en.md b/platform/mv3/description/en.md
new file mode 100644
index 0000000..5714024
--- /dev/null
+++ b/platform/mv3/description/en.md
@@ -0,0 +1,41 @@
+## Description
+
+**uBO Lite** (uBOL), a **permission-less** [MV3 API-based](https://developer.chrome.com/docs/extensions/mv3/intro/) content blocker.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is [performed reliably](https://developer.chrome.com/docs/extensions/reference/scripting/#method-registerContentScripts) by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read/modify data" [permission](https://developer.chrome.com/docs/extensions/mv3/declare_permissions/) at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read/modify data" permissions at install time. <details><summary>**However, [...]**</summary>
+ uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using declarative cosmetic and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+![uBOL's popup panel: no permission](https://user-images.githubusercontent.com/585534/195468156-d7e63ab9-abfa-443c-a8f6-e646a29b801e.png)
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request:
+
+![uBOL's popup panel: browser warning](https://user-images.githubusercontent.com/585534/195342593-2b82b740-70a3-4507-a0e5-d7aee803b286.png)
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site:
+
+![uBOL's popup panel: permissions to inject content](https://user-images.githubusercontent.com/585534/195342612-85d109d9-9006-4eb5-95a5-fec8a4f233ea.png)
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to modify and read data on all websites:
+
+![uBOL's options: Default filtering mode](https://user-images.githubusercontent.com/585534/195343335-a0aa103e-621e-4137-9bcf-9821dc881be1.png)
+
+</details>
+
+The default ruleset corresponds to at least uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.ar.txt b/platform/mv3/description/webstore.ar.txt
new file mode 100644
index 0000000..a86b45a
--- /dev/null
+++ b/platform/mv3/description/webstore.ar.txt
@@ -0,0 +1,36 @@
+uBO الخفيف (uBOL) مانع محتوى *لا يحتاج لتصاريح* مبني على MV3.
+
+تتوافق مجموعة القواعد الافتراضية مع مجموعة عوامل التصفية الافتراضية لـ uBlock Origin:
+
+- قوائم التصفية المدمجة في uBlock Origin
+- القائمة السهلة
+- الخصوصية السهلة
+- قائمة خادم الإعلانات والتتبع لبيتر لوي
+
+يمكنك إضافة المزيد من القواعد من خلال زيارة صفحة الخيارات ومن ثم أنقر على رمز _Cogs_ في اللوحة المنبثقة.
+
+uBOL صريح تمامًا، مما يعني أنه لا تحتاج إلى uBOL بشكل دائم لحدوث تصفية المحتوى، يتم إجراء تصفية المحتوى من خلال إضافة CSS/JS بشكل موثوق به بواسطة المتصفح نفسه بدلًا من الإضافة.
+ هذا يعني أن uBOL نفسه لا يستهلك موارد وحدة المعالجة المركزية/الذاكرة أثناء استمراره في حظر المحتوى. عملية عامل الخدمة في uBOL مطلوبة _فقط_ عند التفاعل مع اللوحة المنبثقة أو صفحة الخيارات.
+
+لا يتطلب uBOL صلاحية واسعة «لقراءة البيانات وتعديلها» في وقت التثبيت، وبالتالي فإن قدراته محدودة مقارنة بـ uBlock Origin أو إضافات حظر الإعلانات الأخرى التي تتطلب صلاحية واسعة «قراءة البيانات وتعديلها» في وقت التثبيت.
+
+
+ومع ذلك، يسمح لك uBOL "بوضوح" بمنح صلاحيات موسعة على مواقع محددة من اختيارك حتى يتمكن من التصفية بشكل أفضل على تلك المواقع باستخدام التصفية التجميلية وإضافة النص.
+
+
+لمنح صلاحيات موسعة على موقع معين، افتح اللوحة المنبثقة واختر وضع التصفية إما الأمثل أو الكامل.
+
+سيحذرك المتصفح من مخاطر منح صلاحيات إضافية التي يطلبها الامتداد على الموقع الحالي، وسيتعين عليك إختيار بما إذا كنت تقبل الطلب أو ترفضه.
+
+
+إذا قبلت طلب uBOL بالحصول على صلاحيات إضافية على الموقع الحالي، فستتمكن من تصفية المحتوى بشكل أفضل للموقع الحالي.
+
+
+بإمكانك اختيار وضع التصفية الافتراضية من خلال صفحة خيارات uBOL. إذا اخترت الوضع الأمثل أو الكامل باعتباره الوضع الافتراضي، فستحتاج إلى منح uBOL الإذن لقراءة البيانات وتعديلها على جميع مواقع الويب.
+
+
+ضع في اعتبارك أن هذا لا يزال عملًا قيد التنفيذ، هذه هي الأهداف النهائية:
+
+لا يمكنك تحديد الأذونات المستخدمة لاحقًا في التثبيت، تحديدك للأذونات سيكون خلال زيارتك لكل موقع.
+
+تقريري تمامًا للموثوقية ولكفاءة وحدة المعالجة المركزية/الذاكرة.
diff --git a/platform/mv3/description/webstore.az.txt b/platform/mv3/description/webstore.az.txt
new file mode 100644
index 0000000..8ff852c
--- /dev/null
+++ b/platform/mv3/description/webstore.az.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) *icazəsiz* MV3 əsaslı məzmun bloklayıcısıdır.
+
+Defolt qaydalar dəsti uBlock Origin-in defolt filtr dəstinə uyğundur:
+
+- uBlock Origin-in daxili filtr siyahıları
+- EasyList
+- EasyPrivacy
+- Peter Lowe-un Reklam və izləyici server siyahısı
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.be.txt b/platform/mv3/description/webstore.be.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.be.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.bg.txt b/platform/mv3/description/webstore.bg.txt
new file mode 100644
index 0000000..6e58550
--- /dev/null
+++ b/platform/mv3/description/webstore.bg.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) е блокер за съдържание *без разрешения*, базиран на MV3.
+
+Наборът от правила по подразбиране съответства на набора от филтри по подразбиране на uBlock Origin:
+
+- Вградени списъци с филтри на uBlock Origin
+- EasyList
+- EasyPrivacy
+- Списък със сървъри на Peter Lowe за реклами и проследяване
+
+Можете да добавите още набори от правила, като посетите страницата с опции – щракнете върху иконата _зъбно колело_ в изскачащия панел.
+
+uBOL е изцяло декларативен, което означава, че няма нужда от постоянен процес на uBOL за филтриране, а филтрирането на съдържание, базирано на инжектиране на CSS/JS, се извършва надеждно от самия браузър, а не от разширението. Това означава, че самият uBOL не консумира ресурси на процесора/паметта, докато тече блокирането на съдържанието – работният процес на услугата на uBOL е необходим _само_ когато взаимодействате с изскачащия панел или страниците с опции.
+
+uBOL не изисква широко разрешение за "четене и промяна на данни" по време на инсталиране, поради което възможностите му са ограничени в сравнение с uBlock Origin или други блокери на съдържание, изискващи широко разрешение за "четене и промяна на данни" по време на инсталиране.
+
+Въпреки това uBOL ви позволява да предоставите *изрично* разширени разрешения за определени сайтове по ваш избор, за да може да филтрира по-добре тези сайтове, като използва козметично филтриране и инжектиране на скриптове.
+
+За да предоставите разширени разрешения за даден сайт, отворете изскачащия панел и изберете по-висок режим на филтриране, например Оптимален или Пълен.
+
+След това браузърът ще ви предупреди за последиците от предоставянето на допълнителните разрешения, поискани от разширението, за текущия сайт и ще трябва да кажете на браузъра дали приемате или отхвърляте искането.
+
+Ако приемете искането на uBOL за допълнителни разрешения за текущия сайт, той ще може да филтрира по-добре съдържанието на текущия сайт.
+
+Можете да зададете режима на филтриране по подразбиране от страницата с опции на uBOL. Ако изберете оптимален или пълен режим по подразбиране, ще трябва да предоставите на uBOL разрешение за четене и промяна на данни във всички уебсайтове.
+
+Имайте предвид, че това все още е в процес на разработка с тези крайни цели:
+
+- По време на инсталацията няма широки разрешения за хоста – разширените разрешения се предоставят изрично от потребителя за всеки сайт.
+
+- Изцяло декларативен за надеждност и ефективност на процесора/паметта.
diff --git a/platform/mv3/description/webstore.bn.txt b/platform/mv3/description/webstore.bn.txt
new file mode 100644
index 0000000..83e739a
--- /dev/null
+++ b/platform/mv3/description/webstore.bn.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) হল একটি *অনুমতি-হীন* MV3-ভিত্তিক কন্টেন্ট ব্লকার।
+
+পূর্ব নির্ধারিত নিয়ম সেট uBlock অরিজিনের ডিফল্ট ফিল্টারসেটের সাথে মিলে যায়:
+
+- uBlock অরিজিনের বিল্ট ইন ফিল্টার তালিকা
+- ইজিলিস্ট
+- সহজ গোপনীয়তা
+- পিটার লো এর বিজ্ঞাপন এবং ট্র্যাকিং সার্ভার তালিকা
+
+আপনি অপশন পেজে গিয়ে আরও নিয়ম সেট যোগ করতে পারেন -- পপআপ প্যানেলে _Cogs_ আইকনে ক্লিক করুন।
+
+uBOL সম্পূর্ণরূপে ঘোষণামূলক, অর্থাৎ ফিল্টারিং করতে একটি স্থায়ী uBOL প্রক্রিয়ার প্রয়োজন নেই, এবং CSS/JS ইনজেকশন-ভিত্তিক বিষয়বস্তু ফিল্টারিং এক্সটেনশনের পরিবর্তে ব্রাউজার নিজেই নির্ভরযোগ্যভাবে এই কাজ করে থাকে। এর মানে হল যে কন্টেন্ট ব্লকিং চলমান থাকা অবস্থায় uBOL নিজেই CPU/মেমরি রিসোর্স ব্যবহার করে না -- uBOL-এর পরিষেবার প্রক্রিয়ার প্রয়োজন শুধুমাত্র_ যখন আপনি পপআপ প্যানেল বা অপশন পেজগুলির সাথে ইন্টারঅ্যাক্ট করেন।
+
+uBOL-এর ইন্সটল করার সময় বিস্তৃত "পড়ার ও ডেটা পরিবর্তন করার" অনুমতির প্রয়োজন হয় না, তাই ইউব্লক অরিজিন বা অন্যান্য কনটেন্ট ব্লকের তুলনায় এটির সীমিত ক্ষমতা বাক্সের বাইরে রয়েছে যার জন্য ইন্সটল করার সময় বিস্তৃত "ডেটা পড়ুন এবং পরিবর্তন করুন" অনুমতি প্রয়োজন।
+
+যাইহোক, uBOL আপনাকে আপনার পছন্দের নির্দিষ্ট সাইটে *স্পষ্টভাবে* বর্ধিত অনুমতি প্রদান করে যাতে এটি কসমেটিক ফিল্টারিং এবং স্ক্রিপ্টলেট ইনজেকশন ব্যবহার করে সেই সাইটগুলিতে আরও ভাল ফিল্টার করতে পারে।
+
+একটি সাইটে বর্ধিত অনুমতি প্রদানের জন্য, পপআপ প্যানেল খুলুন এবং একটি উচ্চতর ফিল্টারিং মোড বাছাই করুন যেমন অপটিমাল বা কমপ্লিট।
+
+ব্রাউজারটি তখন বর্তমান সাইটে এক্সটেনশন দ্বারা অনুরোধ করা অতিরিক্ত অনুমতি প্রদানের প্রভাব সম্পর্কে আপনাকে সতর্ক করবে এবং আপনি অনুরোধটি গ্রহণ করবেন বা প্রত্যাখ্যান করবেন কিনা তা আপনার ব্রাউজারকে বলতে হবে।
+
+আপনি যদি বর্তমান সাইটে অতিরিক্ত অনুমতির জন্য uBOL-এর অনুরোধ গ্রহণ করেন, তাহলে এটি বর্তমান সাইটের জন্য আরও ভালভাবে ফিল্টার করতে সক্ষম হবে।
+
+আপনি uBOL এর বিকল্প পৃষ্ঠা থেকে ডিফল্ট ফিল্টারিং মোড সেট করতে পারেন। আপনি যদি অপটিমাল বা কমপ্লিট মোডটিকে ডিফল্ট হিসেবে বেছে নেন, তাহলে আপনাকে uBOL-কে সমস্ত ওয়েবসাইটের ডেটা পড়তে এবং পরিবর্তন করার অনুমতি দিতে হবে।
+
+মনে রাখবেন এই শেষ লক্ষ্যগুলির ফলাফলের সাথে এখনও সংস্করণ কাজ চলছে:
+
+- ইনস্টল করার সময় কোনও বিস্তৃত অনুমতি নেই -- বর্ধিত অনুমতিগুলি প্রতি-সাইট ভিত্তিতে ব্যবহারকারীর দ্বারা স্পষ্টভাবে প্র্রদান করা হয়।
+
+- নির্ভরযোগ্যতা এবং CPU/মেমরি দক্ষতার জন্য সম্পূর্ণরূপে পূর্বঘোষণামুূলক।
diff --git a/platform/mv3/description/webstore.br_FR.txt b/platform/mv3/description/webstore.br_FR.txt
new file mode 100644
index 0000000..bfc4ce6
--- /dev/null
+++ b/platform/mv3/description/webstore.br_FR.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) zo ur stanker noazadurioù *hep aotreoù* diazezet war ar manifesto MV3.
+
+Ar reolennoù dre ziouer a glot gant silañ dre ziouer uBlock Origin:
+
+- Rolloù siloù genidik a uBlock Origin
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+Tu zo deoc'h ouzhpennañ reolennoù all en arventennoù -- klikit war an ikon _kendentadur_ er banell popup.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+Koulskoude ez eus tu deoc'h reiñ *sklaer* aotreoù ouzhpenn da uBOL el lec'hiennoù ma fell deoc'h, mod-se e vint silet gwelloc'h en ur implij siloù kened hag ensinkladurioù scriplet.
+
+Evit reiñ aotreoù ouzhpenn da uBOL en ul lec'hienn bennak, n'ho peus nemet digeriñ ar prenestr pop-up ha diuzañ ul live silañ uheloc'h evel ar mod Gwellañ pe ar mod Klok
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+Ma asantit da reiñ muioc'h a aotreoù da uBOL war ar bajenn-mañ e vo silet gwelloc'h.
+
+Gallout a rit termeniñ ar mod silañ dre ziouer e pajenn arventennoù uBOL. Ma tibabit ar mod Gwellañ pe an hini Klok evel ar mod dre ziouer e vo ret deoc'h aotren uBOL da lenn ha kemmañ roadennoù en holl lec'hiennoù.
+
+Dalc'hit soñj ez eo uBOL ur raktres war ober c'hoazh hag a zo e bal:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.bs.txt b/platform/mv3/description/webstore.bs.txt
new file mode 100644
index 0000000..96ba7d2
--- /dev/null
+++ b/platform/mv3/description/webstore.bs.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) je blokator sadržaja zasnovan na MV3 *bez dozvole*.
+
+Zadani skup pravila odgovara zadanom skupu filtera uBlock Origin:
+
+- UBlock Origin ugrađene liste filtera
+- EasyList
+- EasyPrivacy
+- Oglas Peter Lowe i lista servera za praćenje
+
+Možete dodati još skupova pravila tako što ćete posjetiti stranicu sa opcijama -- kliknite na ikonu _Cogs_ na iskačućoj ploči.
+
+uBOL je potpuno deklarativno, što znači da nema potrebe za trajnim uBOL procesom da bi se filtriranje dogodilo, a filtriranje sadržaja zasnovano na CSS/JS injekcijama se pouzdano izvodi od strane samog pretraživača, a ne ekstenzije. To znači da sam uBOL ne troši CPU/memorijske resurse dok je blokiranje sadržaja u toku -- proces uBOL-a servisnog radnika je potreban _samo_ kada stupite u interakciju sa iskačućim panelom ili stranicama sa opcijama.
+
+uBOL ne zahtijeva široku dozvolu za "čitanje i modificiranje podataka" u vrijeme instalacije, stoga su njegove ograničene mogućnosti izvan kutije u poređenju sa uBlock Origin-om ili drugim blokatorima sadržaja koji zahtijevaju široke dozvole za "čitanje i modificiranje podataka" u vrijeme instalacije.
+
+Međutim, uBOL vam omogućava da *eksplicitno* dodijelite proširene dozvole na određenim web lokacijama po vašem izboru kako bi mogao bolje filtrirati te stranice koristeći kozmetičko filtriranje i injekcije skriptleta.
+
+Da biste dali proširene dozvole za datu web lokaciju, otvorite iskačući panel i odaberite viši način filtriranja kao što je Optimal ili Complete.
+
+Pregledač će vas tada upozoriti na efekte odobravanja dodatnih dozvola koje ekstenzija traži na trenutnoj web stranici, a vi ćete morati reći pretraživaču da li prihvatate ili odbijate zahtjev.
+
+Ako prihvatite uBOL-ov zahtjev za dodatnim dozvolama na trenutnoj stranici, moći će bolje filtrirati sadržaj za trenutnu stranicu.
+
+Možete postaviti zadani način filtriranja sa uBOL-ove stranice sa opcijama. Ako odaberete Optimal ili Kompletan način kao zadani, morat ćete dati uBOL-u dozvolu da čita i mijenja podatke na svim web stranicama.
+
+Imajte na umu da je ovo još uvijek u toku, sa ovim krajnjim ciljevima:
+
+- Nema širokih dozvola za host u vrijeme instalacije - proširene dozvole se eksplicitno dodeljuju od strane korisnika na bazi po lokaciji.
+
+- Potpuno deklarativno za pouzdanost i efikasnost CPU/memorije.
diff --git a/platform/mv3/description/webstore.ca.txt b/platform/mv3/description/webstore.ca.txt
new file mode 100644
index 0000000..e750255
--- /dev/null
+++ b/platform/mv3/description/webstore.ca.txt
@@ -0,0 +1,32 @@
+L'uBO Lite (uBOL) és un blocador de contingut *sense permisos* basat en MV3.
+
+El conjunt de regles per defecte correspon al conjunt de filtres per defecte d'uBlock Origin:
+
+- Llistes de filtres integrades d'uBlock Origin
+- EasyList
+- EasyPrivacy
+- Llista de servidors de seguiment i anuncis de Peter Lowe
+
+Podeu afegir més conjunts de regles si visiteu la pàgina d'opcions: feu clic a la icona _Cogs_ al tauler emergent.
+
+L'uBOL és totalment declaratiu, és a dir, no cal un procés uBOL permanent perquè es produeixi el filtratge, i el filtratge de contingut basat en injecció CSS/JS es realitza de manera fiable pel propi navegador més que per l'extensió.
+ Això vol dir que l'uBOL en si no consumeix recursos de CPU/memòria mentre el bloqueig de contingut està en curs; el procés de treballador de servei d'uBOL només es requereix quan interactueu amb el tauler emergent o les pàgines d'opcions.
+
+
+L'uBOL no requereix un ampli permís de "lectura i modificació de dades" en el moment de la instal·lació, per tant, les seves capacitats limitades en comparació amb l'uBlock Origin o altres blocadors de contingut que requereixen amplis permisos de "lectura i modificació de dades" en el moment de la instal·lació.
+
+Tanmateix, l'uBOL us permet concedir *explícitament* permisos ampliats en llocs específics que trieu perquè pugui filtrar millor en aquests llocs mitjançant filtres cosmètics i injeccions de scriptlet.
+
+Per concedir permisos ampliats en un lloc determinat, obriu el tauler emergent i seleccioneu un mode de filtrat superior, com ara Òptim o Complet.
+
+Aleshores, el navegador us avisarà sobre els efectes de la concessió dels permisos addicionals sol·licitats per l'extensió al lloc actual, i haureu d'indicar-li al navegador si accepteu o rebutgeu la sol·licitud.
+
+Si accepteu la sol·licitud d'uBOL de permisos addicionals al lloc actual, podrà filtrar millor el contingut del lloc actual.
+
+Podeu establir el mode de filtratge per defecte des de la pàgina d'opcions d'uBOL. Si trieu el mode Òptim o Complet per defecte, haureu de concedir a l'uBOL el permís per llegir i modificar dades a tots els llocs web.
+
+Tingueu en compte que encara és un treball en curs, amb aquests objectius finals:
+
+- No hi ha permisos d'amfitrió amplis en el moment de la instal·lació; els permisos ampliats els concedeix explícitament l'usuari per lloc.
+
+- Totalment declaratiu per a la fiabilitat i l'eficiència de la CPU/memòria.
diff --git a/platform/mv3/description/webstore.cs.txt b/platform/mv3/description/webstore.cs.txt
new file mode 100644
index 0000000..05ca248
--- /dev/null
+++ b/platform/mv3/description/webstore.cs.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) je blokovač obsahu vyžadující méně oprávnění, založený na MV3.
+
+Výchozí sada pravidel koresponduje k výchozím sadám filtrů uBlock Origin:
+
+- Vestavěný seznam filtrů uBlock Origin
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+Můžete přidat více sad pravidel navštívením stránky nastavení -- klikněte na ikonu ozubených kol ve vyskakovácím panelu.
+
+uBOL je zcela deklarativní, což znamená, že pro filtrování není potřeba permanentní proces uBOL a filtrování obsahu založené na vstřikování CSS/JS je spolehlivě prováděno samotným prohlížečem, nikoli rozšířením. To znamená, že samotný uBOL nespotřebovává zdroje CPU/paměti, zatímco probíhá blokování obsahu – proces servisního pracovníka uBOL je vyžadován _pouze_ při interakci s vyskakovacím panelem nebo stránkami nastavení.
+
+uBOL nevyžaduje rozsáhlá oprávnění ke "čtení a úpravě dat" v době instalace, a proto má ihned po instalaci omezené možnosti ve srovnání s uBlock Origin nebo jinými blokovači obsahu, které vyžadují rozsáhlá oprávnění ke "čtení a úpravě dat" v době instalace.
+
+Nicméně, uBOL vám umožňuje *explicitně* udělit rozšířená oprávnění na konkrétních webech podle vašeho výběru, aby mohl na těchto webech lépe filtrovat pomocí kosmetického filtrování a vstřikování skriptů.
+
+Chcete-li na daném webu udělit rozšířená oprávnění, otevřete vyskakovací panel a vyberte vyšší režim filtrování, například optimální nebo kompletní.
+
+Prohlížeč vás poté upozorní na důsledky udělení dalších oprávnění požadovaných rozšířením na aktuálním webu a vy budete muset prohlížeči sdělit, zda žádost přijímáte nebo odmítáte.
+
+Pokud přijmete žádost uBOL o další oprávnění na aktuálním webu, bude moci lépe filtrovat obsah aktuálního webu.
+
+Výchozí filtrovací režim můžete nastavit na stránce nastavení uBOL. Pokud jako výchozí zvolíte režim optimální nebo kompletní, budete muset uBOL udělit oprávnění ke čtení a úpravě dat na všech webových stránkách.
+
+Mějte na paměti, že toto je stále nedokončená práce s těmito konečnými cíli:
+
+- Žádná rozsáhlá oprávnění hostitele v době instalace -- rozšířená oprávnění uděluje explicitně uživatel na jednotlivých stránkách.
+
+- Zcela deklarativní pro spolehlivost a efektivitu CPU/paměti.
diff --git a/platform/mv3/description/webstore.cv.txt b/platform/mv3/description/webstore.cv.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.cv.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.da.txt b/platform/mv3/description/webstore.da.txt
new file mode 100644
index 0000000..1d32188
--- /dev/null
+++ b/platform/mv3/description/webstore.da.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) er en *tilladelsesløs* MV3-baseret indholdsblocker.
+
+Standardregelsættet svarer til uBlock Origins standardfiltersæt:
+
+- uBlock Origins indbyggede filterlister
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+Flere regelsæt kan tilføjes ved at gå til indstillingssiden -- klik på ikonet _Cogs_ i pop op-panelet.
+
+uBOL er fuldstændig deklarativ, hvilket betyder, at ingen permanent uBOL-proces behøves for at filtreringen kan finde sted, og CSS/JS-injektionsbaseret indholdsfiltrering udføres pålideligt af browseren selv i stedet for af udvidelsen. Dette betyder, at uBOL ikke selv forbruger CPU-/hukommelsesressourcer under indholdsblokeringen -- uBOLs tjenestearbejdsproces er _kun_ nødvendig under interaktion med pop op-panelet eller indstillingssiderne.
+
+uBOL kræver ikke en omfattende "læse og ændre data" tilladelse under installationen, derfor dens begrænsede egenskaber fra start af sammenlignet med uBlock Origin eller andre indholdsblockere, som kræver omfattende "læse/ændre data" tilladelser under installationen.
+
+uBOL giver dog mulighed for *eksplicit* at tildele udvidede tilladelser på bestemte websteder efter eget valg, så den bedre kan filtrere på disse websteder vha. kosmetisk filtrering og scriptlet-injektioner.
+
+For at tildele udvidede tilladelser på et givent websted, åbn pop op-panelet og vælg en højere filtreringstilstand, såsom Optimal eller Komplet.
+
+Browseren advarer derefter om virkningerne af de ekstra tildelte tilladelser, som udvidelsen anmoder om på det aktuelle websted, og man vil skulle fortælle browseren, hvorvidt anmodningen accepteres eller afslås.
+
+Accepteres uBOLs anmodning om ekstra tilladelser på det aktuelle websted, vil den bedre kunne filtrere indhold på webstedet.
+
+Standardfiltreringstilstanden kan angives via uBOLs indstillingsside. Hvis tilstanden Optimal eller Komplet vælges som standardtilstand, skal uBOL tildeles tilladelse til at læse og ændre data på alle websteder.
+
+Husk dog, at dette stadig er et igangværende arbejde med disse slutmål:
+
+- Ingen omfattende værtstilladelser under installationen -- udvidede tilladelser tildeles eksplicit af brugeren på webstedsbasis.
+
+- Fuldstændig deklarativ for pålidelighed og CPU-/hukommelseseffektivitet.
diff --git a/platform/mv3/description/webstore.de.txt b/platform/mv3/description/webstore.de.txt
new file mode 100644
index 0000000..807ec84
--- /dev/null
+++ b/platform/mv3/description/webstore.de.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) ist ein Inhaltsblocker, der *ohne Berechtigungen* auskommt und auf MV3 basiert.
+
+Die Standardregeln entsprechen den Standardfiltern von uBlock Origin:
+
+- Integrierte Filterlisten von uBlock Origin
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+Sie können weitere Regeln hinzufügen, indem Sie die Optionen aufrufen — klicken Sie dazu im Pop-up-Fenster auf das Symbol mit den _Zahnrädern_.
+
+uBOL ist vollständig deklarativ, d. h. es ist kein dauerhafter uBOL-Prozess für das Filtern erforderlich, und die auf CSS/JS-Injektion basierende Inhaltsfilterung wird zuverlässig vom Browser selbst und nicht von der Erweiterung durchgeführt. Das bedeutet, dass uBOL selbst keine CPU-/Speicherressourcen verbraucht, während der Inhalt blockiert wird — der uBOL-Service-Worker-Prozess wird _nur_ benötigt, wenn Sie mit dem Pop-up-Fenster oder den Optionen interagieren.
+
+uBOL erfordert bei der Installation keine weitreichende Berechtigung zum Lesen und Ändern von Daten, daher sind die Möglichkeiten im Vergleich zu uBlock Origin oder anderen Inhaltsblockern, die bei der Installation weitreichende Berechtigungen zum Lesen und Ändern von Daten erfordern, von vornherein begrenzt.
+
+Allerdings können Sie mit uBOL *explizit* erweiterte Berechtigungen für bestimmte Websites Ihrer Wahl erteilen, sodass diese Websites durch kosmetisches Filtern und Scriptlet-Injektionen besser gefiltert werden können.
+
+Um erweiterte Berechtigungen für eine bestimmte Website zu erteilen, öffnen Sie das Pop-up-Fenster und wählen Sie einen stärkeren Filtermodus wie »Optimal« oder »Vollständig«.
+
+Der Browser warnt Sie anschließend über die Auswirkungen der zusätzlichen Berechtigungen, die von der Erweiterung für die aktuelle Website angefordert werden, und Sie können entscheiden, ob Sie zustimmen oder ablehnen.
+
+Wenn Sie die Anfrage von uBOL nach zusätzlichen Berechtigungen für die aktuelle Website zustimmen, kann uBOL die Inhalte für die aktuelle Website besser filtern.
+
+Sie können den Standardfiltermodus in den Optionen von uBOL festlegen. Wenn Sie den Modus »Optimal« oder »Vollständig« als Standardmodus wählen, müssen Sie uBOL die Berechtigung erteilen, Daten auf allen Websites lesen und ändern zu dürfen.
+
+Denken Sie daran, dass sich dieses Projekt noch in Entwicklung befindet und folgende Ziele verfolgt:
+
+- Keine weitreichenden Host-Berechtigungen bei der Installation — erweiterte Berechtigungen werden explizit von Ihnen für jede einzelne Website erteilt.
+
+- Vollständig deklarativ für Zuverlässigkeit und CPU-/Speichereffizienz.
diff --git a/platform/mv3/description/webstore.el.txt b/platform/mv3/description/webstore.el.txt
new file mode 100644
index 0000000..32d043d
--- /dev/null
+++ b/platform/mv3/description/webstore.el.txt
@@ -0,0 +1,30 @@
+Το uBO Lite (uBOL) είναι ένας blocker περιεχομένου *χωρίς άδειες* που βασίζεται σε MV3.
+
+Το προεπιλεγμένο σύνολο κανόνων αντιστοιχεί στο προεπιλεγμένο σύνολο φίλτρων του uBlock Origin:
+
+- Οι ενσωματωμένες λίστες φίλτρων του uBlock Origin
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+Μπορείτε να προσθέσετε περισσότερα σύνολα κανόνων μεταβαίνοντας στη σελίδα επιλογών -- κάντε κλικ στο εικονίδιο _Cogs_ στον αναδυόμενο πίνακα.
+
+Το uBOL είναι εξ'ολοκλήρου δηλωτικό, πράγμα που σημαίνει ότι δεν υπάρχει ανάγκη για μόνιμη διαδικασία uBOL για να πραγματοποιηθεί το φιλτράρισμα, και το φιλτράρισμα περιεχομένου που βασίζεται σε έγχυση CSS/JS εκτελείται αξιόπιστα από το ίδιο το πρόγραμμα περιήγησης και όχι από την επέκταση. Αυτό σημαίνει ότι το ίδιο το uBOL δεν καταναλώνει πόρους CPU/μνήμης ενώ ο αποκλεισμός περιεχομένου είναι σε εξέλιξη -- η διαδικασία του service worker του uBOL απαιτείται _μόνο_ όταν αλληλεπιδράτε με τον αναδυόμενο πίνακα ή τις σελίδες επιλογών.
+
+Το uBOL δεν απαιτεί ευρεία άδεια "ανάγνωσης και τροποποίησης δεδομένων" κατά τον χρόνο εγκατάστασης, επομένως έχει εξαρχής περιορισμένες δυνατότητές σε σύγκριση με το uBlock Origin ή άλλα προγράμματα αποκλεισμού περιεχομένου που απαιτούν ευρείες άδειες "ανάγνωσης/τροποποίησης δεδομένων" κατά την εγκατάσταση.
+
+Ωστόσο, το uBOL σάς επιτρέπει *ρητά* να εκχωρείτε εκτεταμένες άδειες σε συγκεκριμένους ιστότοπους της επιλογής σας, ώστε να μπορεί να φιλτράρει καλύτερα σε αυτούς τους ιστότοπους χρησιμοποιώντας κοσμητικό φιλτράρισμα και έγχυση scriptlet.
+
+Για να εκχωρήσετε εκτεταμένα δικαιώματα σε έναν δεδομένο ιστότοπο, ανοίξτε το αναδυόμενο πλαίσιο και επιλέξτε μια υψηλότερη λειτουργία φιλτραρίσματος, όπως Βέλτιστη ή Ολοκληρωμένη.
+
+Στη συνέχεια, το πρόγραμμα περιήγησης θα σας προειδοποιήσει για τα αποτελέσματα της χορήγησης των πρόσθετων δικαιωμάτων που ζητούνται από την επέκταση στον τρέχοντα ιστότοπο και θα πρέπει να ενημερώσετε το πρόγραμμα περιήγησης εάν αποδέχεστε ή απορρίπτετε το αίτημα.
+
+Εάν αποδεχτείτε το αίτημα του uBOL για πρόσθετα δικαιώματα στον τρέχοντα ιστότοπο, θα μπορεί να φιλτράρει καλύτερα το περιεχόμενο για τον τρέχοντα ιστότοπο.
+
+Μπορείτε να ορίσετε την προεπιλεγμένη λειτουργία φιλτραρίσματος από τη σελίδα επιλογών του uBOL. Εάν επιλέξετε τη λειτουργία Βέλτιστη ή Ολοκληρωμένη ως προεπιλεγμένη, θα πρέπει να εκχωρήσετε στην uBOL την άδεια ανάγνωσης και τροποποίησης δεδομένων σε όλους τους ιστότοπους.
+
+Λάβετε υπόψη ότι αυτό είναι ακόμη ένα έργο σε εξέλιξη, με αυτούς τους τελικούς στόχους:
+
+- Να μην υπάρχουν ευρείες άδειες hosts κατά την εγκατάσταση -- οι εκτεταμένες άδειες παραχωρούνται ρητά από τον χρήστη σε βάση ανά τοποθεσία.
+
+- Εντελώς δηλωτικό για αξιοπιστία και απόδοση CPU/μνήμης.
diff --git a/platform/mv3/description/webstore.en_GB.txt b/platform/mv3/description/webstore.en_GB.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.en_GB.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.eo.txt b/platform/mv3/description/webstore.eo.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.eo.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.es.txt b/platform/mv3/description/webstore.es.txt
new file mode 100644
index 0000000..2ea6cff
--- /dev/null
+++ b/platform/mv3/description/webstore.es.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) es un bloqueador de contenido con *menos permisos* basado en MV3.
+
+Por defecto ya trae configuradas las siguientes listas de filtros:
+
+- Listas de filtros de uBlock Origin
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+Puedes añadir más conjuntos de reglas visitando la página de opciones, haz clic en el icono de _engranaje_ del panel emergente.
+
+uBOL es completamente declarativo, lo que significa que no hay necesidad de un proceso uBOL permanente para que se produzca el filtrado, y el filtrado de contenido basado en la inyección de CSS/JS se realiza de forma confiable por el propio navegador en lugar de la extensión. Esto significa que uBOL en sí mismo no consume recursos de CPU/memoria mientras el bloqueo de contenido está en curso, el proceso service worker de uBOL se requiere _solo_ cuando se interactúa con el panel emergente o las páginas de opciones.
+
+uBOL no requiere amplios permisos para "leer y modificar datos" en el momento de la instalación, de ahí sus capacidades limitadas en comparación con uBlock Origin u otros bloqueadores de contenido que requieren amplios permisos para "leer y modificar datos" en el momento de la instalación.
+
+Sin embargo, uBOL te permite otorgar *explícitamente* permisos extendidos en sitios específicos de tu elección para que pueda filtrar mejor en esos sitios usando filtrado cosmético e inyecciones de scriptlet.
+
+Para otorgar permisos extendidos en un sitio determinado, abre el panel emergente y elige un modo de filtrado superior, como óptimo o completo.
+
+Después el navegador te advertirá sobre los efectos de otorgar los permisos adicionales solicitados por la extensión en el sitio actual, y deberás indicar al navegador si aceptas o rechazas la solicitud.
+
+Si aceptas la solicitud de uBOL para permisos adicionales en el sitio actual, será capaz de filtrar mejor el contenido para el sitio actual.
+
+Puedes establecer el modo de filtrado predeterminado desde la página de opciones de uBOL. Si eliges como predeterminado el modo óptimo o completo, tendrás que otorgar a uBOL el permiso para leer y modificar datos en todos los sitios web.
+
+Téngase en cuenta que esto todavía es un trabajo en progreso, con estos objetivos finales:
+
+- Sin amplios permisos de host en el momento de la instalación, los permisos ampliados son otorgados explícitamente por el usuario en cada sitio.
+
+- Completamente declarativo para confiabilidad y eficiencia de la CPU/memoria.
diff --git a/platform/mv3/description/webstore.et.txt b/platform/mv3/description/webstore.et.txt
new file mode 100644
index 0000000..d3bf236
--- /dev/null
+++ b/platform/mv3/description/webstore.et.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) on MV3-l põhinev *lubadeta* sisutõkestaja.
+
+Tavaline reeglitekogum vastab uBlock Origini tavalisele filtritekogumile:
+
+- uBlock Origini sisseehitatud filtriloendid
+- EasyList
+- EasyPrivacy
+Peter Lowe'i reklaamide ja jälitusserverite loend
+
+Reeglitekogumeid saate lisada valikute lehelt ehk avanenud paneelis klõpsake _Cogs_ ikooni.
+
+uBOL on läbinisti deklaratiivne ehk filtreerimiseks pole vaja kogu aeg töötavat uBOLi protsessi ja CSS/JS süstipõhist sisu filtreerib tegelikult brauser, mitte laiendus. Teisisõnu, uBOL ei kasuta sisu tõkestamisel protsessori/mälu ressursse. uBOLi teenuse toimimise protsessi on vaja _vaid_ juhul, kui kasutate hüpikpaneeli või valikute lehekülgi.
+
+uBOL ei nõua paigaldamise ajal teadatuntud andmete lugemise ja muutmise luba, seega võrreldes seda nõudva laiendusega uBlock Origin või muu sisutõkestiga on uBOL-i piiratus üks selle funktsioonidest.
+
+Kuid uBOL võimaldab *selgesõnaliselt* anda täpsemaid lubasid teie valitud veebilehtedele, et neid saaks paremini filtreerida ilufiltrite ja skriptisüstidega.
+
+Veebilehele täpsustatud lubade andmiseks ava hüpikpaneel ja vali põhjalikum filtreerimisrežiim, näiteks Optimaalne või Põhjalik.
+
+Seejärel hoiatab brauser teid praeguse saidi laienduse taotletud täiendavate õiguste andmise tagajärgede eest ja peate brauserile ütlema, kas nõustute taotlusega või keeldute sellest.
+
+Kui nõustute uBOLi taotlusega täiendavate õiguste saamiseks praegusel saidil, saab see praeguse saidi sisu paremini filtreerida.
+
+Vaikimisi filtreerimisrežiimi saate määrata uBOLi suvandite lehelt Kui määrate optimaalse või põhjaliku režiimi tavaliseks, peate andma uBOLile loa kõikide veebilehtede andmete lugemiseks ja muutmiseks.
+
+Pidage meeles, et see on veel pooleliolev töö, mille lõppeesmärgid on järgmised:
+
+- Installimise ajal laialdased hostiõigused puuduvad – kasutaja annab laiendatud load selgesõnaliselt saidipõhiselt.
+
+- Täiesti deklaratiivne töökindluse ja protsessori/mälu tõhususe osas.
diff --git a/platform/mv3/description/webstore.eu.txt b/platform/mv3/description/webstore.eu.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.eu.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.fa.txt b/platform/mv3/description/webstore.fa.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.fa.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.fi.txt b/platform/mv3/description/webstore.fi.txt
new file mode 100644
index 0000000..7e5693c
--- /dev/null
+++ b/platform/mv3/description/webstore.fi.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) on *käyttöoikeudeton* MV3-pohjainen sisällönestotyökalu.
+
+Oletusarvoiset sääntömääritykset vastaavat uBlock Origin -laajennuksen oletuksia:
+
+- uBlock Originin sisäänrakennetut suodatinlistat
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+Voit lisätä sääntömäärityksiä asetussivulta -- paina ponnahduspaneelin _Rataskuvaketta_.
+
+uBOL on täysin deklaratiivinen, eli suodatus ei edellytä pysyvää uBOL-prosessia ja CSS-/JS-koodin manipulointiin perustuva sisällönsuodatuksen suorittaa laajennusprosessin sijaan luotettavasti selainsovellus. Tämän ansiosta itse uBOL ei kuormita prosessoria tai keskusmuistia sisällöneston tapahtuessa -- uBOL:n työprosessia tarvitaan _ainoastaan_ ponnahduspaneelia ja asetussivuja käytettäessä.
+
+uBOL ei edellytä laajan tietojen luku- ja muokkausoikeuden myöntämistä asennuksen yhteydessä, jonka vuoksi sen oletusarvoiset valmiudet ovat uBlock Originia ja muita vastaavia sisällönestotyökaluja rajallisemmat.
+
+On kuitenkin mahdollista myöntää *yksinomaisesti* uBOL:lle laajennetut käyttöoikeudet sivustokohtaisesti niiden suodatuksen tehostamiseksi kosmeettisella suodatuksella ja scriplet-injektoinnilla.
+
+Laajemmat oikeudet myönnetään avoimelle sivustolle avaamalla ponnahduspaneeli ja valitsemalla korkeampi suodatustaso, kuten Optimaalinen tai Täysi.
+
+Tällöin selain varoittaa laajennuksen avoimelle sivustolle pyytämien käyttöoikeuksien seurauksista ja pyytää hyväksymään tai hylkäämään pyynnön.
+
+Jos uBOL:n käyttöoikeuspyyntö avoimelle sivustolle hyväksytään, se pystyy suodattamaan sivuston sisältöä tehokkaammin.
+
+Voit asettaa oletusarvoisen suodatustilan uBOL:n asetussivulta. Jos valitset oletustilaksi Optimaalinen tai Täysi, on uBOL:lle myönnettävä oikeus lukea ja muokata tietojasi kaikilla verkkosivustoilla.
+
+Huomioithan, että laajennuksen kehitys vielä kesken, seuraavilla tavoitteilla:
+
+- Laajoja käyttöoikeuksia ei tarvita asennusvaiheesssa, vaan laajennetut oikeudet myönnetään aina sivustokohtaisesti käyttäjän toimesta.
+
+- Täysin deklaratiivinen luotettavuutta ja prosessorin/muistin kuormituksen keventämiseksi.
diff --git a/platform/mv3/description/webstore.fil.txt b/platform/mv3/description/webstore.fil.txt
new file mode 100644
index 0000000..0bb5710
--- /dev/null
+++ b/platform/mv3/description/webstore.fil.txt
@@ -0,0 +1,30 @@
+Ang uBO Lite (uBOL) ay isang eksperimental at *permission-less* na tagaharang ng content na nakabase sa MV3.
+
+Tulad ng uBlock Origin, ito rin ang mga default na listahan ng mga filter:
+
+- Mga built-in na listahan ng mga filter ng uBlock Origin
+- EasyList
+- EasyPrivacy
+- Listahan ni Peter Lowe sa mga ad at tracking server (Peter Lowe’s Ad and tracking server list)
+
+Makakapagdagdag ka ng higit pang mga patakaran sa pahina ng mga opsyon -- pindutin ang icon ng _gulong_ sa popup panel.
+
+Deklaratibo lamang ang uBOL, kaya hindi nito kailangan ng permanenteng proseso upang mag-filter, at mainam na ginagawa ng browser mismo imbes na ekstensyon ang pagfi-filter sa content na nakabase sa CSS o JS. Ibig-sabihin, hindi kumokonsyumo ng CPU o memorya ang uBOL habang nanghaharang -- ang proseso ng trabahante ng serbisyo ay kailangan _lang_ kung nasa popup panel o pahina ng opsyon ka.
+
+Hindi kailangan ng uBOL ang malawakang pahintulot para "basahin at baguhin ang data" pagka-install, kaya kung bago pa lang itong install ay limitado ang kakayahan nito kumpara sa uBlock Origin o iba pang mga pangharang ng content na nangangailangan ng malawakang pahintulot para "basahin at baguhin ang data" pagka-install.
+
+Ngunit, pwede mong *pasadyang* pahintulutan ang uBOL na magkaroon ng pinalawak na pahintulot sa mga website na pipiliin mo para mas mapabuti ang pagfi-filter sa mga site na iyon gamit ang kosmetikong pagfi-filter at injeksyon ng scriptlet.
+
+Upang bigyan ito ng pinalawak na pahintulot sa isang site, buksan ang popup panel at pumili ng isang mode sa pagfi-filter tulad ng Pinainam o Kumpleto.
+
+Babalaan ka ng browser tungkol sa mga epekto ng pagbibigay ng karagdagang pahintulot na hinihiling ng ekstensyon sa kasalukuyang site, at kailangan mong tumugon kung pinapahintulutan mo ba ito o hindi.
+
+Kung tatanggapin mo ang hiling ng uBOL para sa karagdagang mga pahintulot sa kasalukuyang site, mas magiging mainam ang pagfi-filter nito sa content para sa kasalukuyang site.
+
+Maitatakda mo ang default na mode sa pagfi-filter sa pahina ng mga opsyon ng uBOL. Kailangan mong pahintulutan ang uBOL na basahin o baguhin ang datos sa lahat ng mga website kung pipiliin mo ang Pinainam o Kumpleto bilang default na mode sa pagfi-filter.
+
+Tandaang kasalukuyan pang binubuo ang ekstensyong ito, at nilalayon nitong:
+
+- Walang kakailanganing malawakang pahintulot pagka-install -- ibibigay lang ng user ang karagdagang pahintulot sa mga piling site.
+
+- Deklaratibo lamang upang maging mapagkakatiwalaan at matipid sa CPU at memorya.
diff --git a/platform/mv3/description/webstore.fr.txt b/platform/mv3/description/webstore.fr.txt
new file mode 100644
index 0000000..12b9d01
--- /dev/null
+++ b/platform/mv3/description/webstore.fr.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) est un bloqueur de contenu *sans permission* basé sur le manifeste MV3.
+
+Les règles par défaut correspondent au filtrage par défaut d'uBlock Origin :
+
+- Les listes de filtres natifs d'uBlock Origin
+- EasyList
+- EasyPrivacy
+- La liste anti-serveurs pub et pistage de Peter Lowe
+
+Vous pouvez ajouter plus de règles en consultant la page des paramètres -- Cliquez sur l'_Engrenage_ dans le panneau pop-up.
+
+uBOL est entièrement déclarative, c'est-à-dire qu'il n'y a pas besoin d'un processus uBOL permanent pour filtrer, et le filtrage basé sur l'injection CSS/JavaScript se fait en toute fiabilité par le navigateur lui-même. Cela veut dire qu'en soi, uBOL ne consomme pas de ressources processeur/mémoire pendant le blocage de contenu -- l'agent de service d'uBOL n'est sollicité _que_ quand vous interagissez avec le panneau pop-up ou la page des paramètres.
+
+Contrairement à uBlock Origin ou d'autres extensions de blocage, uBOL ne nécessite pas de larges permissions de "lecture/modification des données" au moment de l'installation, ce qui explique ses capacités au départ limitées.
+
+Cependant, uBOL vous permet *explicitement* d'accorder des permissions étendues sur les sites Web de votre choix, pour qu'elle puisse mieux les filtrer en utilisant le filtrage esthétique et des injections de scriptlet.
+
+Pour accorder des permissions étendues sur un site Web donné, ouvrez le panneau pop-up et choisissez un mode de filtrage plus élevé comme le mode Optimal ou le mode Complet.
+
+Le navigateur vous préviendra alors des effets de l'accord de permissions additionnelles requises par l'extension sur le site Web en cours de consultation et vous devrez indiquer votre choix au navigateur (Accepter/Refuser).
+
+Si vous acceptez la requête d'uBOL pour des permissions additionnelles sur le site Web en cours de consultation, le filtrage de son contenu sera renforcé.
+
+Vous pouvez définir le mode de filtrage par défaut depuis la page des paramètres d'uBOL. Si vous choisissez le mode Optimal ou Complet en tant que mode par défaut, vous devrez accorder à uBOL l'autorisation de lire et de modifier des données sur tous les sites Web.
+
+Gardez à l'esprit que c'est en cours de développement, avec comme objectifs :
+
+- De ne pas accorder de permissions globales au moment de l'installation -- les permissions étendues s'accordent explicitement par l'utilisateur site par site.
+
+- De travailler de manière entièrement déclarative pour la fiabilité et l'efficacité processeur/mémoire.
diff --git a/platform/mv3/description/webstore.fy.txt b/platform/mv3/description/webstore.fy.txt
new file mode 100644
index 0000000..63fa94a
--- /dev/null
+++ b/platform/mv3/description/webstore.fy.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is in *tastimmingsleaze* MV3-basearre ynhâldsblokkearder.
+
+De standert regelset komt oerien mei de standert filterset fan uBlock Origin:
+
+- Ynboude filterlisten fan uBlock Origin
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking-serverlist
+
+Jo kinne mear regelsets tafoegje troch de opsjesside te besykjen – klik op it _tântsjilpiktogram_ yn it pop-uppaniel.
+
+uBOL is folslein deklaratyf, wat betsjut dat in permanint uBOL-proses foar de filtering net nedich is, en ynhâldsfiltering op basis fan CSS/JS-ynjeksje op in betroubere manier troch de browser sels útfierd wurdt yn stee fan de útwreiding. Dit betsjut dat uBOL sels gjin CPU-/ûnthâldboarnen brûkt wylst ynhâldsblokkearring aktyf is – it serviceworker-proses fan uBOL is _allinnich_ fereaske as jo mei it pop-uppaniel of de opsjessiden wurkje.
+
+uBOL fereasket gjin brede tastimming foar it ‘lêzen en oanpassen fan gegevens’ wylst ynstallaasje, fan dêr de beheinde ynboude mooglikheden dêrfan yn fergeliking mei uBlock Origin of oare ynhâldsblokkearders dy’t brede tastimmingen foar it ‘lêzen en oanpassen fan gegevens’ fereaskje wylst de ynstallaasje.
+
+Jo kinne yn uBOL echter *eksplisyt* wiidweidige tastimmingen ferliene op bepaalde websites fan jo kar, sadat it op dy websites better filterje kin fia kosmetyske filtering en scriptlet-ynjeksjes.
+
+Om wiidweidige tastimmingen op in bepaalde website te ferlienen, iepenje jo it pop-uppaniel en kieze jo in hegere filtermodus, lykas Optimaal of Folslein.
+
+De browser warskôget jo dan oer de gefolgen fan it ferlienen fan de troch de útwreiding oanfrege oanfoljende tastimmingen op de aktuele website, en jo moatte de browser witte litte oft jo de oanfraach akseptearje of wegerje.
+
+As jo de oanfraach fan uBOL foar oanfoljende tastimmingen op de aktuele website akseptearje, sil it ynhâld foar de aktuele website better filterje kinne.
+
+Jo kinne de standert filtermodus ynstelle fan de opsjesside fan uBOL ôf. As jo de modus Optimaal of Folslein as de standertmodus kieze, moatte jo uBOL de tastimming foar it lêzen en oanpassen fan gegevens op alle websites te ferlienen.
+
+Unthâld dat dit noch wurk yn útfiering is, mei dizze eindoelen:
+
+- Gjin brede host-tastimmingen wylst ynstallaasje – wiidweidige tastimmingen wurde eksplisyt en per website ferliend troch de brûker.
+
+- Folslein deklaratyf omwille fan betrouberheid en CPU-/ûnthâldeffisjinsje.
diff --git a/platform/mv3/description/webstore.gl.txt b/platform/mv3/description/webstore.gl.txt
new file mode 100644
index 0000000..a82432f
--- /dev/null
+++ b/platform/mv3/description/webstore.gl.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) é un bloqueador de contido baseado en MV3 *sen permisos.
+
+O conxunto de regras predeterminado corresponde ao conxunto de filtros predeterminado de uBlock Origin:
+
+- Listas de filtros integradas de uBlock Origin
+- EasyList
+- EasyPrivacy
+Lista de servidores de seguimento e anuncios de Peter Lowe
+
+Podes engadir máis conxuntos de regras visitando a páxina de opcións: fai clic na icona _Cogs_ no panel emerxente.
+
+uBOL é totalmente declarativo, o que significa que non é necesario un proceso permanente de uBOL para que se produza o filtrado e o filtrado de contido baseado en inxección de CSS/JS realízao de forma fiable o propio navegador en lugar da extensión. Isto significa que o propio uBOL non consume recursos de CPU/memoria mentres o bloqueo de contido está en curso -- o proceso do traballador do servizo de uBOL é necesario _só_ cando interactúas co panel emerxente ou coas páxinas de opcións.
+
+uBOL non require amplos permisos de "ler e modificar datos" no momento da instalación, de aí as súas capacidades limitadas fóra da caixa en comparación con uBlock Origin ou outros bloqueadores de contido que requiren amplos permisos de "ler e modificar datos" no momento da instalación.
+
+Non obstante, uBOL permítelle *de forma explícita* conceder permisos estendidos en sitios específicos da súa elección para que poida filtrar mellor neses sitios mediante filtrado cosmético e inxeccións de scriptlet.
+
+Para conceder permisos estendidos nun sitio determinado, abra o panel emerxente e escolle un modo de filtrado superior, como Óptimo ou Completa.
+
+A continuación, o navegador avisará sobre os efectos da concesión dos permisos adicionais solicitados pola extensión no sitio actual, e terá que indicarlle ao navegador se acepta ou rexeita a solicitude.
+
+Se aceptas a solicitude de uBOL de permisos adicionais no sitio actual, poderá filtrar mellor o contido do sitio actual.
+
+Podes establecer o modo de filtrado predeterminado desde a páxina de opcións de uBOL. Se escolle o modo Óptimo ou Completo como o predeterminado, terá que conceder a uBOL o permiso para ler e modificar datos en todos os sitios web.
+
+Teña en conta que este aínda é un traballo en curso, cos seguintes obxectivos finais:
+
+- Non hai permisos de host amplos no momento da instalación. Os permisos estendidos son concedidos explícitamente polo usuario por cada sitio.
+
+- Totalmente declarativo para a fiabilidade e a eficiencia da CPU/memoria.
diff --git a/platform/mv3/description/webstore.gu.txt b/platform/mv3/description/webstore.gu.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.gu.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.he.txt b/platform/mv3/description/webstore.he.txt
new file mode 100644
index 0000000..d931b3d
--- /dev/null
+++ b/platform/mv3/description/webstore.he.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) הוא חוסם תוכן *ללא הרשאות* מבוסס MV3.
+
+ערכת הכללים ברירת מחדל שמתכתבת עם ערכת המסננים של uBlock Origin:
+
+- רשימת מסננים מובנים של uBlock Origin
+- EasyList
+- EasyPrivacy
+- רשימת שרתי מודעות ומעקב של פיטר לואו
+
+ניתן להוסיף ערכות כללים נוספות מעמוד האפשרויות –על ידי הקשה על סמל _Cogs_ בלוח הצץ.
+
+uBOL הוא הכרזתי לחלוטין, כלומר אין צורך בתהליך uBOL קבוע כדי שהסינון יתרחש, וסינון תוכן מבוסס הזרקת CSS/JS מבוצע באופן אמין על ידי הדפדפן עצמו ולא על ידי ההרחבה. המשמעות היא ש־uBOL עצמו לא צורכך משאבי מעבד/זיכרון בזמן שחסימת התוכן מתרחשת – תהליך ה־service worker של uBOL נדרש _אך ורק_ בזמן הידוד עם החלון הקופץ או עם עמוד האפשרויות.
+
+uBOL לא דורש הרשאת "קריאה ושינוי נתונים" נרחבות במהלך ההתקנה, לכן היכולות המוגבלות שלה הישר מהקופסה בהשוואה ל־uBlock Origin או חוסמי תוכן אחרים הדורשים הרשאות "קריאה ושינוי נתונים" נרחבות כבר בזמן ההתקנה.
+
+עם זאת, uBOL מאפשר להעניק *באופן מפורש* הרשאות נרחבות לאתרים מסויימים על פי בחירה, למיטוב הסינון באתרים אלה, באמצעות סינון קוסמטי והזרקות סקריפלטים.
+
+כדי להעניק הרשאות נרחבות באתר נתון, נא לפתוח את הלוח הקופץ ולבחור באופן סינון גבוה יותר כגון מיטבי או מלא.
+
+לאחר מכן, תוצג אזהרת דפדפן על השפעות מתן הרשאות נוספות אותן מבקשת ההרחה באתר הנוכחי, הדפדפן ימתין לקבלת תשובה האם לקבל או לדוחות את בקשת ההרשאה.
+
+אם תקבל את הבקשה של uBOL להרשאות נוספות באתר הנוכחי, הוא יוכל לסנן טוב יותר תוכן עבור האתר הנוכחי.
+
+ניתן להגדיר את מצב הסינון המוגדר כברירת מחדל מדף האפשרויות של uBOL. אם הבחירה היתה באופןסינון מיטבי או מלא כברירת המחדל, יידרש להעניק ל־uBOL הרשאת קריאה שנוי נתונים בכל אתרי הרשת.
+
+יש לזכור שזו עדיין 'עבודה בתהליך', עם המטרות הבאות:
+
+- אין הרשאות מארח רחבות בזמן ההתקנה -- הרשאות מורחבות מוענקות במפורש על ידי המשתמש על בסיס לכל אתר.
+
+הכרזתי לחלוטין, אמין ויעיל בצריכת משאבי מעבד/זיכרון.
diff --git a/platform/mv3/description/webstore.hi.txt b/platform/mv3/description/webstore.hi.txt
new file mode 100644
index 0000000..7b361bc
--- /dev/null
+++ b/platform/mv3/description/webstore.hi.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) एक *अनुमति-रहित* MV3-आधारित कन्टेन्ट ब्लॉकर है।
+
+डिफ़ॉल्ट रूलसेट uBlock Origin के डिफ़ॉल्ट फ़िल्टर सेट के अनुरूप होता है:
+
+- uBlock Origin की बिल्ट-इन फ़िल्टर सूचियां
+- EasyList
+- EasyPrivacy
+- Peter Lowe की विज्ञापन एवं ट्रैकिंग सर्वर सू‍ची
+
+आप विकल्प पृष्ठ पर जाकर और अधिक रूलसेट जोड़ सकते हैं -- पॉपअप पैनल में _Cogs_ आइकन पर क्लिक करें।
+
+uBOL पूरी तरह से वर्णनात्मक है, जिसका यह अर्थ है कि फ़िल्टरिंग के लिए एक स्थायी uBOL प्रक्रिया की कोई आवश्यकता नहीं है, और CSS/JS इंजेक्शन-आधारित कन्टेन्ट फ़िल्टरिंग एक्सटेंशन के बजाय ब्राउज़र द्वारा विश्वसनीय रूप से की जाती है। इसका यह अर्थ है कि कन्टेन्ट ब्लॉक करते समय uBOL द्वारा सीपीयू/मेमोरी संसाधनों का उपभोग स्वयं नहीं किया जाता है -- uBOL की सर्विस प्रोसेस की आवश्यकता _केवल_ तब होती है जब आप पॉपअप पैनल या विकल्प पृष्ठों पर कोई अंत:क्रिया करते हैं।
+
+uBOL को इन्सटॉल करते समय "डेटा को पढ़ने और संशोधित करने" की व्यापक अनुमति की आवश्यकता नहीं होती है, अतः इसकी सीमित क्षमताओं तत्काल उपयोगिता की तुलना में uBlock Origin या अन्य कन्टेन्ट ब्लॉकर को इन्सटॉलेशन के समय "डेटा को पढ़ने और संशोधित करने" की व्यापक अनुमतियों की आवश्यकता होती है।
+
+हालांकि, uBOL आपको अपनी मनपसंद विशिष्ट साइटों पर विस्तारित अनुमतियां देना *स्पष्ट रूप से* अनुमत करता है ताकि यह कॉस्मेटिक फ़िल्टरिंग और स्क्रिप्टलेट इंजेक्शन का उपयोग करके उन साइटों पर अच्छी तरह से फ़िल्टर कर सके।
+
+किसी एक साइट पर विस्तारित अनुमतियां देने के लिए, पॉपअप पैनल खोलें और उच्च फ़िल्टरिंग मोड, जैसे कि 'अनुकूलतम' (ऑप्टिमल) या 'पूर्ण' (कंपलीट) चुनें।
+
+इसके बाद ब्राउज़र द्वारा आपको वर्तमान साइट पर एक्सटेंशन द्वारा अनुरोधित अतिरिक्त अनुमतियों को देने के प्रभावों के बारे में चेतावनी दी जाएगी, और आपको ब्राउज़र को यह बताना होगा कि आप अनुरोध को स्वीकार करते हैं या अस्वीकार करते हैं।
+
+यदि आप वर्तमान साइट पर अतिरिक्त अनुमतियों के लिए uBOL के अनुरोध को स्वीकार करते हैं, तो यह वर्तमान साइट के लिए कन्टेन्ट अच्छी तरह से फ़िल्टर करने में सक्षम होगा।
+
+आप uBOL के विकल्प पृष्ठ से डिफ़ॉल्ट फ़िल्टरिंग मोड को सेट कर सकते हैं। यदि आप 'अनुकूलतम' (ऑप्टिमल) या 'पूर्ण' (कंपलीट) मोड को डिफ़ॉल्ट रूप से चुनते हैं, तो आपको uBOL को सभी वेबसाइटों पर डेटा को पढ़ने और संशोधित करने के लिए अनुमत करना होगा।
+
+ध्यान रखें कि यह कार्य अभी भी प्रगतिधीन है, और इसके न‍िम्नांकित अंतिम लक्ष्यों तय किये गए हैं:
+
+- इन्सटॉल करते समय कोई व्यापक होस्ट अनुमतियां नहीं -- विस्तारित अनुमतियां उपयोगकर्ता द्वारा हर एक साइट के आधार पर स्पष्ट रूप से दी जाती हैं।
+
+- विश्वसनीयता और सीपीयू/मेमोरी दक्षता के लिए पूरी तरह वर्णनात्मक।
diff --git a/platform/mv3/description/webstore.hr.txt b/platform/mv3/description/webstore.hr.txt
new file mode 100644
index 0000000..898bc20
--- /dev/null
+++ b/platform/mv3/description/webstore.hr.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) je bloker sadržaja *bez dopuštenja* baziran na MV3.
+
+Zadana lista pravila odgovara uBlock Origin-ovoj zadanoj listi filtera:
+
+- uBlock Origin ugrađene liste filtera
+- EasyList
+- EasyPrivacy
+- Peter Lowe-ova lista oglasa i pratećih servera
+
+Možete dodati više skupova pravila tako što ćete posjetiti stranicu s opcijama -- kliknite ikonu _zupčanika_ na skočnoj ploči.
+
+uBOL je u potpunosti deklarativan, što znači da nema potrebe za trajnim uBOL procesom za filtriranje, a filtriranje sadržaja temeljeno na ubacivanju CSS/JS pouzdano izvodi sam preglednik, a ne ekstenzija. To znači da sam uBOL ne troši CPU/memorijske resurse dok je blokiranje sadržaja u tijeku -- uBOL-ov servisni radni proces potreban je _samo_ kada komunicirate s skočnom pločom ili stranicama s opcijama.
+
+uBOL ne zahtijeva široku dozvolu za "čitanje i izmjenu podataka" u vrijeme instalacije, stoga ima zadane ograničene mogućnosti u usporedbi s uBlock Origin ili drugim blokatorima sadržaja koji zahtijevaju veću dozvolu za "čitanje i izmjenu podataka" u vrijeme instalacije.
+
+Međutim, uBOL vam omogućuje da *eksplicitno* dodijelite proširena dopuštenja na određenim web-stranicama po vašem izboru tako da može bolje filtrirati te web-stranice koristeći kozmetičko filtriranje i injekcijske skripte.
+
+Da biste dodijelili proširena dopuštenja na određenoj web stranici, otvorite skočnu ploču i odaberite viši način filtriranja kao što je Optimalno ili Potpuno.
+
+Preglednik će vas tada upozoriti o učincima dodjele dodatnih dopuštenja koje traži ekstenzija na trenutnom mjestu, a vi ćete morati reći pregledniku prihvaćate li ili odbijate zahtjev.
+
+Ako prihvatite uBOL-ov zahtjev za dodatnim dozvolama na trenutnoj stranici, moći će bolje filtrirati sadržaj na njoj.
+
+Zadani način filtriranja možete postaviti na stranici s opcijama uBOL-a. Ako kao zadano odaberete Optimalni ili Potpuni način rada, morati ćete dati uBOL-u dopuštenje za čitanje i izmjenu podataka na svim web stranicama.
+
+Imajte na umu da je ovo još u tijeku, sa sljedećim krajnjim ciljevima:
+
+- Nema širokih dopuštenja hosta u vrijeme instalacije -- proširena dopuštenja izričito dodjeljuje korisnik za svaku pojedinačnu stranicu.
+
+- Potpuno deklarativno za pouzdanost i učinkovitost CPU/memorije.
diff --git a/platform/mv3/description/webstore.hu.txt b/platform/mv3/description/webstore.hu.txt
new file mode 100644
index 0000000..4d54e2f
--- /dev/null
+++ b/platform/mv3/description/webstore.hu.txt
@@ -0,0 +1,30 @@
+Az uBO Lite (uBOL) egy *engedélyt nem igénylő* MV3-alapú tartalomblokkoló.
+
+Az alapértelmezett szabálykészlet megfelel a uBlock Origin alapértelmezett szűrőkészletének:
+
+- uBlock Origin beépített szűrőlistái
+- EasyList
+- EasyPrivacy
+- Peter Lowe hirdetési és nyomkövető-kiszolgálókat tartalmazó listája
+
+További szabályokat adhat hozzá a beállítások oldalon – kattintson a _Fogaskerekek_ ikonra a felugró panelen.
+
+Az uBOL teljes mértékben deklaratív, vagyis nincs szükség állandó uBOL folyamatra a szűréshez, és a CSS/JS injektálás-alapú tartalomszűrést maga a böngésző végzi megbízhatóan, nem pedig a kiegészítő. Ez azt jelenti, hogy az uBOL maga nem fogyaszt CPU/memória erőforrásokat, amíg a tartalom blokkolása folyamatban van – az uBOL service worker folyamatára _csak_ akkor van szükség, amikor az felugró panellel vagy a beállítási oldalakkal érintkezik.
+
+Az uBOL nem igényel széles körű „adatok módosítása és olvasása” engedélyt a telepítéskor, ezért korlátozott képességei vannak az uBlock Originhez vagy más tartalomblokkolókhoz képest, amelyek széles körű „adatok olvasása és módosítása” engedélyeket igényelnek a telepítésükkor.
+
+Az uBOL azonban lehetővé teszi, hogy *kifejezetten* kiterjesztett engedélyeket adjon az Ön által választott bizonyos webhelyekhez, hogy jobban szűrhessen ezeken a webhelyeken kozmetikai szűréssel és szkriptlet-injekciókkal.
+
+Ha kiterjesztett engedélyeket szeretne adni egy adott webhelyen, nyissa meg az előugró panelt, és válasszon magasabb szűrési módot, például Optimális vagy Teljes.
+
+A böngésző ekkor figyelmezteti Önt a bővítmény által kért további engedélyek megadásának hatásaira az aktuális webhelyen, és közölnie kell a böngészővel, hogy elfogadja-e vagy elutasítja a kérést.
+
+Ha elfogadja az uBOL további engedélyekre vonatkozó kérését az aktuális webhelyen, akkor jobban tudja szűrni az aktuális webhely tartalmát.
+
+Az alapértelmezett szűrési módot az uBOL beállítási oldalán állíthatja be. Ha az Optimális vagy a Teljes módot választja alapértelmezettként, akkor az uBOL-nak engedélyt kell adnia az adatok olvasására és módosítására az összes webhelyen.
+
+Ne feledje, hogy ez még folyamatban van, a következő célokkal:
+
+- Nincsenek széles körű gazdagép-engedélyek a telepítés során – a kiterjesztett engedélyeket a felhasználó kifejezetten webhelyenként adja meg.
+
+- Teljesen deklaratív a nagyobb megbízhatóság illetve CPU- és memóriahatékonyság érdekében.
diff --git a/platform/mv3/description/webstore.hy.txt b/platform/mv3/description/webstore.hy.txt
new file mode 100644
index 0000000..c847ac6
--- /dev/null
+++ b/platform/mv3/description/webstore.hy.txt
@@ -0,0 +1,34 @@
+uBO Lite (uBOL)-ը բովանդակության արգելափակիչ է, որը *չի պահանջում թույլտվություններ*, և հիմնված է MV3-ի վրա։
+
+Կանոնների լռելյայն փաթեթը համապատասխանում է uBlock Origin-ի լռելյայն զտիչների փաթեթին։
+
+- uBlock Origin-ի ներկառուցված զտիչների ցանկ
+- EasyList
+- EasyPrivacy
+- Peter Lowe-ի գովազդային և հետագծող սպասարկիչների ցուցակ
+
+Դուք կարող եք ավելացնել ուրիշ կանոններ՝ այցելելով ընտրանքների էջը. կտտացրեք Ժանանվակի_պատկերակին դուրս լողացող վահանակում։
+
+uBOL-ն ամբողջությամբ դեկլարատիվ է, այսինքն՝ զտման համար անընդհատ կատարվող uBOL գործընթացի կարիք չկա, իսկ CSS/JS արմատավորման վրա հիմնված բովանդակության զտումը հուսալիորեն իրականացվում է զննիչի կողմից, այլ ոչ թե ընդլայնման միջոցով։ Սա նշանակում է, որ uBOL հավելումը չի սպառում մշակիչի/հիշողության որևէ ռեսուրս, երբ տեղի է ունենում գովազդի արգելափակումը. uBOL աշխատանքային գործընթացն աշխատում է _միայն_ երբ Դուք փոփոխություններ եք կատարում դուրս լողացող վահանակում կամ ընտրանքների էջում։
+
+uBOL-ը տեղադրման ժամանակ «տվյելները լիովին ընթերցելու և փոփոխելու» թույլտվություն չի պահանջում, ուստի այն ունի սահմանափակ հնարավորություններ՝ համեմատած uBlock Origin-ի և բովանդակության այլ արգելափակիչների հետ, որոնք տեղադրման ժամանակ պահանջում են այդպիսի թույլտվություն։
+
+Однако uBOL позволяет *намеренно* давать расширенные разрешения для определенных сайтов - по вашему усмотрению, чтобы эффективнее работать, используя косметическую фильтрацию и scriptlet-внедрения.
+
+Այնուամենայնիվ, uBOL-ը թույլ է տալիս *դիտմամբ* տրամադրել ընդլայնված թույլտվություններ Ձեր ընտրած կայքերի համար, որպեսզի այն կարողանա էլ ավելի լավ զտել այդ կայքերը՝ օգտագործելով կոսմետիկ զտումը և սցենարների արմատավորումները։
+
+Для предоставления расширенных разрешений на текущем сайте - откройте всплывающую панель и выберите повышенный режим фильтрации: Оптимальный или Полный.
+
+Ընթացիկ կայքում ընդլայնված թույլտվություններ տրամադրելու համար բացեք դուրս լողացող վահանակը և ընտրեք ընդլայնված զտման ռեժիմ՝ Գերադասելի կամ Ամբողջական։
+
+Այնուհետև զննիչը կզգուշացնի Ձեզ ընթացիկ կայքում ընդլայնման կողմից պահանջվող լրացուցիչ թույլտվությունների տրամադրման հետևանքների մասին, և Դուք պետք է ընտրեք՝ ընդունում եք, թե մերժում եք հայտը։
+
+Եթե ​​ընդունեք uBOL-ին լրացուցիչ թույլտվություններ տալու հայտը, ապա այն կկարողանա ավելի արդյունավետ կերպով զտել ընթացիկ կայքի բովանդակությունը։
+
+Դուք կարող եք սահմանել զտման լռելյայն ռեժիմը uBOL-ի ընտրանքների էջում։ Եթե ​​որպես լռելյայն ընտրեք «Գերադասելի» կամ «Ամբողջական» ռեժիմը, պետք կլինի uBOL-ին թույլտվություն տրամադրեք կարդալու և փոփոխելու տվյալները բոլոր կայքերում։
+
+Հիշեք, որ այս նախագիծը մշակման ակտիվ փուլում է, որ ունի հետևյալ նպատակները.
+
+- Տեղադրման ընթացքում Սահմանափակ թույլտվություններով աշխատանք տեղադրման ժամանակ. օգտվողը ընդլայնված թույլտվություններ է տալիս իր հայեցողությամբ, յուրաքանչյուր կայքի համար առանձին։
+
+- Ամբողջովին դեկլարատիվ է հուսալիության և մշակիչի/հիշողության արտադրողականության համար։
diff --git a/platform/mv3/description/webstore.id.txt b/platform/mv3/description/webstore.id.txt
new file mode 100644
index 0000000..33c86b6
--- /dev/null
+++ b/platform/mv3/description/webstore.id.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) adalah pemblokir konten berbasis MV3 yang membutuhkan lebih sedikit perizinan.
+
+Kumpulan aturan bawaan sesuai dengan kumpulan penyaringan bawaan uBlock Origin:
+
+- Daftar filter bawaan uBlock Origin
+- EasyList
+- EasyPrivacy
+- Daftar server iklan dan pelacak Peter Lowe
+
+kamu dapat menambahkan rulesets di halaman opsi -- klik ikon _Cogs_ di panel popup.
+
+uBOL sepenuhnya deklaratif, yang mana tidak membutuhkan proses permanen uBOL agar penyaringan dapat terjadi, dan penyaringan konten berbasis injeksi CSS/JS dilakukan sepenuhnya oleh peramban itu sendiri ketimbang oleh ekstensi. Ini berarti bahwa uBOL sendiri tidak mengkonsumsi sumber daya CPU/memori selama melakukan pemblokiran konten -- proses pekerja layanan uBOL dibutuhkan _hanya_ ketika Anda berinteraksi dengan panel popup atau opsi halaman.
+
+uBOL tidak membutuhkan izin "baca dan modifikasi data" pada waktu penginstalan, maka kemampuannya lebih terbatas jika dibandingkan dengan uBlock Origin atau pemblokir konten lain yang memerlukan izin "baca dan modifikasi data" pada waktu penginstalan.
+
+Namun, uBOL memberi anda opsi untuk *secara eksplisit* memberikan izin tambahan pada situs pilihan Anda, sehingga dapat memfilter situs tersebut dengan lebih baik menggunakan pemfilteran kosmetik dan injeksi scriptlet.
+
+Untuk memberikan izin tambahan pada situs tertentu, buka panel popup dan pilih mode pemfilteran yang lebih tinggi seperti Optimal atau Complete.
+
+Perambaan kemudian akan memperingatkan anda tentang efek memberikan izin tambahan yang diminta oleh ekstensi pada situs saat ini, dan Anda harus memberitahu perambaan apakah anda menyetujui atau menolak permintaan.
+
+Jika Anda menyetujui permintaan uBOL untuk izin tambahan pada situs terkini, uBOL akan dapat menyaring konten dengan lebih baik untuk situs terkini.
+
+Anda dapat menentukan mode penyaringan bawaan dari halaman pengaturan uBOL Jika Anda memilih mode Optimal atau Complete sebagai mode bawaan, Anda perlu memberikan uBOL izin untuk membaca dan mengubah data pada semua situs web.
+
+Mohon diingat bahwa ini msaih dalam tahap proses pengerjaan, dengan tujuan akhir sebagai berikut:
+
+- Tidak ada izin pengguna yang luas saat penginstalan -- izin tambahan diberikan secara eksplisit oleh pengguna berdasarkan tiap situs.
+
+- Sepenuhnya delkaratif untuk reliabilitas dan CPU/efisiensi memori.
diff --git a/platform/mv3/description/webstore.it.txt b/platform/mv3/description/webstore.it.txt
new file mode 100644
index 0000000..0aa86f4
--- /dev/null
+++ b/platform/mv3/description/webstore.it.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) è un sistema per bloccare contenuti che *non richiede autorizzazioni* basato su MV3.
+
+L'insieme di regole predefinite corrisponde a quello di uBlock Origin:
+
+- Elenco dei filtri integrati in uBlock Origin
+- EasyList
+- EasyPrivacy
+- Elenco dei server di tracciatura e pubblicità di Peter Lowe
+
+Puoi aggiungere altre regole nella pagina delle opzioni. Clicca sull'icona _Ingranaggi_ nel pannello a comparsa.
+
+uBOL è interamente dichiarativo ovvero non è necessario un processo uBOL permanente per eseguire il filtraggio e il filtraggio dei contenuti CSS/JS inietattai viene eseguito in modo affidabile dal browser stesso piuttosto che dall'estensione. Ciò significa che lo stesso uBOL non consuma risorse di CPU/memoria mentre il blocco dei contenuti è in corso: il processo di lavoro di servizio di uBOL è richiesto _solo_ quando interagisci con il pannello popup o le pagine delle opzioni.
+
+uBOL non richiede un'ampia autorizzazione di "lettura e modifica dei dati" al momento dell'installazione, da qui le sue capacità limitate rispetto a uBlock Origin o ad altre estensioni che richiedono ampie autorizzazioni di "lettura e modifica dei dati" al momento dell'installazione.
+
+Tuttavia, uBOL consente di concedere *esplicitamente* permessi estesi a siti specifici di vostra scelta, in modo da poter filtrare meglio tali siti utilizzando il filtraggio cosmetico e le iniezioni di scriptlet.
+
+Per concedere autorizzazioni estese su un determinato sito, apri il pannello popup e scegli una modalità di filtraggio più restrittiva come Ottimale o Completa.
+
+Il browser ti avviserà degli effetti della concessione delle autorizzazioni aggiuntive richieste dall'estensione sul sito corrente e dovrai comunicare al browser se accetti o rifiuti la richiesta.
+
+Se accetti la richiesta di uBOL per ulteriori autorizzazioni sul sito corrente, sarà in grado di filtrare meglio i contenuti per il sito corrente.
+
+Puoi impostare la modalità di filtraggio predefinita dalla pagina delle opzioni di uBOL. Se scegli come predefinita la modalità Ottimale o Completa, dovrai concedere a uBOL il permesso di leggere e modificare i dati di tutti i siti web.
+
+Tieni presente che questo è ancora un work in progress, con questi obiettivi finali:
+
+- Nessuna autorizzazione host ampia al momento dell'installazione: le autorizzazioni estese vengono concesse esplicitamente dall'utente in base al sito.
+
+- Interamente dichiarativo per l'affidabilità e l'efficienza della CPU/memoria.
diff --git a/platform/mv3/description/webstore.ja.txt b/platform/mv3/description/webstore.ja.txt
new file mode 100644
index 0000000..5ae015f
--- /dev/null
+++ b/platform/mv3/description/webstore.ja.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) は権限を必要としない MV3 ベースのコンテンツブロッカーです。
+
+デフォルトのルールセットは以下の通り。uBlock Origin のデフォルトフィルターセットと同じです。
+
+- uBlock Origin の内製フィルターリスト
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+オプションページでルールセットを追加できます -- ポップアップ パネルの「歯車」アイコンをクリックします。
+
+uBOL は完全に宣言的です。つまり、フィルタリングを行うための恒久的な uBOL プロセスは必要なく、CSS/JS インジェクション ベースのコンテンツフィルタリングは拡張機能ではなくブラウザによって、確実に実行されます。 これは uBOL がコンテンツブロッキングの際に CPU、メモリを消費しないことを意味します。uBOL のサービス ワーカーは ポップアップ パネルや設定ページでのみ必要とされます。
+
+uBOL はインストール時に広範な「データの読み取りと変更」の権限を要求しません。したがって uBlock Origin やその他の、インストール時に広範な「データの読み取りと変更」の権限を要求するコンテンツ ブロッカーに比べて、行えることが制限されています。
+
+しかし、ユーザーの選んだ特定のサイトに対する拡張権限を「明示的に」付与すれば、そのサイト上で整形フィルターやスクリプトレットの挿入を用いた優れたフィルタリングを行うことができます。
+
+特定のサイトで拡張された権限を付与するには、ポップアップ パネルを開いて、「最適」や「完全」のようなより高いフィルタリングモードを選択します。
+
+ブラウザは、現在のサイトで拡張機能によってリクエストされた追加の権限を付与することによってもたらされる影響について警告します。承認または拒否することができます。
+
+閲覧中のサイトに対するuBOLの追加的な権限要求リクエストを承認すると、そのサイトへのコンテンツフィルタリングの品質をあげることができます。
+
+uBOL の設定ページで既定のフィルタリングモードを設定できます。 「最適」または「完全」を規定のフィルタリング モードに設定した場合、すべてのWebサイトで「データの読み取りと変更」権限を付与する必要があります。
+
+注意として、uBOL はまだ開発途中で、これらの開発目標があります。
+
+- インストール時に広範なホスト権限は不要 -- 拡張された権限はサイトごとにユーザーによって明示的に付与されます。
+
+- 完全に宣言的で、CPU、メモリの効率性が良い
diff --git a/platform/mv3/description/webstore.ka.txt b/platform/mv3/description/webstore.ka.txt
new file mode 100644
index 0000000..ffcc985
--- /dev/null
+++ b/platform/mv3/description/webstore.ka.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) *ნებართვებისგან თავისუფალი* MV3-ზე დაფუძნებული შიგთავსის შემზღუდავია.
+
+წესების ნაგულისხმევი კრებული იგივეა, რასაც uBlock Origin იყენებს:
+
+- uBlock Origin – ფილტრების ჩაშენებული სიები
+- EasyList
+- EasyPrivacy
+- Peter Lowe – სარეკლამო სერვერების სია
+
+შეგიძლიათ სხვა კრებულებიც დაამატეთ პარამეტრების გვერდიდან -- დაწკაპეთ _Cogs_ ხატულაზე ამომხტომ არეში.
+
+uBOL სრულად დეკლარაციულია, ანუ არაა საჭირო მუდმივად იყოს გაშვებული uBOL-პროცესი გასაფილტრად, CSS/JS ჩანაცვლებით შიგთავსის გაფილტვრას თავად ბრაუზერი უზრუნველყოფს ნაცვლად გაფართოებისა, რაც მეტად საიმედოა. შესაბამისად, uBOL თავად არ დატვირთავს პროცესორს/ოპერატიულს შიგთავსის შეზღუდვის დროს -- uBOL-ის შუამავალი მომსახურე პროცესი საჭიროა _მხოლოდ_ მაშინ, როცა ამომხტომ არესთან ურთიერთქმედებთ ან ცვლით პარამეტრებს.
+
+uBOL არ საჭიროებს „მონაცემთა წაკითხვისა და შეცვლის“ სრულ ნებართვას დაყენებისას, ვინაიდან მოკვეცილი შესაძლებლობებითაა წარმოდგენილი uBlock Origin-თან ან რეკლამის სხვა შემზღუდავებთან შედარებით, რომლებიც ერთბაშად ითხოვს „მონაცემთა წაკითხვისა და შეცვლის“ უფლებას დაყენებისთანავე.
+
+ამასთანავე, uBOL საშუალებას გაძლევთ *მკაფიო* თანხმობით მიუთითოთ გაფართოებული ნებართვები ცალკეულ საიტებზე სურვილისამებრ, რომ უკეთ შეიზღუდოს შიგთავსი ხილული ელემენტების გაფილტვრითა და პროგრამული ჩამატებებით.
+
+გაფართოებული ნებართვების მისაცემად მითითებულ საიტზე, გახსენით ამომხტომი არე და აირჩიეთ ფილტრაციის უფრო მაღალი დონე, როგორიცაა „წონასწორული“ ან „სრული“.
+
+შემდეგ ბრაუზერი გაგაფრთხილებთ გაფართოების მიერ დამატებითი ნებართვების მოთხოვნის შესახებ მოცემულ საიტზე და აირჩევთ, დათანხმდებით თუ უარყოფთ მოთხოვნას.
+
+თუ დათანხმდებით uBOL-ს მოთხოვნას დამატებითი ნებართვებისთვის მიმდინარე საიტზე, უკეთ შეძლებს შიგთავსის შეზღუდვას აღნიშნულ საიტზე.
+
+შეგიძლიათ მიუთითოთ გაფილტვრის ნაგულისხმევი რეჟიმი uBOL-ის პარამეტრების გვერდიდან. თუ აირჩევთ „წონასწორულ“ ან „სრულ“ რეჟიმს ნაგულისხმევად, uBOL-ს უნდა დართოთ ყველა საიტზე მონაცემთა წაკითხვისა და შეცვლის ნება.
+
+დაიხსომეთ, რომ ჯერ კიდევ მუშავდება შემდეგი მიზნებისთვის:
+
+- არანაირი სრული ნებართვების ერთბაშად მოთხოვნა დაყენებისას -- დამატებითი უფლებები შეიძლება მიეცეს მომხმარებლის მკაფიო თანხმობით თითოეულ საიტზე ცალ-ცალკე.
+
+- სრულად დეკლარაციულია პროცესორის/მეხსიერების დასაზოგად.
diff --git a/platform/mv3/description/webstore.kk.txt b/platform/mv3/description/webstore.kk.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.kk.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.kn.txt b/platform/mv3/description/webstore.kn.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.kn.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.ko.txt b/platform/mv3/description/webstore.ko.txt
new file mode 100644
index 0000000..c23291c
--- /dev/null
+++ b/platform/mv3/description/webstore.ko.txt
@@ -0,0 +1,30 @@
+uBO Lite(uBOL)는 *적은 권한을 요구하는* MV3 기반 콘텐츠 차단기입니다.
+
+기본 규칙 목록은 uBlock Origin의 기본 필터 목록과 대응됩니다.
+
+- uBlock Origin 내장 필터 목록
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+설정 페이지에서 규칙 목록을 더욱 추가할 수 있습니다. 팝업 창의 _Cogs_ 아이콘을 누르세요.
+
+uBOL은 완전히 선언적이라 필터링 중 영구적으로 실행되는 uBOL 프로세스를 필요로 하지 않으며, CSS/JS 주입 기반 콘텐츠 필터링이 확장 프로그램이 아닌 브라우저 자체에서 더욱 안정적으로 동작합니다. 즉 uBOL 자체는 콘텐츠 차단을 하는 동안 CPU/메모리 리소스를 소비하지 않습니다. uBOL 서비스워커 프로세스는 사용자가 팝업 창이나 설정을 열었을 _때에만_ 동작합니다.
+
+uBOL은 설치 시 광범위한 "읽기 및 수정" 권한을 요구하지 않으므로, 설치 후 즉시 쓸 수 있는 기능들은 uBlock Origin이나 설치 시 광범위한 "읽기 및 수정" 권한을 요구하는 다른 콘텐츠 차단기에 비해 제한됩니다.
+
+하지만 uBOL에서는 원하는 특정 사이트에 대해 확장된 권한을 부여하여, 해당 사이트를 표면 필터링 및 스크립트 주입을 바탕으로 더욱 잘 필터링할 수 있습니다.
+
+주어진 사이트에 확장된 권한을 부여하려면, 팝업 창을 열고 필터링 모드를 '최적'이나 '완전'과 같이 더 높은 수준으로 선택하세요.
+
+브라우저는 확장 프로그램에 현재 사이트에 대한 추가 권한을 부여했을 때 발생할 수 있는 일에 대해 경고할 것이며, 권한 요청을 수락할지 거부할지 선택해야 합니다.
+
+현재 사이트에 대해 uBOL에 추가 권한을 부여하면, 해당 사이트의 콘텐츠를 더욱 잘 필터링할 수 있습니다.
+
+uBOL 설정 페이지에서 기본 필터링 모드를 설정할 수 있습니다. 기본 모드를 '최적' 혹은 '완전'으로 설정하는 경우, uBOL에 모든 웹사이트에서 데이터를 읽고 수정할 수 있는 권한을 부여해야 합니다.
+
+본 확장 프로그램은 여전히 아래 목표를 달성하기 위해 개발 중인 단계입니다.
+
+- 설치 시 광범위한 호스트 권한을 요구하지 않고, 사용자가 사이트마다 명시적으로 확장된 권한을 부여할 수 있도록 합니다.
+
+- 완전히 선언적으로 구현하여 CPU/메모리 효율성과 신뢰성을 확보합니다.
diff --git a/platform/mv3/description/webstore.ku.txt b/platform/mv3/description/webstore.ku.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.ku.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.lt.txt b/platform/mv3/description/webstore.lt.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.lt.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.lv.txt b/platform/mv3/description/webstore.lv.txt
new file mode 100644
index 0000000..078cf7f
--- /dev/null
+++ b/platform/mv3/description/webstore.lv.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) ir *bezatļauju* uz MV3 balstīts satura aizturētājs.
+
+Noklusējuma nosacījumu kopa atbilst uBokc Origin noklusējuma aizturēšanas kopai:
+
+- uBlock Origin iebūvētie aizturēšanas saraksti
+- EasyList
+- EasyPrivacy
+- Pētera Lova (Peter Lowe) reklāmu un izsakošanas serveru saraksts
+
+Vairāk nosacījumu kopu var pievienot iestatījumu sadaļā -- jāklikšķina _Zobratu_ ikona uznirstošajā logā.
+
+uBOL ir pilnībā vispārīgs, kas nozīmē, ka nav nepieciešamības pēc pastāvīga uBOL procesa, lai notiktu aizturēšana, un uz CSS/JS ievietošanu balstīta satura aizturēšanu uzticami veic pārlūks, nevis paplašinājums. Tas nozīmē, ka uBOL pats par sevi neizmanto procesoru un atmiņu, kamēr satura aizturēšana ir notiekoša -- uBOL pakalpojuma strādņa process ir nepieciešams _tikai_ tad, kad notiek mijiedarbība ar uznirstošo logu vai iestatījumu sadaļām.
+
+uBOL nav nepieciešamas plašas "lasīt un pārveidot datus" atļaujas uzstādīšanas laikā, tāpēc tam ir ierobežotas spējas pēc noklusējuma, salīdzinājumā ar uBlock Origin vai citiem satura aizturētājiem, kas pieprasa plašas "lasīt un pārveidot datus" atļaujas uzstādīšanas laikā.
+
+Tomēr uBOL ļauj piešķirt paplašinātās atļaujas *tieši* noteiktās vietnēs pēc izvēles, lai tas varētu labāk veikt aizturēšanu tajās, izmantojot kosmētisku aizturēšanu un skriptu ievietošanu.
+
+Lai nodrošinātu paplašinātas piekļuves tiesības noteiktā vietnē, jāatver uznirstošais logs un jāizvēlas striktāks aizturēšanas veids, kā, piemēram, "Labākais" vai "Pilnīgais".
+
+Pārlūks tad brīdinās ietekmi, ko radīs paplašinājuma pieprasīto papildu atļauju nodrošināšana pašreizējā vietnē, un būs jānorāda, vai pieprasījums tiek apstiprināts vai noraidīts.
+
+Ja pašreizējā vietnē tiek apstiprināts uBOL papildu atļauju pieprasījums, paplašinājums varēs labāk veikt satura aizturēšanu.
+
+Noklusējuma aizturēšanas veids ir norādāms uBOL uzstādījumu lapā. Ja tiek izvēlēts "Labākais" vai "Pilnīgais" kā noklusējuma, tad būs nepieciešams nodrošināt uBOL tiesības rakstīt un pārveidot datus visās tīmekļa vietnēs.
+
+Jāpatur prātā, ka šī iespēja joprojām tiek izstrādāta ar šādiem mērķiem:
+
+- Nav plašu saimniekdatora atļauju uzstādīšanas laikā -- paplašinātas atļaujas nodrošina lietotājs atsevišķi katrai vietnei.
+
+- Pilnībā vispārīgs - uzticamībai un procesora/atmiņas lietderīgai izmantošanai.
diff --git a/platform/mv3/description/webstore.mk.txt b/platform/mv3/description/webstore.mk.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.mk.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.ml.txt b/platform/mv3/description/webstore.ml.txt
new file mode 100644
index 0000000..9237bc2
--- /dev/null
+++ b/platform/mv3/description/webstore.ml.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) ഒരു *അനുമതി-കുറവ്* MV3 അടിസ്ഥാനമാക്കിയുള്ള ഉള്ളടക്ക ബ്ലോക്കറാണ്.
+
+ഡിഫോൾട്ട് റൂൾസെറ്റ് uBlock Origin-ന്റെ ഡിഫോൾട്ട് ഫിൽട്ടർസെറ്റുമായി യോജിക്കുന്നു:
+
+- uBlock ഒറിജിനിന്റെ ബിൽറ്റ്-ഇൻ ഫിൽട്ടർ ലിസ്റ്റുകൾ
+- ഈസി ലിസ്റ്റ്
+- ഈസി സ്വകാര്യത
+- പീറ്റർ ലോവിന്റെ പരസ്യവും ട്രാക്കിംഗ് സെർവർ ലിസ്റ്റും
+
+ഓപ്ഷനുകൾ പേജ് സന്ദർശിച്ച് നിങ്ങൾക്ക് കൂടുതൽ നിയമങ്ങൾ ചേർക്കാൻ കഴിയും -- പോപ്പ്അപ്പ് പാനലിലെ _Cogs_ ഐക്കണിൽ ക്ലിക്കുചെയ്യുക.
+
+uBOL പൂർണ്ണമായും ഡിക്ലറേറ്റീവ് ആണ്, അതായത് ഫിൽട്ടറിംഗ് സംഭവിക്കുന്നതിന് ഒരു സ്ഥിരമായ uBOL പ്രക്രിയയുടെ ആവശ്യമില്ല, കൂടാതെ CSS/JS ഇഞ്ചക്ഷൻ അടിസ്ഥാനമാക്കിയുള്ള ഉള്ളടക്ക ഫിൽട്ടറിംഗ്, എക്സ്റ്റൻഷനേക്കാൾ വിശ്വസനീയമായി ബ്രൗസർ തന്നെ നിർവഹിക്കുന്നു. ഉള്ളടക്കം തടയൽ നടന്നുകൊണ്ടിരിക്കുമ്പോൾ uBOL തന്നെ CPU/മെമ്മറി ഉറവിടങ്ങൾ ഉപയോഗിക്കില്ല എന്നാണ് ഇതിനർത്ഥം -- നിങ്ങൾ പോപ്പ്അപ്പ് പാനലുമായോ ഓപ്‌ഷൻ പേജുകളുമായോ സംവദിക്കുമ്പോൾ _only_ uBOL-ന്റെ സേവന വർക്കർ പ്രോസസ്സ് ആവശ്യമാണ്.
+
+ഇൻസ്റ്റാളേഷൻ സമയത്ത് uBOL ന് വിശാലമായ "ഡാറ്റ വായിക്കാനും പരിഷ്‌ക്കരിക്കാനും" അനുമതി ആവശ്യമില്ല, അതിനാൽ uBlock ഒറിജിൻ അല്ലെങ്കിൽ മറ്റ് ഉള്ളടക്ക ബ്ലോക്കറുകൾ എന്നിവയുമായി താരതമ്യപ്പെടുത്തുമ്പോൾ അതിന്റെ പരിമിതമായ കഴിവുകൾ ഇൻസ്റ്റാളേഷൻ സമയത്ത് വിശാലമായ "ഡാറ്റ വായിക്കാനും പരിഷ്‌ക്കരിക്കാനും" അനുമതികൾ ആവശ്യമാണ്.
+
+എന്നിരുന്നാലും, നിങ്ങൾ തിരഞ്ഞെടുക്കുന്ന നിർദ്ദിഷ്ട സൈറ്റുകളിൽ വിപുലീകൃത അനുമതികൾ *വ്യക്തമായി* നൽകാൻ uBOL നിങ്ങളെ അനുവദിക്കുന്നു, അതുവഴി കോസ്മെറ്റിക് ഫിൽട്ടറിംഗും സ്ക്രിപ്റ്റ്ലെറ്റ് കുത്തിവയ്പ്പുകളും ഉപയോഗിച്ച് ആ സൈറ്റുകളിൽ മികച്ച രീതിയിൽ ഫിൽട്ടർ ചെയ്യാൻ കഴിയും.
+
+തന്നിരിക്കുന്ന സൈറ്റിൽ വിപുലമായ അനുമതികൾ നൽകുന്നതിന്, പോപ്പ്അപ്പ് പാനൽ തുറന്ന് ഒപ്റ്റിമൽ അല്ലെങ്കിൽ കംപ്ലീറ്റ് പോലുള്ള ഉയർന്ന ഫിൽട്ടറിംഗ് മോഡ് തിരഞ്ഞെടുക്കുക.
+
+നിലവിലെ സൈറ്റിൽ വിപുലീകരണം അഭ്യർത്ഥിച്ച അധിക അനുമതികൾ നൽകുന്നതിന്റെ ഫലങ്ങളെക്കുറിച്ച് ബ്രൗസർ നിങ്ങൾക്ക് മുന്നറിയിപ്പ് നൽകും, നിങ്ങൾ അഭ്യർത്ഥന സ്വീകരിക്കുകയോ നിരസിക്കുകയോ ചെയ്യണോ എന്ന് നിങ്ങൾ ബ്രൗസറിനോട് പറയേണ്ടിവരും.
+
+നിലവിലെ സൈറ്റിൽ കൂടുതൽ അനുമതികൾക്കായുള്ള uBOL-ന്റെ അഭ്യർത്ഥന നിങ്ങൾ അംഗീകരിക്കുകയാണെങ്കിൽ, നിലവിലെ സൈറ്റിനായി മികച്ച ഉള്ളടക്കം ഫിൽട്ടർ ചെയ്യാൻ അതിന് കഴിയും.
+
+uBOL-ന്റെ ഓപ്‌ഷൻ പേജിൽ നിന്ന് നിങ്ങൾക്ക് ഡിഫോൾട്ട് ഫിൽട്ടറിംഗ് മോഡ് സജ്ജമാക്കാൻ കഴിയും. ഒപ്റ്റിമൽ അല്ലെങ്കിൽ കംപ്ലീറ്റ് മോഡ് ഡിഫോൾട്ടായി നിങ്ങൾ തിരഞ്ഞെടുക്കുകയാണെങ്കിൽ, എല്ലാ വെബ്‌സൈറ്റുകളിലെയും ഡാറ്റ വായിക്കാനും പരിഷ്‌ക്കരിക്കാനും നിങ്ങൾ uBOL-ന് അനുമതി നൽകേണ്ടതുണ്ട്.
+
+ഈ അന്തിമ ലക്ഷ്യങ്ങളോടെ ഇത് ഇപ്പോഴും പുരോഗമിക്കുന്ന ഒരു ജോലിയാണെന്ന് ഓർമ്മിക്കുക:
+
+- ഇൻസ്റ്റാളേഷൻ സമയത്ത് ബ്രോഡ് ഹോസ്റ്റ് അനുമതികളൊന്നുമില്ല -- ഓരോ സൈറ്റിന്റെ അടിസ്ഥാനത്തിൽ വിപുലീകൃത അനുമതികൾ ഉപയോക്താവ് വ്യക്തമായി നൽകുന്നു.
+
+- വിശ്വാസ്യതയ്ക്കും സിപിയു/മെമ്മറി കാര്യക്ഷമതയ്ക്കും പൂർണ്ണമായും പ്രഖ്യാപനം.
diff --git a/platform/mv3/description/webstore.mr.txt b/platform/mv3/description/webstore.mr.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.mr.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.ms.txt b/platform/mv3/description/webstore.ms.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.ms.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.nb.txt b/platform/mv3/description/webstore.nb.txt
new file mode 100644
index 0000000..c45f9a5
--- /dev/null
+++ b/platform/mv3/description/webstore.nb.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) er en *tillatelses-begrenset* MV3-basert innholdsblokkerer.
+
+Standardregelsettet tilsvarer standardfiltersettet til uBlock Origin:
+
+- uBlock Origin's innebygde filterlister
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+Du kan legge til flere regelsett ved å gå til innstillingssiden -- klikk _Tannhjul_-ikonet i oppsprettspanelet.
+
+uBOL er fullstendig deklarativ, noe som betyr at det ikke er behov for en permanent uBOL-prosess for at filtreringen skal skje, og CSS/JS-injeksjonsbasert innholdsfiltrering utføres pålitelig av nettleseren selv i stedet for av utvidelsen. Dette betyr at uBOL selv ikke bruker CPU/minneressurser mens innholdsblokkering pågår -- uBOL's service worker-prosess kreves _bare_ når du samhandler med oppsprettspanelet eller innstillingssidene.
+
+uBOL krever ikke bred "lese og endre data"-tillatelse under installasjonen, derav begrensede muligheter i utgangspunktet sammenlignet med uBlock Origin eller andre innholdsblokkerere som krever bred "lese og endre data"-tillatelse under installasjonen.
+
+Men, uBOL lar deg *uttrykkelig* gi utvidede tillatelser på bestemte nettsteder du velger, slik at uBOL bedre kan filtrere på disse nettstedene ved bruk av kosmetisk filtrering og skriptlet-injeksjoner.
+
+For å gi utvidede tillatelser på et gitt nettsted, åpne oppsprettspanelet og velg en høyere filtreringsmodus som Optimal eller Fullstendig.
+
+Nettleseren vil da advare deg om effektene av å gi de ekstra tillatelsene som utvidelsen ber om på det gjeldende nettstedet, og du må fortelle nettleseren om du godtar eller avslår forespørselen.
+
+Hvis du godtar forespørselen fra uBOL om ekstra tillatelser på det gjeldende nettstedet, vil uBOL være i stand til å filtrere innhold bedre for det gjeldende nettstedet.
+
+Du kan angi standard filtreringsmodus fra innstillingssiden i uBOL. Hvis du velger Optimal eller Fullstendig modus som standard, må du gi uBOL tillatelsen til å lese og endre data på alle nettsteder.
+
+Husk at dette fortsatt er et arbeid som pågår, med disse sluttmålene:
+
+- Ingen brede vertstillatelser under installasjonen -- utvidede tillatelser gis uttrykkelig av brukeren på per-side-basis.
+
+- Helt deklarativt for pålitelighet og CPU/minneeffektivitet.
diff --git a/platform/mv3/description/webstore.nl.txt b/platform/mv3/description/webstore.nl.txt
new file mode 100644
index 0000000..ecdf0c3
--- /dev/null
+++ b/platform/mv3/description/webstore.nl.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is een *toestemmingsloze* MV3-gebaseerde inhoudsblokkeerder.
+
+De standaard regelset komt overeen met de standaard filterset van uBlock Origin:
+
+- Ingebouwde filterlijsten van uBlock Origin
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+U kunt meer regelsets toevoegen door de optiespagina te bezoeken -- klik op het _tandwielpictogram_ in het pop-uppaneel.
+
+uBOL is volledig declaratief, wat betekent dat er geen permanent uBOL-proces voor de filtering nodig is, en inhoudsfiltering op basis van CSS/JS-injectie op een betrouwbare manier door de browser zelf wordt uitgevoerd in plaats van door de extensie. Dit betekent dat uBOL zelf geen CPU-/geheugenbronnen gebruikt terwijl inhoudsblokkering actief is -- het serviceworker-proces van uBOL is _alleen_ vereist als u met het pop-uppaneel of de optiespagina’s werkt.
+
+uBOL vereist geen brede toestemming voor het ‘lezen en aanpassen van gegevens’ tijdens installatie, vandaar de beperkte ingebouwde mogelijkheden ervan in vergelijking met uBlock Origin of andere inhoudsblokkeerders die brede toestemmingen voor het ‘lezen en aanpassen van gegevens’ vereisen tijdens installatie.
+
+U kunt in uBOL echter *expliciet* uitgebreide toestemmingen verlenen op bepaalde websites van uw keuze, zodat het op die websites beter kan filteren via cosmetische filtering en scriptlet-injecties.
+
+Om uitgebreide toestemmingen op een bepaalde website te verlenen, opent u het pop-uppaneel en kiest u een hogere filtermodus, zoals Optimaal of Volledig.
+
+De browser waarschuwt u dan over de gevolgen van het verlenen van de door de extensie aangevraagde aanvullende toestemmingen op de huidige website, en u dient de browser te laten weten of u de aanvraag accepteert of weigert.
+
+Als u de aanvraag van uBOL voor aanvullende toestemmingen op de huidige website accepteert, zal het inhoud voor de huidige website beter kunnen filteren.
+
+U kunt de standaard filtermodus instellen vanaf de optiespagina van uBOL. Als u de modus Optimaal of Volledig als de standaardmodus kiest, dient u uBOL de toestemming voor het lezen en aanpassen van gegevens op alle websites te verlenen.
+
+Onthoud dat dit nog werk in uitvoering is, met deze einddoelen:
+
+- Geen brede host-toestemmingen tijdens installatie -- uitgebreide toestemmingen worden expliciet en per website verleend door de gebruiker.
+
+- Volledig declaratief omwille van betrouwbaarheid en CPU-/geheugenefficiëntie.
diff --git a/platform/mv3/description/webstore.oc.txt b/platform/mv3/description/webstore.oc.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.oc.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.pa.txt b/platform/mv3/description/webstore.pa.txt
new file mode 100644
index 0000000..b413fe3
--- /dev/null
+++ b/platform/mv3/description/webstore.pa.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) ਇੱਕ *ਬਿਨਾਂ ਇਜਾਜ਼ਤਾਂ* ਵਾਲਾ MV3-ਅਧਾਰਿਤ ਸਮੱਗਰੀ ਬਲਾਕਰ ਹੈ।
+
+ਮੂਲ ਨਿਯਮ-ਸਮੂਹ uBlock Origin ਦੇ ਮੂਲ ਫਿਲਟਰ-ਸਮੂਹ ਨਾਲ ਸੰਬੰਧਿਤ ਹੈ:
+
+- uBlock Origin ਦੀਆਂ ਬਿਲਟ-ਇਨ ਫਿਲਟਰ ਸੂਚੀਆਂ
+-ਸੌਖੀ-ਸੂਚੀ
+-ਸੌਖੀ ਪਰਦੇਦਾਰੀ
+- Peter Lowe ਦੀ ਇਸ਼ਤਿਹਾਰ ਅਤੇ ਟਰੈਕਿੰਗ ਸਰਵਰ ਸੂਚੀ
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+ਯਾਦ ਰੱਖੋ ਕਿ ਇਹ ਕੰਮ ਹਾਲੇ ਵੀ ਜਾਰੀ ਹੈ, ਜਿਸ ਦੇ ਟੀਚੇ ਇਹ ਹਨ:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.pl.txt b/platform/mv3/description/webstore.pl.txt
new file mode 100644
index 0000000..a65d0db
--- /dev/null
+++ b/platform/mv3/description/webstore.pl.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) to *niewymagający uprawnień* bloker treści bazujący na MV3.
+
+Domyślny zestaw reguł odpowiada domyślnemu zestawowi filtrów uBlock Origin:
+
+– wbudowane listy filtrów uBlock Origin
+– EasyList
+– EasyPrivacy
+– lista serwerów śledzących i reklam Petera Lowe'a
+
+Możesz dodać więcej zestawów reguł, odwiedzając stronę opcji – kliknij ikonę _Trybika_ w wyskakującym panelu.
+
+uBOL jest całkowicie deklaratywny, co oznacza, że nie jest potrzebny stały proces uBOL w celu filtrowania, a filtrowanie treści oparte na wstrzykiwaniu CSS/JS jest wykonywane niezawodnie przez samą przeglądarkę, a nie przez rozszerzenie. Oznacza to, że sam uBOL nie zużywa zasobów procesora/pamięci, gdy trwa blokowanie treści – proces Service Worker uBOL jest wymagany _tylko_ podczas interakcji z panelem wyskakującym lub stronami opcji.
+
+uBOL w trakcie instalacji nie wymaga szerokich uprawnień do „odczytu i modyfikacji danych”, stąd jego ograniczone możliwości w porównaniu z uBlock Origin lub innymi blokerami treści, które w czasie instalacji wymagają szerokich uprawnień do „odczytu i modyfikacji danych”.
+
+Jednakże uBOL umożliwia *jawnie* udzielanie rozszerzonych uprawnień na określonych wybranych witrynach, dzięki czemu może lepiej filtrować te witryny za pomocą filtrowania kosmetycznego i wstrzykiwania skryptletów.
+
+Aby przyznać rozszerzone uprawnienia na danej witrynie, otwórz panel wyskakujący i wybierz wyższy tryb filtrowania, taki jak Optymalny lub Kompletny.
+
+Przeglądarka wyświetli ostrzeżenie o skutkach przyznania dodatkowych uprawnień wymaganych przez rozszerzenie na bieżącej witrynie i będziesz musiał poinformować przeglądarkę, czy akceptujesz, czy odrzucasz żądanie.
+
+Jeśli zaakceptujesz żądanie uBOL o dodatkowe uprawnienia na bieżącej witrynie, będzie on w stanie lepiej filtrować zawartość bieżącej witryny.
+
+Możesz ustawić domyślny tryb filtrowania na stronie opcji uBOL. Jeśli tryb Optymalny lub Pełny zostanie wybrany jako domyślny, trzeba będzie przyznać uBOL uprawnienia do odczytu i modyfikacji danych na wszystkich stronach internetowych.
+
+Należy pamiętać, że nadal trwają prace z następującymi celami końcowymi:
+
+– Brak szerokich uprawnień hosta w czasie instalacji – rozszerzone uprawnienia są przyznawane jawnie przez użytkownika na podstawie poszczególnych witryn.
+
+– Całkowicie deklaratywna niezawodność i wydajność procesora/pamięci.
diff --git a/platform/mv3/description/webstore.pt_BR.txt b/platform/mv3/description/webstore.pt_BR.txt
new file mode 100644
index 0000000..884ec64
--- /dev/null
+++ b/platform/mv3/description/webstore.pt_BR.txt
@@ -0,0 +1,30 @@
+O uBO Lite (uBOL) é um bloqueador de conteúdo baseado no MV3 com menor permissão.
+
+O conjunto de regras padrão corresponde ao conjunto de filtros padrão do uBlock Origin:
+
+- Listas embutidas dos filtros do uBlock Origin
+- EasyList
+- EasyPrivacy
+- Lista de servidores de anúncios e rastreadores do Peter Lowe
+
+Você pode adicionar mais conjuntos de regras visitando a página das opções -- clique no ícone _Engrenagens_ no painel do pop-up.
+
+O uBOL é totalmente declarativo, significando que não há necessidade de um processo permanente do uBOL para a filtragem ocorrer e a filtragem de conteúdo baseada em injeção do CSS/JS é realizada confiavelmente pelo próprio navegador ao invés da extensão. Isto significa que o próprio uBOL não consome recursos de CPU/memória enquanto o bloqueio de conteúdo está em andamento -- o processo do service worker do uBOL _só_ é necessário quando você interage com o painel do pop-up ou as páginas das opções.
+
+O uBOL não requer permissão ampla pra "ler e modificar dados" na hora da instalação, logo suas capacidades limitadas fora da caixa comparadas com o uBlock Origin ou outros bloqueadores de conteúdo requerem permissões amplas pra "ler e modificar dados" na hora da instalação.
+
+Contudo, o uBOL permite a você garantir *explicitamente* permissões estendidas em sites específicos de sua escolha pra que possa filtrar melhor esses sites usando filtragem cosmética e injeções de scriptlet.
+
+Pra conceder permissões estendidas num site dado, abra o painel do pop-up e escolha um modo de filtragem superior tal como Otimizado ou Completo.
+
+O navegador então avisará você sobre os efeitos de garantir as permissões adicionais requisitadas pela extensão no site atual e você terá que dizer ao navegador se você aceita ou recusa a requisição.
+
+Se você aceitar a requisição do uBOL por permissões adicionais no site atual ele será capaz de filtrar melhor o conteúdo do site atual.
+
+Você pode definir o modo de filtragem padrão na página de opções do uBOL. Se você escolher o Modo Otimizado ou Completo como o modo padrão você precisará garantir ao uBOL a permissão de ler e modificar os dados em todos os sites.
+
+Mantenha em mente que este ainda é um trabalho em progresso com estes objetivos finais:
+
+- Sem permissões amplas do hospedeiro na hora da instalação -- as permissões estendidas são garantidas explicitamente pelo usuário numa base por site.
+
+- Totalmente declarativo para confiabilidade e eficiência de CPU/memória.
diff --git a/platform/mv3/description/webstore.pt_PT.txt b/platform/mv3/description/webstore.pt_PT.txt
new file mode 100644
index 0000000..f390d5d
--- /dev/null
+++ b/platform/mv3/description/webstore.pt_PT.txt
@@ -0,0 +1,30 @@
+O uBO Lite (uBOL) é um bloqueador de conteúdo baseado no MV3 *sem permissões*.
+
+O conjunto de regras padrão corresponde ao conjunto de filtros padrão do uBlock Origin:
+
+- Listas de filtros integrados do uBlock Origin
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+Pode adicionar mais conjuntos de regras visitando a página de opções -- clique no ícone _Cogs_ no painel pop-up.
+
+O uBOL é totalmente declarativo, o que elimina a necessidade de um processo ativo constante para a filtragem ocorrer. A injeção de CSS e JS para filtragem de conteúdo é efetuada de maneira confiável pelo navegador, não dependendo da extensão. Isso significa que o uBOL por si só não gasta recursos de CPU/memória enquanto o bloqueio de conteúdo está a acontecer -- o processo do trabalhador de serviço do uBOL é necessário apenas quando se interage com a janela flutuante ou as páginas de opções.
+
+uBOL não requer ampla permissão de "ler e modificar dados" no momento da instalação, daí as suas capacidades limitadas de pronto a usar em comparação com uBlock Origin ou outros bloqueadores de conteúdo que requerem amplas permissões de "ler e modificar dados" no momento da instalação.
+
+No entanto, o uBOL permite-lhe que *explicitamente* conceda permissões alargadas em websites específicos de sua escolha, para que possa filtrar melhor esses websites usando filtragem cosmética e injeções de scriptlet.
+
+Para conceder permissões alargadas num determinado sítio web, abra a janela flutuante e escolha um modo de filtragem superior, como 'Ideal' ou 'Completo'.
+
+O navegador irá avisá-lo sobre os efeitos da concessão das permissões adicionais solicitadas pela extensão no site atual, e terá que informar ao navegador se aceita ou recusa o pedido.
+
+Se aceitar o pedido do uBOL para permissões adicionais no site atual, o mesmo poderá filtrar melhor o conteúdo do site atual.
+
+Pode definir o modo de filtragem padrão na página de opções do uBOL. Se escolher o modo Ideal ou Completo como o modo predefinido, terá de conceder ao uBOL a permissão para ler e modificar dados em todos os sítios web.
+
+Tenha em mente que este ainda é um trabalho em curso, com estes objetivos finais:
+
+Sem permissões amplas de anfitrião no momento da instalação -- permissões estendidas são concedidas explicitamente pelo utilizador numa base por site.
+
+- Totalmente declarativo para fiabilidade e eficiência de CPU/memória
diff --git a/platform/mv3/description/webstore.ro.txt b/platform/mv3/description/webstore.ro.txt
new file mode 100644
index 0000000..6e1f05d
--- /dev/null
+++ b/platform/mv3/description/webstore.ro.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) este blocant de conținut experimental *fără permisiuni* bazat pe MV3.
+
+Setul de reguli implicit corespunde setului de filtre implicit al uBlock Origin:
+
+Listele de filtre încorporate de uBlock Origin
+- EasyList
+- EasyPrivacy
+- Oglas Peter Lowe i lista servera za praćenje
+
+Puteți adăuga mai multe seturi de reguli vizitând pagina de opțiuni -- făcând clic pe pictograma _Cogs_ din panoul pop-up
+
+uBOL este în întregime declarativ, ceea ce înseamnă că nu este nevoie de un proces uBOL permanent pentru ca filtrarea să aibă loc, iar filtrarea conținutului pe bază de injecție CSS/JS este realizată în mod sigur de browser în sine, mai degrabă decât de extensie. Aceasta înseamnă că uBOL în sine nu consumă resurse CPU/memorie în timp ce blocarea conținutului este în desfășurare -- procesul de lucru al serviciului uBOL este necesar _doar_ atunci când interacționați cu panoul pop-up sau cu paginile de opțiuni.
+
+uBOL nu necesită permisiunii extinse de „citire și modificare a datelor” în momentul instalării, astfel capacitățile sale limitate din momentul instalării în comparație cu uBlock Origin sau alte blocare de conținut necesită permisiuni largi de „citire și modificare a datelor” în momentul instalării.
+
+Cu toate acestea, uBOL vă permite să acordați *explicit* permisiuni extinse pe anumite site-uri alese de dvs., astfel încât să poată filtra mai bine pe acele site-uri folosind filtrarea cosmetică și injecțiile de scriptlet.
+
+Pentru a acorda permisiuni extinse pe un anumit site, deschideți panoul pop-up și alegeți un mod de filtrare mai ridicat, cum ar fi Optimal sau Complet.
+
+Browser-ul vă va avertiza apoi cu privire la efectele acordării permisiunilor suplimentare solicitate de extensie pe site-ul curent și va trebui să-i precizați browserului dacă acceptați sau refuzați cererea.
+
+Dacă acceptați solicitarea uBOL pentru permisiuni suplimentare pe site-ul curent, acesta va putea filtra mai bine conținutul pentru site-ul curent.
+
+Puteți seta modul implicit de filtrare din pagina de opțiuni a uBOL. Dacă alegeți modul optim sau complet ca implicit, va trebui să acordați lui uBOL permisiunea de a citi și modifica datele de pe toate site-urile web.
+
+Rețineți că aceasta este în curs de desfășurare, cu aceste obiective finale:
+
+- Fără permisiuni de gazdă largi în momentul instalării - permisiunile extinse sunt acordate în mod explicit de către utilizator pe bază de site.
+
+- Complet declarativ pentru fiabilitate și eficiență CPU/memorie.
diff --git a/platform/mv3/description/webstore.ru.txt b/platform/mv3/description/webstore.ru.txt
new file mode 100644
index 0000000..637d7cb
--- /dev/null
+++ b/platform/mv3/description/webstore.ru.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) — это блокировщик содержимого, *не требующий разрешений*, и основанный на MV3.
+
+Стандартный набор правил соответствует типовому набору фильтров uBlock Origin:
+
+- Встроенные списки фильтров uBlock Origin
+- EasyList
+- EasyPrivacy
+- Список рекламных и отслеживающих серверов от Peter Lowe
+
+Вы можете добавить больше правил, посетив страницу настроек -- нажмите на значок_Шестеренок на всплывающей панели.
+
+uBOL - полностью декларативный, т.е. для фильтрации не нужен постоянно выполняющийся uBOL процесс, а фильтрация контента, основанная на внедрении CSS/JS, производится непосредственно браузером. Это значит, что дополнение uBOL не расходует ресурсы ЦПУ/памяти, когда происходит блокировка рекламы -- служебный процесс uBOL запускается, _только_ когда вы вносите изменения на всплывающей панели или странице настроек.
+
+uBOL не требует разрешение на полное "чтение и изменение данных" в момент установки, поэтому имеет ограниченные возможности по сравнению с uBlock Origin, и другими блокировщиками контента, которые требуют полное разрешение на "чтение и изменение данных" в момент установки.
+
+Однако uBOL позволяет *намеренно* давать расширенные разрешения для определенных сайтов - по вашему усмотрению, чтобы эффективнее работать, используя косметическую фильтрацию и scriptlet-внедрения.
+
+Для предоставления расширенных разрешений на текущем сайте - откройте всплывающую панель и выберите повышенный режим фильтрации: Оптимальный или Полный.
+
+Далее браузер выдаст предупреждение об эффектах предоставления расширенных разрешений, запрошенных дополнением на текущем сайте, и надо будет выбрать: принять или отклонить этот запрос.
+
+Если вы принимаете запрос uBOL на предоставление дополнительных разрешений, тогда дополнение сможет эффективнее фильтровать контент на текущем сайте.
+
+Вы можете установить стандартный режим фильтрации на странице настроек uBOL. Если вы выбираете Оптимальный или Полный режим - режимом по умолчанию, необходимо предоставить uBOL разрешение на чтение и изменение данных на всех веб-сайтах.
+
+Помните, что данный проект - в активной фазе разработки, преследующей следующие цели:
+
+- Работа с ограниченными разрешениями при установке -- расширенные разрешения пользователь выдает по своему усмотрению, каждому сайту отдельно.
+
+- Полностью декларативная работа - для надежности и эффективного использования ЦПУ/памяти.
diff --git a/platform/mv3/description/webstore.si.txt b/platform/mv3/description/webstore.si.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.si.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.sk.txt b/platform/mv3/description/webstore.sk.txt
new file mode 100644
index 0000000..686e41f
--- /dev/null
+++ b/platform/mv3/description/webstore.sk.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) je blokovač obsahu založený na MV3 *bez povolenia*.
+
+Predvolený súbor pravidiel zodpovedá predvolenému súboru filtrov uBlock Origin:
+
+- Vstavané zoznamy filtrov uBlock Origin
+- EasyList
+- EasyPrivacy
+- Zoznam reklamných a sledovacích serverov Petra Lowea
+
+Ďalšie súbory pravidiel môžete pridať na stránke s možnosťami – kliknite na ikonu _súkolesia_ vo vyskakovacom paneli.
+
+uBOL je úplne deklaratívny, čo znamená, že na filtrovanie nie je potrebný trvalý proces uBOL a filtrovanie obsahu založené na injektovaní CSS/JS spoľahlivo vykonáva samotný prehliadač, a nie rozšírenie. To znamená, že samotný uBOL nespotrebúva zdroje CPU/pamäte, kým prebieha blokovanie obsahu -- proces uBOL Service Worker je potrebný _len_ pri interakcii s vyskakovacím panelom alebo stránkami možností.
+
+uBOL pri inštalácii nevyžaduje všeobecné oprávnenie "čítať a upravovať údaje", preto má obmedzené možnosti v porovnaní s uBlock Origin alebo inými blokovačmi obsahu, ktoré pri inštalácii vyžadujú všeobecné oprávnenie "čítať a upravovať údaje".
+
+uBOL vám však umožňuje *výslovne* udeliť všebecné oprávnenia na konkrétne stránky podľa vášho výberu, aby mohol lepšie filtrovať na týchto stránkach pomocou kozmetického filtrovania a injektovaných skriptletov.
+
+Ak chcete udeliť všeobecné oprávnenia na danom webe, otvorte vyskakovací panel a vyberte vyšší režim filtrovania, napríklad Optimálny alebo Kompletný.
+
+Prehliadač vás potom upozorní na dôsledky udelenia dodatočných oprávnení požadovaných rozšírením na aktuálnej stránke a vy budete musieť prehliadaču oznámiť, či požiadavku prijímate alebo odmietate.
+
+Ak prijmete žiadosť uBOL o dodatočné povolenia na aktuálnom webe, bude môcť lepšie filtrovať obsah aktuálneho webu.
+
+Predvolený režim filtrovania môžete nastaviť na stránke možností uBOL. Ak ako predvolený režim vyberiete Optimálny alebo Kompletný režim, budete musieť uBOL-u udeliť oprávnenie na čítanie a úpravu údajov na všetkých webových stránkach.
+
+Majte na pamäti, že na tomto projekte sa stále pracuje, pričom jeho konečné ciele sú takéto:
+
+- Žiadne všeobecné oprávnenia hostiteľa v čase inštalácie -- rozšírené oprávnenia udeľuje používateľ explicitne pre jednotlivé stránky.
+
+- Úplne deklaratívne pre spoľahlivosť a efektivitu CPU/pamäte.
diff --git a/platform/mv3/description/webstore.sl.txt b/platform/mv3/description/webstore.sl.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.sl.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.so.txt b/platform/mv3/description/webstore.so.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.so.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.sq.txt b/platform/mv3/description/webstore.sq.txt
new file mode 100644
index 0000000..6b087c3
--- /dev/null
+++ b/platform/mv3/description/webstore.sq.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) është një bllokues *i pavarur* që funksionon sipas modelit MV3.
+
+Rregullat e tij janë të barasvlershme me filtrat standardë që përdor uBlock Origin:
+
+- Listat e filtrave të integruar në uBlock Origin
+- EasyList
+- EasyPrivacy
+- Lista e Peter Lowe për reklamat dhe gjurmuesit
+
+Në faqen e opsioneve mund të shtoni rregulla të tjera – klikoni ikonën e _ingranazhit_ në panelin modal.
+
+uBOL është tërësisht deklarativ, domethënë filtrimi ndodh pa qenë nevoja që procesi i uBOL të vijojë vazhdimisht në sfond, ndërsa injektimi i filtrave CSS/JS te materialet kryhet me saktësi nga vetë shfletuesi. Pra, uBOL i bllokon materialet pa konsumuar resurset e procesorit/memories – asetet e uBOL nevojiten _vetëm_ kur ndërveproni me panelin modal ose faqen e opsioneve të tij.
+
+uBO Lite nuk kërkon leje shtesë për "leximin dhe modifikimin e të dhënave" kur e instaloni, prandaj fillimisht ka aftësi më të kufizuara sesa uBlock Origin apo bllokuesit e tjerë që kërkojnë leje shtesë për "leximin dhe modifikimin e të dhënave" gjatë instalimit.
+
+Megjithatë ju mund t'i jepni uBOL leje të posaçme *eksplicite* për ato uebsajte që doni, në mënyrë që t'i filtrojë më mirë me filtra kozmetikë dhe skripte.
+
+Lejet e posaçme për uebsajtet jepen nëpërmjet panelit modal duke zgjedhur mënyrën e filtrimit Optimal ose Komplet.
+
+Më tej shfletuesi do ju informojë për efektet e dhënies së këtyre lejeve për uebsajtin në fjalë dhe ju duhet ta pranoni ose refuzoni kërkesën.
+
+Po ta pranoni dhënien e lejeve shtesë për uebsajtin në fjalë, uBOL do mundet ta filtrojë më mirë atë.
+
+Në faqen e opsioneve të uBOL mund të përcaktoni mënyrën standarde të filtrimit. Nëse vendosni si standard mënyrën Optimale ose Komplete, uBOL do ju marrë leje për leximin dhe modifikimin e të dhënave në të gjitha uebsajtet.
+
+Kini parasysh se ky projekt është në zhvillim e sipër sipas këtyre objektivave:
+
+- Instalohet pa leje shtesë – lejet e posaçme për çdo uebsajt jepen në mënyrë eksplicite nga përdoruesi.
+
+- Tërësisht deklarativ për të qenë më i qëndrueshëm dhe eficient me procesorin/memorien.
diff --git a/platform/mv3/description/webstore.sr.txt b/platform/mv3/description/webstore.sr.txt
new file mode 100644
index 0000000..065d6c0
--- /dev/null
+++ b/platform/mv3/description/webstore.sr.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) је блокатор садржаја *без дозвола*, заснован на MV3.
+
+Подразумевани скуп правила одговара подразумеваном скупу филтера uBlock Origin-а:
+
+- uBlock Origin листе уграђених филтера
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+Можете додати још скупова правила тако што ћете посетити страницу са опцијама - кликните на иконицу зупчаника у искачућем панелу.
+
+uBOL је потпуно декларативан, што значи да нема потребе за трајним uBOL процесом да би дошло до филтрирања, а филтрирање садржаја засновано на убацивању CSS/JS се обавља поуздано од стране самог прегледача, а не проширења. То значи да сам uBOL не троши CPU/меморијске ресурсе док је блокирање садржаја у току -- сервисни радни процес uBOL-а је потребан _само_ када ступите у интеракцију са искачућим панелом или страницама опција.
+
+uBOL не захтева широку дозволу за „читање и измену података” у тренутку инсталације, стога су његове ограничене могућности ван оквира у поређењу са uBlock Origin-ом или другим блокаторима садржаја који захтевају широке дозволе за „читање и измену података” у тренутку инсталације.
+
+Међутим, uBOL вам омогућује да *експлицитно* доделите проширене дозволе на одређеним сајтовима по вашем избору тако да може боље да филтрира те сајтове користећи козметичко филтрирање и ињекције скриптлета.
+
+Да бисте доделили проширене дозволе за дати сајт, отворите искачући панел и изаберите виши режим филтрирања, као што је Оптимално или Комплетно.
+
+Прегледач ће вас тада упозорити на ефекте давања додатних дозвола које захтева проширење на тренутном сајту, а ви ћете морати да кажете прегледачу да ли прихватате или одбијате захтев.
+
+Ако прихватите uBOL-ов захтев за додатне дозволе на тренутном сајту, он ће моћи боље да филтрира садржај за тренутни сајт.
+
+Можете подесити подразумевани режим филтрирања на uBOL-овој страници са опцијама. Ако изаберете режим Оптимално или Комплетно као подразумевани, мораћете да доделите uBOL-у дозволу да чита и мења податке на свим веб сајтовима.
+
+Имајте на уму да је ово још увек рад у току, са овим крајњим циљевима:
+
+– Нема широких дозвола за хост у тренутку инсталације – проширене дозволе се експлицитно додељују од стране корисника на основу сваког сајта.
+
+- Потпуно декларативан за поузданост и ефикасност CPU/меморије.
diff --git a/platform/mv3/description/webstore.sv.txt b/platform/mv3/description/webstore.sv.txt
new file mode 100644
index 0000000..0355bd0
--- /dev/null
+++ b/platform/mv3/description/webstore.sv.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) är en *behörighetslös* MV3-baserad innehållsblockerare.
+
+Standardregeluppsättningen motsvarar uBlock Origins standardfilteruppsättning:
+
+- uBlock Origins inbyggda filterlistor
+- EasyList
+- EasyPrivacy
+- Peter Lowes reklam- och spårningsserverlista
+
+Du kan lägga till fler regeluppsättningar genom att besöka alternativsidan -- klicka på ikonen _Kugghjulet_ i popup-panelen.
+
+uBOL är helt deklarativt, vilket innebär att det inte finns något behov av en permanent uBOL-process för att filtreringen ska ske och CSS/JS-injektionsbaserad innehållsfiltrering utförs på ett tillförlitligt sätt av webbläsaren själv snarare än av tillägget. Detta innebär att uBOL själv inte förbrukar CPU/minnesresurser medan innehållsblockering pågår -- uBOLs serviceworkerprocess krävs _endast_ när du interagerar med popup-panelen eller alternativsidorna.
+
+uBOL kräver inte högre behörighet för "läs och ändra data" vid installationen, därav dess begränsade möjligheter jämfört med uBlock Origin eller andra innehållsblockerare som kräver högre behörighet för "läs och ändra data" vid installationen.
+
+Däremot låter uBOL dig *uttryckligen* ge utökade behörigheter på specifika webbplatser du väljer så att den bättre kan filtrera på dessa webbplatser med hjälp av kosmetisk filtrering och scriptletinjektioner.
+
+För att ge utökade behörigheter på en viss webbplats, öppna popup-panelen och välj ett högre filtreringsläge som optimal eller fullständig.
+
+Webbläsaren kommer sedan att varna dig om effekterna av att bevilja de ytterligare behörigheter som tillägget begär på den aktuella webbplatsen och du måste tala om för webbläsaren om du accepterar eller avslår begäran.
+
+Om du accepterar uBOLs begäran om ytterligare behörigheter på den aktuella webbplatsen kommer den att kunna filtrera innehåll för den aktuella webbplatsen bättre.
+
+Du kan ställa in standardfiltreringsläget från uBOLs alternativsida. Om du väljer läge optimalt eller fullständigt som standard måste du ge uBOL behörighet att läsa och ändra data på alla webbplatser.
+
+Tänk på att detta fortfarande är ett pågående arbete med dessa slutmål:
+
+- Inga högre värdbehörigheter vid installationen - utökade behörigheter ges uttryckligen av användaren per webbplats.
+
+- Helt deklarativt för tillförlitlighet och CPU/minneseffektivitet.
diff --git a/platform/mv3/description/webstore.sw.txt b/platform/mv3/description/webstore.sw.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.sw.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.ta.txt b/platform/mv3/description/webstore.ta.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.ta.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.te.txt b/platform/mv3/description/webstore.te.txt
new file mode 100644
index 0000000..2f0c87d
--- /dev/null
+++ b/platform/mv3/description/webstore.te.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) అనేది *అనుమతి-తక్కువ* MV3-ఆధారిత కంటెంట్ బ్లాకర్.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOLకి ఇన్‌స్టాల్ సమయంలో విస్తృత "డేటాను చదవడం మరియు సవరించడం" అనుమతి అవసరం లేదు, అందువల్ల uBlock ఆరిజిన్ లేదా ఇన్‌స్టాల్ సమయంలో విస్తృతమైన "డేటాను చదవడం మరియు సవరించడం" అనుమతులు అవసరమయ్యే ఇతర కంటెంట్ బ్లాకర్‌లతో పోలిస్తే దాని పరిమిత సామర్థ్యాలు బాక్స్ వెలుపల ఉన్నాయి.
+
+అయితే, uBOL మీకు నచ్చిన నిర్దిష్ట సైట్‌లలో పొడిగించిన అనుమతులను *స్పష్టంగా* మంజూరు చేయడానికి మిమ్మల్ని అనుమతిస్తుంది, తద్వారా కాస్మెటిక్ ఫిల్టరింగ్ మరియు స్క్రిప్ట్‌లెట్ ఇంజెక్షన్‌లను ఉపయోగించి ఆ సైట్‌లలో మెరుగ్గా ఫిల్టర్ చేయవచ్చు.
+
+ఇచ్చిన సైట్‌లో పొడిగించిన అనుమతులను మంజూరు చేయడానికి, పాప్‌అప్ ప్యానెల్‌ను తెరిచి, ఆప్టిమల్ లేదా కంప్లీట్ వంటి అధిక ఫిల్టరింగ్ మోడ్‌ను ఎంచుకోండి.
+
+ప్రస్తుత సైట్‌లో పొడిగింపు ద్వారా అభ్యర్థించిన అదనపు అనుమతులను మంజూరు చేయడం వల్ల కలిగే ప్రభావాల గురించి బ్రౌజర్ మిమ్మల్ని హెచ్చరిస్తుంది మరియు మీరు అభ్యర్థనను అంగీకరించాలా లేదా తిరస్కరించాలా అని మీరు బ్రౌజర్‌కి తెలియజేయాలి.
+
+మీరు ప్రస్తుత సైట్‌లో అదనపు అనుమతుల కోసం uBOL అభ్యర్థనను అంగీకరిస్తే, అది ప్రస్తుత సైట్ కోసం కంటెంట్‌ను మెరుగ్గా ఫిల్టర్ చేయగలదు.
+
+మీరు uBOL ఎంపికల పేజీ నుండి డిఫాల్ట్ ఫిల్టరింగ్ మోడ్‌ను సెట్ చేయవచ్చు. మీరు డిఫాల్ట్‌గా ఆప్టిమల్ లేదా కంప్లీట్ మోడ్‌ని ఎంచుకుంటే, మీరు అన్ని వెబ్‌సైట్‌లలోని డేటాను చదవడానికి మరియు సవరించడానికి uBOLకి అనుమతిని మంజూరు చేయాలి.
+
+ఈ తుది లక్ష్యాలతో ఇది ఇంకా పురోగతిలో ఉందని గుర్తుంచుకోండి:
+
+- ఇన్‌స్టాల్ సమయంలో విస్తృత హోస్ట్ అనుమతులు లేవు -- పొడిగించిన అనుమతులు ప్రతి-సైట్ ప్రాతిపదికన వినియోగదారు ద్వారా స్పష్టంగా మంజూరు చేయబడతాయి.
+
+- విశ్వసనీయత మరియు CPU/మెమరీ సామర్థ్యం కోసం పూర్తిగా డిక్లరేటివ్.
diff --git a/platform/mv3/description/webstore.th.txt b/platform/mv3/description/webstore.th.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.th.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.tr.txt b/platform/mv3/description/webstore.tr.txt
new file mode 100644
index 0000000..125ca90
--- /dev/null
+++ b/platform/mv3/description/webstore.tr.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL), *izin gerektirmeyen* MV3 tabanlı bir içerik engelleyicidir.
+
+Varsayılan kural seti, uBlock Origin'in varsayılan filtre setine karşılık gelir:
+
+- uBlock Origin'in yerleşik filtre listeleri
+- EasyList
+- EasyPrivacy
+- Peter Lowe'un Reklam ve izleme sunucusu listesi
+
+Seçenekler sayfasını ziyaret ederek daha fazla kural seti ekleyebilirsiniz -- açılan paneldeki _Cogs_ simgesine tıklayın.
+
+uBOL tamamen bildirimseldir, yani filtrelemenin gerçekleşmesi için kalıcı bir uBOL işlemine gerek yoktur, içerik filtreleme eklenti yerine tarayıcının kendisi tarafından CSS/JS yerleştirerek gerçekleştirilir. Bu, içerik engelleme devam ederken uBOL'nin kendisinin CPU/bellek kaynaklarını tüketmediği anlamına gelir -- uBOL'un hizmet çalışanı işlemi, _only_ açılan panel veya seçenek sayfalarıyla etkileşim kurduğunuzda gereklidir.
+
+uBOL, kurulum sırasında geniş "veri okuma ve değiştirme" izni gerektirmez, bu nedenle, kurulum sırasında geniş "veri okuma ve değiştirme" izinleri gerektiren uBlock Origin veya diğer içerik engelleyicilere kıyasla, kutudan çıkar çıkmaz sınırlı yetenekleri vardır.
+
+Bununla birlikte, uBOL, kozmetik filtreleme ve komut dosyası enjeksiyonları kullanarak bu sitelerde daha iyi filtre uygulayabilmesi için, seçtiğiniz belirli sitelerde *açıkça* genişletilmiş izinler vermenize izin verir.
+
+Belirli bir sitede genişletilmiş izinler vermek için açılır paneli açın ve Optimal veya Complete gibi daha yüksek bir filtreleme modu seçin.
+
+Ardından tarayıcı, uzantı tarafından istenen ek izinlerin geçerli sitede verilmesinin etkileri konusunda sizi uyaracak ve tarayıcıya isteği kabul edip etmediğinizi söylemeniz gerekecektir.
+
+uBOL'un mevcut site için ek izin talebini kabul ederseniz, mevcut site için içeriği daha iyi filtreleyebilecektir.
+
+Varsayılan filtreleme modunu uBOL'un seçenekler sayfasından ayarlayabilirsiniz. Varsayılan mod olarak Optimal veya Complete modunu seçerseniz, uBOL'a tüm web sitelerindeki verileri okuma ve değiştirme izni vermeniz gerekecektir.
+
+Bunun, şu nihai hedeflerle hala devam eden bir çalışma olduğunu unutmayın:
+
+- Yükleme sırasında geniş ana bilgisayar izinleri yoktur -- genişletilmiş izinler, her site için kullanıcı tarafından açıkça verilir.
+
+- Güvenilirlik ve CPU/bellek verimliliği için tamamen bildirimsel.
diff --git a/platform/mv3/description/webstore.txt b/platform/mv3/description/webstore.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.uk.txt b/platform/mv3/description/webstore.uk.txt
new file mode 100644
index 0000000..799bdec
--- /dev/null
+++ b/platform/mv3/description/webstore.uk.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) - це блокувальник вмісту на основі MV3, що не потребує дозволу.
+
+Усталений набір правил відповідає типовому набору фільтрів uBlock Origin:
+
+- Вбудовані списки фільтрів uBlock Origin
+- EasyList
+- EasyPrivacy
+- Список серверів реклами та стеження від Peter Lowe
+
+Ви можете додати більше наборів правил, перейшовши на сторінку налаштувань — натисніть на піктограму _Шестерень_ на спливній панелі.
+
+uBOL повністю декларативний, тобто немає необхідності в постійному процесі uBOL для здійснення фільтрації, а фільтрація вмісту на основі CSS/JS-ін'єкцій надійно виконується самим браузером, а не розширенням. Це означає, що сам uBOL не споживає ресурси процесора/пам'яті під час блокування вмісту — службовий робочий процес uBOL потрібен _лише_ під час взаємодії зі спливною панеллю або сторінками опцій.
+
+uBOL не вимагає широкого дозволу на «читання та зміну даних» під час встановлення, отже, його можливості «з коробки» обмежені порівняно з uBlock Origin або іншими блокувальниками, які вимагають широкого дозволу на «читання/зміну даних» під час встановлення.
+
+Однак uBOL дозволяє вам *явно* надавати розширені дозволи на певних сайтах на ваш вибір, щоб він міг краще виконувати фільтрування на цих сайтах, використовуючи косметичну фільтрацію та вкладення скриптів.
+
+Щоб надати розширені дозволи на певному сайті, відкрийте спливну панель і виберіть вищий режим фільтрації, наприклад, «Оптимальний» або «Повний».
+
+Потім браузер попередить вас про наслідки надання додаткових дозволів, запитуваних розширенням, на поточному сайті, і ви повинні будете повідомити браузеру, чи приймаєте ви запит або відхиляєте його.
+
+Якщо ви приймете запит uBOL на додаткові дозволи на поточному сайті, він зможе краще фільтрувати вміст для цього сайту.
+
+Ви можете типовий налаштувати режим фільтрації на сторінці налаштувань uBOL. Якщо ви обираєте типовим режим Оптимальний або Повний, вам потрібно буде надати uBOL дозвіл на читання та зміну даних на всіх вебсайтах.
+
+Варто пам'ятати, що це досі незавершена робота з такими цілями:
+
+- Відсутність широких дозволів на хост під час встановлення — розширені дозволи надаються користувачем окремо для кожного сайту.
+
+- Повністю декларативна оцінка надійності та ефективності роботи процесора/пам'яті.
diff --git a/platform/mv3/description/webstore.ur.txt b/platform/mv3/description/webstore.ur.txt
new file mode 100644
index 0000000..e03fa80
--- /dev/null
+++ b/platform/mv3/description/webstore.ur.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) is a *permission-less* MV3-based content blocker.
+
+The default ruleset corresponds to uBlock Origin's default filterset:
+
+- uBlock Origin's built-in filter lists
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+You can add more rulesets by visiting the options page -- click the _Cogs_ icon in the popup panel.
+
+uBOL is entirely declarative, meaning there is no need for a permanent uBOL process for the filtering to occur, and CSS/JS injection-based content filtering is performed reliably by the browser itself rather than by the extension. This means that uBOL itself does not consume CPU/memory resources while content blocking is ongoing -- uBOL's service worker process is required _only_ when you interact with the popup panel or the option pages.
+
+uBOL does not require broad "read and modify data" permission at install time, hence its limited capabilities out of the box compared to uBlock Origin or other content blockers requiring broad "read and modify data" permissions at install time.
+
+However, uBOL allows you to *explicitly* grant extended permissions on specific sites of your choice so that it can better filter on those sites using cosmetic filtering and scriptlet injections.
+
+To grant extended permissions on a given site, open the popup panel and pick a higher filtering mode such as Optimal or Complete.
+
+The browser will then warn you about the effects of granting the additional permissions requested by the extension on the current site, and you will have to tell the browser whether you accept or decline the request.
+
+If you accept uBOL's request for additional permissions on the current site, it will be able to better filter content for the current site.
+
+You can set the default filtering mode from uBOL's options page. If you pick the Optimal or Complete mode as the default one, you will need to grant uBOL the permission to read and modify data on all websites.
+
+Keep in mind this is still a work in progress, with these end goals:
+
+- No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis.
+
+- Entirely declarative for reliability and CPU/memory efficiency.
diff --git a/platform/mv3/description/webstore.vi.txt b/platform/mv3/description/webstore.vi.txt
new file mode 100644
index 0000000..70d6d59
--- /dev/null
+++ b/platform/mv3/description/webstore.vi.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) là trình chặn nội dung dựa trên MV3 *không-cần-cấp-phép*.
+
+Bộ quy tắc mặc định tương tự bộ lọc của uBlock Origin:
+
+- Bộ lọc cài sẵn của uBlock Origin
+- EasyList
+- EasyPrivacy
+- Danh sách máy chủ chạy quảng cáo và trình theo dõi của Pete Lowe
+
+Bạn có thể tự thêm quy tắc mới ở trang cài đặt -- click vào icon _Bánh răng_ ở trong cửa sổ popup.
+
+uBOL mang tính khai báo hoàn toàn, vì vậy uBOL sẽ không cần phải liên tục chạy để chặn nội dung. Thay vào đó, chính trình duyệt sẽ thực hiện lọc nội dung bằng cách sử dụng công cụ chèn CSS/JS hiệu quả hơn có sẵn của nó. Điều này cũng đồng thời có nghĩa là uBOL sẽ không tiêu tốn tài nguyên CPU/bộ nhớ của bạn để chặn nội dung. uBOL sẽ chỉ chạy _khi và chỉ khi_ bạn đang xem cửa sổ popup của uBOL, hoặc bạn đang cấu hình uBOL ở trang cài đặt.
+
+uBOL không yêu cầu cấp quyền "đọc và sửa đổi dữ liệu" chung khi cài đặt, vì vậy nên ban đầu nó sẽ hơi yếu hơn uBlock Origin hoặc các trình chặn nội dung khác mà có yêu cầu quyền "đọc và sửa đổi dữ liệu" chung khi cài đặt.
+
+Tuy nhiên, uBOL lại cho phép bạn cấu hình *rất cụ thể* quyền ở trên bất kỳ trang nào tự chọn của bạn, để nó có thể lọc nội dụng trên các trang đấy tốt hơn bằng các kỹ thuật như lọc hiển thị (cosmetic filtering) hay chèn kịch bản con (scriptlet injections).
+
+Để cấp quyền cho uBOL chặn trang bất kỳ, hãy mở cửa sổ popup và chọn một chế độ chặn cao hơn như "Tối ưu" hoặc "Hoàn toàn".
+
+Trình duyệt của bạn sẽ hiện cảnh báo cho việc cấp quyền cho tiện ích trên trang web hiện tại, và bạn sẽ phải chọn đồng ý hoặc từ chối yêu cầu cấp quyền.
+
+Nếu bạn chọn đồng ý cấp quyền cho uBOL sửa trang web bất kỳ, uBOL sẽ có thể lọc nội dung tốt hơn cho web đấy.
+
+Bạn cũng có thể chọn chế độ chặn mặc định ở trang cài đặt của uBOL. Nếu bạn chọn chế độ Tối ưu hoặc Hoàn toàn làm mặc định, bạn sẽ cần phải cấp quyền "đọc và sửa đổi dữ liệu" trên tất cả các trang web.
+
+Lưu ý rằng sản phẩm này vẫn đang trong giai đoạn phát triển, và bản hoàn thiện sẽ có những tính năng sau:
+
+- Không yêu cầu bất kỳ quyền nào khi cài đặt - người dùng sẽ phải tự chủ động cấp bất kỳ quyền gì cho tiện ích, cụ thể từng trang web một.
+
+- Hoàn toàn mang tính khai báo, để có thế chạy nhẹ hơn và ổn định hơn.
diff --git a/platform/mv3/description/webstore.zh_CN.txt b/platform/mv3/description/webstore.zh_CN.txt
new file mode 100644
index 0000000..b307f53
--- /dev/null
+++ b/platform/mv3/description/webstore.zh_CN.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) 是一个基于最新浏览器扩展接口(Manifest Version 3)打造的“无需权限”的内容屏蔽工具。
+
+该扩展预设的规则列表对应 uBlock Origin 的预设过滤规则列表:
+
+- uBlock Origin 内置过滤规则列表
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+您可以通过设置页面添加更多过滤规则列表——点击弹出面板的“齿轮”图标。
+
+uBOL 的过滤规则是完全声明式的,并不需要固定保留一个 uBOL 扩展进程,基于 CSS/JS 注入的内容过滤更是交由浏览器进行调度,比起扩展本身更为可靠。 这也即是说当内容被过滤时 uBOL 自身并不占用额外 CPU 和内存资源,只有在您打开弹出面板或是设置页面时才会生成 uBOL 扩展进程。
+
+uBOL 在安装时并不需要宽泛的“读取或修改网页数据”的权限,因此它的开箱即用功能相对于 uBlock Origin 以及其他安装时要求该权限的屏蔽工具显得较为有限。
+
+不过,uBOL 可在您“明确”授予额外扩屏权限后,对您指定的网站采用基于 CSS/JS 注入的声明式规则加强内容过滤。
+
+若要在特定网站授予扩展权限,请开启弹出面板并选择更高级的过滤模式,例如“优化”或“完全”。
+
+接着浏览器会就在当前网站授予扩展额外权限有何影响示以警告,而您要接受或者拒绝该请求。
+
+如果您允许 uBOL 在当前网站获取额外权限,它就可以更好地对网站内容进行过滤。
+
+您还可以在 uBOL 的设置页面设定默认过滤模式。 如果设定的默认过滤模式是“优化”或“完全”,您必须授予 uBOL 读取或修改所有网页数据的权限。
+
+请注意,该扩展尚未完成,其最终实现目标是:
+
+- 安装时不需要过多扩展权限——额外权限要由用戶指定,按需求及作用域授予。
+
+- 采用完全声明式规则,以求可靠性以及更佳 CPU 和内存使用效率。
diff --git a/platform/mv3/description/webstore.zh_TW.txt b/platform/mv3/description/webstore.zh_TW.txt
new file mode 100644
index 0000000..1add144
--- /dev/null
+++ b/platform/mv3/description/webstore.zh_TW.txt
@@ -0,0 +1,30 @@
+uBO Lite (uBOL) 是款以 MV3 為基礎的「免權限」內容阻擋器。
+
+預設規則集對應了 uBlock Origin 的預設過濾集:
+
+- uBlock Origin 內建的過濾器清單
+- EasyList
+- EasyPrivacy
+- Peter Lowe’s Ad and tracking server list
+
+您可以前往選項頁面(按下彈出面板的 **齒輪** 按鈕)新增更多規則集。
+
+uBOL 是完全宣告式的,意即過濾過程中不需要持續性的 uBOL 處理程序參與,且以 CSS/JS 注入為基礎進行的內容過濾由可靠的瀏覽器執行,而非是擴充功能。 這就代表 uBOL 在內容阻擋過程不會佔用 CPU 和記憶體資源——除了和彈出面板或選項頁面互動的場景外,都不需要 uBOL 的 Service Worker 程序。
+
+uBOL 在安裝期間不需要氾濫的「讀取與修改資料」權限,因此它出廠時的功能和 uBlock Origin 或其他在安裝期間要求「讀取與修改資料」權限的內容阻擋程式相比,會相對受限。
+
+不過 uBOL 能讓你 **明確地** 在自選的特定網站授予額外的權限,使其在這些網站的過濾效果可以在元素過濾及 scripetlet 注入的加持下得到提升。
+
+若要授予指定網站延伸權限,請開啟對話框並選擇更高的過濾模式,如「最佳化」或「完整」。
+
+瀏覽器接著會警告您授予擴充功能請求的額外權限會帶來的後果,而你需要告訴瀏覽器要同意還是拒絕請求。
+
+如果你接受 uBOL 在目前網站請求的額外權限,其在這個網站的過濾效果將會更好。
+
+您可以在 uBOL 的選項頁面設定預設的過濾模式。 如果您選擇「最佳化」或「完整」為預設的模式,您需要授予 uBOL 讀取與修改所有網站資料的權限。
+
+注意這尚未完工,最終目標有:
+
+- 安裝期間不要有氾濫的 host 權限 —— 以網站為基準讓使用者授予延伸權限。
+
+- 完全宣告式,以提升可靠性和 CPU / 記憶體效率。
diff --git a/platform/mv3/extension/_locales/ar/messages.json b/platform/mv3/extension/_locales/ar/messages.json
new file mode 100644
index 0000000..d907faa
--- /dev/null
+++ b/platform/mv3/extension/_locales/ar/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "حاجب محتوى و بأقل التراخيص المسبقة. يحجب الإعلانات والمتتبعات والمعدنات والكثير فوراً عند التثبيت.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} شرط محولة من {{filterCount}} مصفيّات الشبكة ",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "لوحة التحكم",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "الإعدادات",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "عن التطبيق",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "سياسة الخصوصية",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "وضع التصفية",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "افتح لوحة التحكم",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "المزيد",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "أقل",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "افتراضي",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "اعلانات",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "الخصوصية",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "نطاقات البرامج الضارة",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "مضايقات",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "متنوع",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "المناطق واللغات",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "سجل التغييرات",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "شفرة المصدر (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "المساهمون",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "شيفرة المصدر",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "الترجمات",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "قوائم التصفية",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "التبعيات الخارجية (متوافقة مع GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "مرحبا",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "لقد قمت للتو بتثبيت uBO Lite. هنا يمكنك اختيار وضع التصفية الافتراضي لاستخدامه في جميع مواقع الويب.\n\nبشكل افتراضي ، يتم تحديد الوضع <em>الأساسي</em> لأنه لا يتطلب الإذن لقراءة البيانات وتعديلها. إذا كنت تثق في uBO Lite ، فيمكنك منحه إذنًا واسعًا لقراءة البيانات وتعديلها على جميع مواقع الويب من أجل تمكين المزيد من إمكانات التصفية المتقدمة لجميع مواقع الويب افتراضيًا.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "وضع التصفية الافتراضي",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "سيتم تجاوز وضع التصفية الافتراضي بواسطة أوضاع التصفية لكل موقع ويب. يمكنك ضبط وضع التصفية على أي موقع ويب معين وفقًا للوضع الذي يعمل بشكل أفضل على موقع الويب هذا. كل وضع له مزاياه وعيوبه.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "لا تصفية",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "أساسي",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "الأفضل",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "مكتمل",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "تصفية الشبكة الأساسية من قوائم التصفية المحددة.\n\nلا يتطلب إذنًا لقراءة البيانات وتعديلها على مواقع الويب.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "تصفية الشبكة المتقدمة بالإضافة إلى تصفية موسعة محددة من قوائم التصفية المحددة.\n\nيتطلب إذنًا واسعًا لقراءة البيانات وتعديلها على جميع مواقع الويب.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "تصفية الشبكة المتقدمة بالإضافة إلى تصفية موسعة محددة وعامة من قوائم التصفية المحددة.\n\nيتطلب إذنًا واسعًا لقراءة البيانات وتعديلها على جميع مواقع الويب.\n\nقد تؤدي التصفية الموسعة العامة إلى زيادة استخدام موارد صفحة الويب.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "قائمة بأسماء المضيفين التي لن تتم أي تصفية لها",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "السلوك",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "إعادة تحميل الصفحة تلقائيًا عند تغيير وضع التصفية",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/az/messages.json b/platform/mv3/extension/_locales/az/messages.json
new file mode 100644
index 0000000..59b7d81
--- /dev/null
+++ b/platform/mv3/extension/_locales/az/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "İcazəyə ehtiyac duymayan məzmun əngəlləyicisi. Reklamları, izləyiciləri, maynerləri və daha çoxunu quraşdırmadan dərhal sonra əngəlləyir.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — İdarəetmə paneli",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Tənzimləmələr",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Haqqında",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Məxfilik siyasəti",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "İdarəetmə panelini aç",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Daha çox",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Daha az",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Standart",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklamlar",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Məxfilik",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Zəhlətökən elementlər",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Müxtəlif",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Bölgələr, dillər",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Dəyişikliklər siyahısı",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Mənbə kodu (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Töhfə verənlər",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Mənbə kodu",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Tərcümələr",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filtr siyahıları",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Xoş gəldiniz",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/be/messages.json b/platform/mv3/extension/_locales/be/messages.json
new file mode 100644
index 0000000..795f961
--- /dev/null
+++ b/platform/mv3/extension/_locales/be/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Эксперыментальны блакавальнік змесціва, які не патрабуе дазволаў. Блакуе рэкламу, трэкеры, майнеры і іншае адразу пасля ўсталявання.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} правілаў, сканвертаваных з {{filterCount}} сеткавых фільтраў",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Панэль кіравання",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Налады",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Пра пашырэнне",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Палітыка прыватнасці",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "рэжым фільтравання",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Адкрыць панэль кіравання",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Больш",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Менш",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Прадвызначана",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Рэклама",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Прыватнасць",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Дамены шкодных праграм",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Надакучлівасці",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Рознае",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Рэгіёны, мовы",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Журнал змяненняў",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Зыходны код (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Удзельнікі",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Зыходны код",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Пераклады",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Спісы фільтраў",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Вонкавыя залежнасці (GPLv3-сумяшчальныя):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Вітаем",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Вы толькі што ўсталявалі uBO Lite. Тут вы можаце выбраць прадвызначаны рэжым фільтравання для ўсіх вэб-сайтаў.\n\nКалі не ўказана іншае, выбраны <em>базавы</em> рэжым, бо ён не патрабуе дазволаў на чытанне і змяненне звестак. Калі вы давяраеце uBO Lite, можаце даць яму шырэйшыя дазволы на чытанне і змяненне звестак на ўсіх вэб-сайтах, каб зрабіць магчымымі больш прасунутыя функцыі фільтравання прадвызначана на ўсіх вэб-сайтах.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Прадвызначаны рэжым фільтравання",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Прадвызначаны рэжым фільтравання будзе перавызначаны рэжымам, выбраным для пэўнага вэб-сайта. Вы можаце дапасаваць рэжым фільтравання для кожнага сайта згодна з тым, які рэжым працуе лепей на гэтым вэб-сайце. Кожны рэжым мае свае перавагі і недахопы.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "без фільтравання",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "базавы",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "аптымальны",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "поўны",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Базавае сеткавае фільтраванне па выбраных спісах фільтраў.\n\nНе патрабуе дазволу на чытанне і змяненне звестак на вэб-сайтах.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Пашыранае сеткавае фільтраванне плюс дакладнае фільтраванне па выбраных спісах фільтраў.\n\nПатрабуе шырокі дазвол на чытанне і змяненне звестак на ўсіх вэб-сайтах.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Пашыранае сеткавае фільтраванне плюс дакладнае і агульнае пашыранае фільтраванне па выбраных спісах фільтраў.\n\nПатрабуе шырокі дазвол на чытанне і змяненне звестак на ўсіх вэб-сайтах.\n\nАгульнае пашыранае фільтраванне можа прывесці да павышанага спажывання рэсурсаў вэб-старонкі.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Спіс назваў хостаў, для якіх не будзе праводзіцца фільтраванне",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Паводзіны",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Аўтаматычна абнаўляць старонку пры змене рэжыму фільтравання",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/bg/messages.json b/platform/mv3/extension/_locales/bg/messages.json
new file mode 100644
index 0000000..c440a4b
--- /dev/null
+++ b/platform/mv3/extension/_locales/bg/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Блокиране на съдържание без разрешение. Блокира реклами, тракери, миньори и други веднага след инсталирането.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} правила, преобразувани от {{filterCount}} мрежови филтри",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Табло",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Настройки",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Относно",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Политика за поверителност",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "режим на филтриране",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Табло с настройки",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Още",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "По-малко",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Стандартни",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Реклами",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Поверителност",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Вредоносни домейни",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Досадни неща",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Разни",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Региони, езици",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Списък с промени",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Изходен код (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Сътрудници",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Изходен код",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Преводи",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Списъци с филтри",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Външни зависимости (съвместими с GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Добре дошли",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Току-що инсталирахте uBO Lite. Тук можете да изберете режима на филтриране по подразбиране, който да се използва за всички уебсайтове.\n\nПо подразбиране е избран <em>основен</em> режим, тъй като той не изисква разрешение за четене и промяна на данни. Ако се доверите на uBO Lite, можете да му дадете широко разрешение да чете и променя данни на всички уебсайтове, за да активирате по-разширени възможности за филтриране за всички уебсайтове по подразбиране.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Режим на филтриране по подразбиране",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Режимът на филтриране по подразбиране ще бъде заменен от режимите на филтриране за всеки сайт. Можете да регулирате режима на филтриране на всеки уебсайт в зависимост от това кой режим работи най-добре за този уебсайт. Всеки режим има своите предимства и недостатъци.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "без филтриране",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "основен",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "оптимален",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "пълен",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Основно мрежово филтриране от избрани списъци с филтри.\n\nНе изисква разрешение за четене и промяна на данни в уебсайтове.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Разширено мрежово филтриране и специфично разширено филтриране от избрани списъци с филтри.\n\nИзисква широко разрешение за четене и промяна на данни във всички уебсайтове.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Разширено мрежово филтриране и специфично и общо разширено филтриране от избрани списъци с филтри.\n\nИзисква широко разрешение за четене и промяна на данни във всички уебсайтове.\n\nОбщото разширено филтриране може да доведе до по-голямо използване на ресурси от уебстраницата.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Списък с имена на хостове, за които няма да се извършва филтриране",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Поведение",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Автоматично презареждане на страницата при промяна на режима на филтриране",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/bn/messages.json b/platform/mv3/extension/_locales/bn/messages.json
new file mode 100644
index 0000000..ba12b5d
--- /dev/null
+++ b/platform/mv3/extension/_locales/bn/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "একটি পরীক্ষামূলক, অনুমতিহীন কন্টেন্ট নিষিদ্ধকারক। বিজ্ঞাপন, অনুসরণকারী, খননকারী, এবং আরো জিনিস নিষিদ্ধ করো ইন্সটলের সাথে সাথেই।",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — ড্যাশবোর্ড",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "পছন্দসমূহ",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "সম্পর্কে",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "গোপনীয়তার নীতিমালা",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "ফিল্টারিং মোড",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "ড্যাশবোর্ড খুলুন",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "আরো",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "কম",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "পূর্ব-নির্ধারিত",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "বিজ্ঞাপন",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "গোপনীয়তা",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "ম্যালওয়্যার ডোমেইন",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "বিরক্তি",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "বিবিধ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "অঞ্চল, ভাষা",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "পরিবর্তনসূচি",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "অবদানকারী",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "উৎস কোড",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "অনুবাদসমূহ",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "ছাঁকন তালিকা",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "বাহ্যিক নির্ভরতা (GPLv3-সামঞ্জস্যপূর্ণ):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "স্বাগতম",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "আপনি সবেমাত্র uBO Lite ইনস্টল করেছেন। এখানে আপনি সকল ওয়েবসাইটে ব্যবহার করার জন্য ডিফল্ট ফিল্টারিং মোড চয়ন করতে পারেন৷\n\nডিফল্টরূপে, <em>মৌলিক</em> মোডটি নির্বাচন করা হয়েছে কারণ এতে ডেটা পড়ার এবং পরিবর্তন করার অনুমতির প্রয়োজন হয় না৷ আপনি যদি uBO Lite-কে বিশ্বাস করেন, আপনি ডিফল্টরূপে সকল ওয়েবসাইটের জন্য আরও উন্নত ফিল্টারিং ক্ষমতা সক্ষম করার জন্য এটিকে সকল ওয়েবসাইটের ডেটা পড়তে এবং পরিবর্তন করার বিস্তৃত অনুমতি দিতে পারেন।",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "ডিফল্ট ফিল্টারিং মোড",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "ডিফল্ট ফিল্টারিং মোড প্রতি-ওয়েবসাইটে ফিল্টারিং মোড দ্বারা ওভাররাইড করা হবে। আপনি যে কোনও ওয়েবসাইটে ফিল্টারিং মোড সামঞ্জস্য করতে পারেন যা সেই ওয়েবসাইটে সবচেয়ে ভাল কাজ করে। প্রতিটি মোড এর সুবিধা এবং অসুবিধা আছে।",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "ছাঁকনি নেই",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "মৌলিক",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "কার্যকরী",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "সম্পূর্ণ",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "নির্বাচিত ফিল্টার তালিকা থেকে মৌলিক নেটওয়ার্ক ফিল্টারিং।\n\nওয়েবসাইটগুলোতে ডেটা পড়তে এবং পরিবর্তন করার জন্য অনুমতির প্রয়োজন হয় না।",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "উন্নত নেটওয়ার্ক ফিল্টারিং এবং নির্বাচিত ফিল্টার তালিকা থেকে নির্দিষ্ট বর্ধিত ফিল্টারিং।\n\nসকল ওয়েবসাইটের ডেটা পড়তে এবং পরিবর্তন করার জন্য বিস্তৃত অনুমতির প্রয়োজন৷",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "উন্নত নেটওয়ার্ক ফিল্টারিং এবং নির্বাচিত ফিল্টার তালিকা থেকে নির্দিষ্ট এবং জেনেরিকের বর্ধিত ফিল্টারিং।\n\nসকল ওয়েবসাইটে ডেটা পড়তে এবং পরিবর্তন করার জন্য বিস্তৃত অনুমতির প্রয়োজন৷\n\nজেনেরিক বর্ধিত ফিল্টারিং উচ্চতর ওয়েবপেজ সম্পদসমূহের ব্যবহারের কারণ হতে পারে।",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "হোস্টনেমের তালিকা যেগুলোর কোনো ফিল্টারিং হবে না",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "আচরণ",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "ফিল্টারিং মোড পরিবর্তন করার সময় স্বয়ংক্রিয়ভাবে পেজ পুনরায় লোড করুন",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/br_FR/messages.json b/platform/mv3/extension/_locales/br_FR/messages.json
new file mode 100644
index 0000000..48f7013
--- /dev/null
+++ b/platform/mv3/extension/_locales/br_FR/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Ur stanker saotradurioù hep aotre ebet rekis. Stankañ a ra bruderezh, heulierien, minerien ha kalz muioc'h kerkent ha m'eo staliet.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} reolenn, amdroet diwar {{filterCount}} sil rouedad",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Taolenn-vourzh",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Arventennoù",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Diwar-benn",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Politikerezh prevezded",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "mod silañ",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Digeriñ an daolenn-vourzh",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Muioc'h",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Nebeutoc'h",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Dre ziouer",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Bruderezh",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Prevezded",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Saotradurioù",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "A bep seurt",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Rannvroioù, broioù",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Deizlevr ar cheñchamantoù",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Kod mammenn (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Kenlabourerien",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Kod mammenn",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Troidigezhioù",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Rolloù ar siloù",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Dalc'hioù diavaez (a glot gant GPLv3)",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Donemat",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Emaoc'h o paouez staliañ uBO Lite. Amañ-dindan e c'hallit dibab ar mod silañ dre ziouer evit an holl lec'hiennoù.\n\nDre ziouer eo bet dibabet ar mod <em>diazez</em> peogwir n'eus ket ezhomm reiñ aotre da lenn ha kemmañ roadennoù da uBO Lite. M'ho peus fiziañs e uBO Lite e c'hallit reiñ aotreoù ouzhpenn dezhañ a-benn lenn ha kemmañ roadennoù en holl lec'hiennoù, mod-se e vo enaouet ar mod silañ araokaet en holl lec'hiennoù dre ziouer.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Mod silañ dre ziouer",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Ar mod silañ dre ziouer a vo erlec'hiet gant modoù silañ zo evit lec'hiennoù resis. Gallout a rit aozañ ar mod silañ war bep seurt lec'hienn evit ma vo diouzh al lec'hiennoù-se. Pep mod en deus e duioù fall ha mat.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "Tamm silañ ebet",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "diazez",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "ar gwellañ",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "klok",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Silañ diazez ar rouedad diouzh ar rolloù siloù diuzet.\n\nN'eus ket ezhomm a aotre evit lenn hag aozañ roadennoù el lec'hiennoù.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Silañ rouedad araokaet ha silañ amplaet termenet diouzh ar rolloù siloù bet dibabet.\n\nRet eo reiñ aotreoù ouzhpenn a-benn lenn ha kemmañ roadennoù en holl lec'hiennoù.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Silañ rouedad araokaet ha silañ amplaet resis ha hollek diouzh ar rolloù siloù diuzet.\n\nRet eo reiñ aotreoù ouzhpenn a-benn lenn ha kemmañ roadennoù en holl lec'hiennoù.\n\nGant ar silañ amplaet hollek e c'hallfe ar bajenn web implijout muioc'h a zanvezioù.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Roll eus anvioù domanioù el-lec'h ne vo silañ ebet",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Emzalc'h",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Adkargañ ar bajenn ent-emgefreek pa vez cheñchet an doare silañ",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/bs/messages.json b/platform/mv3/extension/_locales/bs/messages.json
new file mode 100644
index 0000000..14e847d
--- /dev/null
+++ b/platform/mv3/extension/_locales/bs/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Eksperimentalni blokator sadržaja bez dopuštenja. Blokira oglase, trakere za praćenje, \"rudare\" kovanica kripto valute i još [...]",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} pravila, pretvoreno iz {{filterCount}} mrežnih filtera",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Nadzorna Ploča",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Postavke",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "O aplikaciji",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Politika privatnosti",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "način filtriranja",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Otvorite nadzornu ploču",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Više",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Manje",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Zadano",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklame",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privatnost",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Zloćudne domene",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Dosade",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Raznolično",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regije, jezici",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Dnevnik izmjena",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Izvorna koda (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Suradnici",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Izvorna koda",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Prijevodi",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Zapis filtera",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Vanjske ovisnosti (kompatibilno s GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Dobrodošli",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Upravo ste instalirali uBO Lite. Ovdje možete odabrati zadani način filtriranja koji će se koristiti na svim web stranicama.\n\nPrema zadanim postavkama, <em>Osnovni</em> način rada je odabran jer ne zahtijeva dozvolu za čitanje i izmjenu podataka. Ako vjerujete uBO Lite-u, možete mu dati široku dozvolu za čitanje i izmjenu podataka na svim web lokacijama kako bi se omogućile naprednije mogućnosti filtriranja za sve web stranice prema zadanim postavkama.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Zadani način filtriranja",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Zadani način filtriranja bit će zamijenjen načinima filtriranja po web stranici. Možete podesiti način filtriranja na bilo kojoj web stranici prema tome koji način rada najbolje funkcionira na toj web stranici. Svaki način rada ima svoje prednosti i nedostatke.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "nema filtriranja",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "osnovno",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimalno",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "kompletan",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": " Osnovno mrežno filtriranje sa odabranih lista filtera.\n\nNe zahtijeva dozvolu za čitanje i modificiranje podataka na web stranicama.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Napredno mrežno filtriranje plus specifično prošireno filtriranje sa odabranih lista filtera.\n\nZahtijeva široku dozvolu za čitanje i izmjenu podataka na svim web stranicama.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Napredno mrežno filtriranje plus specifično i generičko prošireno filtriranje sa odabranih lista filtera.\n\nZahtijeva široku dozvolu za čitanje i izmjenu podataka na svim web stranicama.\n\nGeneričko prošireno filtriranje može uzrokovati veću upotrebu resursa web stranice.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Lista imena hostova za koja se neće vršiti filtriranje",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Ponašanje",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatski ponovo učitajte stranicu prilikom promjene načina filtriranja",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/ca/messages.json b/platform/mv3/extension/_locales/ca/messages.json
new file mode 100644
index 0000000..abe76eb
--- /dev/null
+++ b/platform/mv3/extension/_locales/ca/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Un blocador de contingut lleuger experimental i sense permisos: bloca anuncis, rastrejadors, miners i molt més per defecte.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} regles, convertides a partir de {{filterCount}} filtres de xarxa",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Tauler",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Configuració",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Quant a",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Política de privadesa",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "mode de filtre",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Obre el tauler",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Més",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Menys",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Per defecte",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Anuncis",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privadesa",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Dominis de programari maliciós",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Elements molestos",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscel·lània",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, llengües",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Registre de canvis",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Codi font (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Col·laboradors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Codi font",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Traduccions",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Llistat de filtres",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Dependències externes (compatibles amb GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Us donem la benvinguda",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Acabeu d'instal·lar l'uBO Lite. Aquí podeu triar el mode de filtrat per defecte per utilitzar-lo a tots els llocs web.\n\nPer defecte, el mode <em>Bàsic</em> està seleccionat perquè no requereix permís per llegir i canviar dades. Si confieu en l'uBO Lite, podeu donar-li un ampli permís per llegir i canviar dades a tots els llocs web per tal d'activar capacitats de filtratge més avançades per a tots els llocs web per defecete.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Mode de filtratge per defecte",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "El mode de filtratge per defecte serà substituït pels modes de filtrat per lloc web. Podeu ajustar el mode de filtratge a qualsevol lloc web segons el qual funcioni millor en aquest lloc web. Cada mode té els seus avantatges i desavantatges.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "sense filtres",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "bàsic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "òptim",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complet",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Filtratge bàsic de xarxa a partir de llistes de filtres seleccionades.\n\nNo requereix permís per llegir i canviar dades als llocs web.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Filtratge de xarxa avançat més un filtratge ampli específic de les llistes de filtres seleccionades.\n\nRequereix un ampli permís per llegir i canviar dades a tots els llocs web.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Filtratge de xarxa avançat més un filtratge estès específic i genèric de les llistes de filtres seleccionades.\n\nRequereix un ampli permís per llegir i canviar dades a tots els llocs web.\n\nEl filtratge estès genèric pot provocar un ús més elevat dels recursos de la pàgina web.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Llistat de noms d'amfitrió als quals no s'aplicarà cap filtre",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Comportament",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Recarrega automàticament la pàgina quan canvieu el mode de filtratge",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/cs/messages.json b/platform/mv3/extension/_locales/cs/messages.json
new file mode 100644
index 0000000..abc5164
--- /dev/null
+++ b/platform/mv3/extension/_locales/cs/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Experimentální blokátor obsahu vyžadující méně oprávnění. Blokuje reklamy, sledovače, těžaře a jiné ihned po instalaci.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} pravidel, převedeno z {{filterCount}} síťových filtrů",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Ovládací panel",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Nastavení",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "O rozšíření",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Ochrana soukromí",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "Filtrovací režim",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Otevřít ovládací panel",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Více",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Méně",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Výchozí",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklamy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Soukromí",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domény",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Dotěrnosti",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Ostatní",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regionální, jazykové",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Přehled změn",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Zdrojový kód (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Přispívající",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Zdrojový kód",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Překlady",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Seznamy filtrů",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Externí závislosti (kompatibilní s GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Vítejte",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Právě jste nainstalovali uBO Lite. Zde si můžete vybrat výchozí režim filtrování pro použití na všech webových stránkách.\n\nVe výchozím nastavení je vybrán režim <em>Základní</em>, protože nevyžaduje oprávnění ke čtení a změně dat. Pokud důvěřujete uBO Lite, můžete mu udělit široké oprávnění číst a měnit data na všech webových stránkách, abyste ve výchozím nastavení povolili pokročilejší možnosti filtrování pro všechny webové stránky.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Výchozí filtrovací režim",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Výchozí režim filtrování bude přepsán režimy filtrování pro jednotlivé webové stránky. Režim filtrování na libovolném webu můžete upravit podle toho, který režim na daném webu funguje nejlépe. Každý režim má své výhody a nevýhody.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "žádné filtrování",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "základní",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimální",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "kompletní",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Základní filtrování sítě z vybraných seznamů filtrů.\n\nNevyžaduje oprávnění ke čtení a změně údajů na webových stránkách.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Pokročilé síťové filtrování plus specificky rozšířené filtrování z vybraných seznamů filtrů.\n\nVyžaduje rozsáhlé oprávnění ke čtení a změně dat na všech webech.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Pokročilé síťové filtrování plus specificky a obecně rozšířené filtrování z vybraných seznamů filtrů.\n\nVyžaduje rozsáhlé oprávnění ke čtení a změně dat na všech webech.\n\nObecně rozšířené filtrování může způsobit vyšší využití zdrojů webových stránek.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Seznam názvů hostitelů, pro které nebude probíhat žádné filtrování",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Chování",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automaticky znovu načíst stránku při změně filtrovacího režimu",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/cv/messages.json b/platform/mv3/extension/_locales/cv/messages.json
new file mode 100644
index 0000000..627857e
--- /dev/null
+++ b/platform/mv3/extension/_locales/cv/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/da/messages.json b/platform/mv3/extension/_locales/da/messages.json
new file mode 100644
index 0000000..7f628ab
--- /dev/null
+++ b/platform/mv3/extension/_locales/da/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "En tilladelsesløs indholdsblocker. Blokerer som standard annoncer, trackere, minere mv. straks efter installationen.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} regler, konverteret fra {{filterCount}} netværksfiltre",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Kontrolpanel",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Indstillinger",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Om",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Fortrolighedspolitik",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtreringstilstand",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Åbn kontrolpanelet",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Flere",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Færre",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Standard",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklamer",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Fortrolighed",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware-domæner",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Irritationsmomenter",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Diverse",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regioner, sprog",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Ændringslog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Kildekode (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Bidragsydere",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Kildekode",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Oversættelser",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filterlister",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Eksterne afhængigheder (GPLv3-kompatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Velkommen",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Du har netop installeret uBO Lite. Her kan den standardfiltreringstilstand vælges, der skal bruges på alle websteder.\n\nStandardvalget er <em>Basis</em>-tilstand, da den ikke kræver tilladelse til at læse og ændre data. Er uBO Lite betroet, kan den udvidede tilladelse til at læse og ændre data på alle websteder tildeles for som standard at aktivere mere avancerede filtreringsfunktioner for alle websteder.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Standardfiltreringstilstand",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Standardfiltreringstilstanden tilsidesættes af per-websted filtreringstilstande. Filtreringstilstanden kan justeres på ethvert givent websted iht., hvilken tilstand der fungere bedst på det givne websted. Hver tilstand har sine fordele og ulemper.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "ingen filtrering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basis",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "fuldstændig",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basis netværksfiltrering ud fra valgte filterlister.\n\nTilladelse kræves ikke til at læse og ændre data på websteder.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Avanceret netværksfiltrering plus specifik udvidet filtrering ud fra valgte filterlister.\n\nOmfattende tilladelse kræves for at læse og ændre data på alle websteder.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Avanceret netværksfiltrering plus specifik og generisk udvidet filtrering ud fra valgte filterlister.\n\nOmfattende tilladelse kræves for at læse og ændre data på alle websteder.\n\nGenerisk, udvidet filtrering kan medføre højere webside-ressourceforbrug.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Liste over værtsnavne, for hvilke ingen filtrering vil ske",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Adfærd",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatisk sidegenindlæsning ved skift af filtreringstilstand",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/de/messages.json b/platform/mv3/extension/_locales/de/messages.json
new file mode 100644
index 0000000..1e014df
--- /dev/null
+++ b/platform/mv3/extension/_locales/de/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Ein Inhaltsblocker, der ohne Berechtigungen auskommt. Blockiert Werbung, Tracker und mehr sofort nach der Installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} Regeln, umgewandelt aus {{filterCount}} Netzwerkfiltern",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Einstellungen",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Über",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Datenschutzhinweise",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "Filtermodus",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Dashboard öffnen",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Mehr",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Weniger",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Standard",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Werbung",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Datenschutz",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Domains mit Schadsoftware",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Belästigungen",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Verschiedenes",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regionen, Sprachen",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Änderungsprotokoll",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Quellcode (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Mitwirkende",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Quellcode",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Übersetzungen",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filterlisten",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Externe Abhängigkeiten (GPLv3-kompatibel):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Willkommen",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Sie haben soeben uBO Lite installiert. Sie können hier den Standardfiltermodus auswählen, der auf allen Websites angewendet werden soll.\n\nStandardmäßig ist der <em>einfache</em> Modus ausgewählt, weil er keine Berechtigung zum Lesen und Ändern von Daten erfordert. Wenn Sie uBO Lite vertrauen, können Sie dieser Erweiterung eine weitreichende Berechtigung zum Lesen und Ändern von Daten auf allen Websites erteilen, um standardmäßig erweiterte Filterfunktionen für alle Websites zu aktivieren.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Standardfiltermodus",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Der Standardfiltermodus wird durch individuelle Filtermodi für die jeweiligen Websites überschrieben. Sie können den Filtermodus auf jeder beliebigen Website entsprechend dem Modus anpassen, der auf dieser Website am besten funktioniert. Jeder Modus hat seine Vor- und Nachteile.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "nicht filtern",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "einfach",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "vollständig",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Einfaches Netzwerkfiltern aus gewählten Filterlisten.\n\nErfordert keine Berechtigung zum Lesen und Ändern von Daten auf Websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Erweitertes Netzwerkfiltern plus spezifisches erweitertes Filtern aus ausgewählten Filterlisten.\n\nErfordert eine weitreichende Berechtigung zum Lesen und Ändern von Daten auf allen Websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Erweitertes Netzwerkfiltern plus spezifisches und allgemeines erweitertes Filtern aus gewählten Filterlisten.\n\nErfordert eine weitreichende Berechtigung zum Lesen und Ändern von Daten auf allen Websites.\n\nDas allgemeine erweiterte Filtern kann zu einem höheren Ressourcenverbrauch der Webseite führen.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Liste der Hostnamen, für die nicht gefiltert wird",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Verhalten",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Seite beim Ändern des Filtermodus automatisch neu laden",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/el/messages.json b/platform/mv3/extension/_locales/el/messages.json
new file mode 100644
index 0000000..cc8c204
--- /dev/null
+++ b/platform/mv3/extension/_locales/el/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Ένα πειραματικό εργαλείο φραγής περιεχομένου χωρίς δικαιώματα. Αποκλείει διαφημίσεις, ιχνηλάτες και πολλά άλλα μετά την εγκατάσταση.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} κανόνες, μετατράπηκαν από {{filterCount}} φίλτρα δικτύου",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Πίνακας ελέγχου",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Ρυθμίσεις",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Σχετικά",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Πολιτική απορρήτου",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "λειτουργία φιλτραρίσματος",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Ανοίξτε τον πίνακα ελέγχου",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Περισσότερα",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Λιγότερα",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Προεπιλογή",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Διαφημίσεις",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Απόρρητο",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Τομείς κακόβουλου λογισμικού",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Ενοχλήσεις",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Διάφορα",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Περιοχές, γλώσσες",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Αρχείο αλλαγών",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Πηγαίος κώδικας (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Συνεισφέροντες",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Πηγαίος κώδικας",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Μεταφράσεις",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Λίστες φίλτρων",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Εξωτερικές εξαρτήσεις (συμβατές με GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Καλώς ήρθατε",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Μόλις εγκαταστήσατε το uBO Lite. Μπορείτε να επιλέξετε εδώ την προεπιλεγμένη λειτουργία φιλτραρίσματος για χρήση σε όλους τους ιστότοπους.\n\nΑπό προεπιλογή, έχει επιλεγεί η λειτουργία <em>Βασική</em> επειδή δεν απαιτεί άδεια ανάγνωσης και αλλαγής δεδομένων. Εάν εμπιστεύεστε το uBO Lite, μπορείτε να του δώσετε ευρεία άδεια να διαβάζει και να αλλάζει δεδομένα σε όλους τους ιστότοπους, προκειμένου να ενεργοποιηθούν πιο προηγμένες δυνατότητες φιλτραρίσματος για όλους τους ιστότοπους από προεπιλογή.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Προεπιλεγμένη λειτουργία φιλτραρίσματος",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Η προεπιλεγμένη λειτουργία φιλτραρίσματος θα αντικατασταθεί από τις λειτουργίες φιλτραρίσματος ανά ιστότοπο. Μπορείτε να προσαρμόσετε τη λειτουργία φιλτραρίσματος σε οποιονδήποτε ιστότοπο, σύμφωνα με όποια λειτουργία λειτουργεί καλύτερα σε αυτόν τον ιστότοπο. Κάθε λειτουργία έχει τα πλεονεκτήματα και τα μειονεκτήματά της.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "χωρίς φιλτράρισμα",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "βασικό",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "βέλτιστο",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "πλήρης",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Βασικό φιλτράρισμα δικτύου από επιλεγμένες λίστες φίλτρων.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Προηγμένο φιλτράρισμα δικτύου και συγκεκριμένο εκτεταμένο φιλτράρισμα από επιλεγμένες λίστες φίλτρων.\n\nΑπαιτεί ευρεία άδεια για την ανάγνωση και την αλλαγή δεδομένων σε όλους τους ιστότοπους.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Προηγμένο φιλτράρισμα δικτύου και ειδικό και γενικό εκτεταμένο φιλτράρισμα από επιλεγμένες λίστες φίλτρων.\n\nΑπαιτεί ευρεία άδεια για την ανάγνωση και την αλλαγή δεδομένων σε όλους τους ιστότοπους.\n\nΤο γενικό εκτεταμένο φιλτράρισμα μπορεί να προκαλέσει μεγαλύτερη χρήση πόρων ιστοσελίδας.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Λίστα των hostnames για τα οποία δεν θα πραγματοποιηθεί φιλτράρισμα",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Συμπεριφορά",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Αυτόματη επαναφόρτωση σελίδας κατά την αλλαγή λειτουργίας φιλτραρίσματος",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/en/messages.json b/platform/mv3/extension/_locales/en/messages.json
new file mode 100644
index 0000000..6725ba1
--- /dev/null
+++ b/platform/mv3/extension/_locales/en/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/en_GB/messages.json b/platform/mv3/extension/_locales/en_GB/messages.json
new file mode 100644
index 0000000..b935137
--- /dev/null
+++ b/platform/mv3/extension/_locales/en_GB/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "An experimental, permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Change-log",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behaviour",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/eo/messages.json b/platform/mv3/extension/_locales/eo/messages.json
new file mode 100644
index 0000000..9e86eea
--- /dev/null
+++ b/platform/mv3/extension/_locales/eo/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Agordoj",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Pri",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Pli",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Malpli",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Defaŭlta",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklamoj",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privateco",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Ŝanĝprotokolo",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Fontkodo (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Kontribuantoj",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Fontkodo",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Tradukoj",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Bonvenon",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "simpla",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/es/messages.json b/platform/mv3/extension/_locales/es/messages.json
new file mode 100644
index 0000000..1bf7af5
--- /dev/null
+++ b/platform/mv3/extension/_locales/es/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Un bloqueador de contenido con menos permisos. Bloquea anuncios, rastreadores, criptomineros y aún más.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} reglas, convertidas desde {{filterCount}} filtros de red",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Panel de control",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Configuración",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Acerca de",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Política de privacidad",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "modo de filtrado",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Abrir panel de control",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Más",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Menos",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Predeterminado",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Anuncios",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacidad",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Dominios de malware",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Elementos molestos",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Varios",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regiones, idiomas",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Registro de cambios",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Código fuente (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Colaboradores",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Código fuente",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Traducciones",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Listas de filtros",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Dependencias externas (compatibles con GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Bienvenida",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Acabas de instalar uBO Lite. Aquí puedes elegir el modo de filtrado predeterminado que se utilizará en todos los sitios web.\n\nPor defecto, el modo <em>básico</em> está seleccionado porque no requiere permiso para leer y modificar datos. Si confías en uBO Lite, puedes otorgarle un permiso amplio para leer y modificar datos en todos los sitios web para habilitar capacidades de filtrado más avanzadas para todos los sitios web de forma predeterminada.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Modo de filtrado predeterminado",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "El modo de filtrado predeterminado será anulado por los modos de filtrado por sitio web. Puedes ajustar el modo de filtrado en cualquier sitio web según el modo que funcione mejor en ese sitio web. Cada modo tiene sus ventajas y desventajas.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "sin filtrado",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "básico",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "óptimo",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "completo",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Filtrado de red básico a partir de listas de filtros seleccionadas.\n\nNo requiere permiso para leer y modificar datos en sitios web.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Filtrado de red avanzado más filtrado extendido específico a partir de listas de filtros seleccionadas.\n\nRequiere un amplio permiso para leer y modificar datos en todos los sitios web.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Filtrado de red avanzado más filtrado extendido específico y genérico a partir de listas de filtros seleccionadas.\n\nRequiere un amplio permiso para leer y modificar datos en todos los sitios web.\n\nEl filtrado genérico extendido puede causar un mayor uso de recursos de la página web.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Lista de nombres de host para los cuales no tendrá lugar el filtrado",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Comportamiento",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Recargar automáticamente la página al cambiar el modo de filtrado",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/et/messages.json b/platform/mv3/extension/_locales/et/messages.json
new file mode 100644
index 0000000..92e5952
--- /dev/null
+++ b/platform/mv3/extension/_locales/et/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Õigustevaba sisublokeerija. Blokeerib reklaamid, jälitajad, kaevandajad ja teised kohe pärast paigaldust.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} reeglit, konverteeritud {{filterCount}} võrgufiltrist",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Töölaud",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Sätted",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Teave",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privaatsusteatis",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtreerimisrežiim",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Ava töölaud",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Rohkem",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Vähem",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Vaikimisi",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklaamid",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privaatsus",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Pahavara domeenid",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Tüütused",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Varia",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regioonid, keeled",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Muudatuste logi",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Lähtekood (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Toetajad",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Lähtekood",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Tõlked",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filtriloendid",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Välised sõltuvused (ühilduvad GPLv3-ga):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Tere tulemast",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Olete paigaldanud uBO Lite'i. Nüüd valige kõigil veebilehtedel kasutatav üldine filtreerimisrežiim.\n\nTavaliselt kehtib <em>Põhiline</em> režiim, kuna see ei vaja andmete lugemise ja muutmise luba. uBO Lite'i usaldamisel saate sel lubada andmeid lugeda ja muuta kõigil veebilehtedel, et neid saaks vaikimisi filtreerida põhjalikumalt.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Tavaline filtreerimisrežiim",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Tavalise filtreerimisrežiimi asemel kasutatakse vajadusel veebilehepõhiseid filtreerimisrežiime. Saate veebilehte filtreerida kõige paremini toimiva režiimiga. Igal režiimil on omad eelised ja puudused.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "filtreerimine puudub",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "põhiline",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimaalne",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "põhjalik",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Interneti üldine filtreerimine valitud filtreerimisloenditega.\n\nPole vaja veebilehtede andmete lugemise ja muutmise luba.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Interneti täpsem filtreerimine enda valitud filtreerimisloenditega.\n\nVajab veebilehtede andmete lugemise ja muutmise üldist luba.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Interneti põhjalikum filtreerimine ja enda valitud filtreerimisloenditega täpsem ning üldisem filtreerimine.\n\nVajab kõikide veebilehtede andmete lugemise ja muutmise üldist luba.\n\nÜldine ja põhjalikum filtreerimine võib koormata veebilehe ressursse tavapärasest rohkem.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Hostinimede loend, kus filtreerimine keelatakse",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Käitumine",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Leht laadib ise uuesti filtreerimisrežiimi muutmisel",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/eu/messages.json b/platform/mv3/extension/_locales/eu/messages.json
new file mode 100644
index 0000000..386468f
--- /dev/null
+++ b/platform/mv3/extension/_locales/eu/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Probatako eta baimenik behar ez duen eduki-blokeatzailea. Iragarkiak, jarraitzeko tresnak, eta gehiago instalatzean bertan [...]",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} erregela, {{filterCount}} sare-filtrotik bihurtuta.",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite - Lan-lekua",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Ezarpenak",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Honi buruz",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Pribatutasun politika",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "Iragazteko modua",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Ireki lan-lekua",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Gehiago",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Gutxiago",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Defektuzkoa",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Iragarkiak",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Pribatutasuna",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domeinuak",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Eragozpenak",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Bestelakoak",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Eskualdeak, hizkuntzak",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Aldaketen erregistroa",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Iturburu kodea (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Laguntzaileak",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Iturburu-kodea",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Itzulpenak",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Iragazki-zerrendak",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Kanpo menpekotasunak (GPLv3 lizentziarekin bateragarriak):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Ongi etorri",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "uBO Lite instalatu berri duzu. Hemen, webgune guztietan erabiltzeko iragazteko modu lehenetsia hauta dezakezu.\n\nAurrez zehaztuta, <em>Oinarrizkoa </em>modua hautatzen da, ez baita baimenik behar datuak irakurtzeko eta aldatzeko. uBO Lite-n konfiantza baduzu, webgune guztietan datuak irakurtzeko eta aldatzeko baimen zabala eman dezakezu, webgune guztietarako iragazteko gaitasun aurreratuagoak gaitzeko, aldez aurretik zehaztuta.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Lehenetsitako iragazteko modua",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Iragazteko aurrez zehaztutako modua webgunearen arabera iragazteko moduen bidez deuseztatuko da. Edozein webgunetan iragazteko modua doitu dezake, webgune horretan hobekien funtzionatzen duen moduaren arabera. Modu bakoitzak bere abantailak eta desabantailak ditu.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "iragazkirik gabe\n",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "Oinarrizkoa",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimo",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "eginda",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Oinarrizko sarea iragaztea, hautatutako iragazkien zerrendetatik abiatuta.\n\nEz da baimenik behar webguneetan datuak irakurtzeko eta aldatzeko.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "\nSare aurreratuaren iragazketa gehi hautatutako iragazkien zerrenden berariazko hedapena.\n\nBaimen zabala behar da webgune guztietan datuak irakurri eta aldatzeko.\n",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "\nSare aurreratuko iragazketa gehi hedapen espezifiko eta generikoa hautatutako iragazkien zerrendetan.\n\nBaimen zabala behar da webgune guztietan datuak irakurri eta aldatzeko.\n\nIragazte hedatu generikoak web-orriaren baliabide gehiago erabiltzea eragin dezake.\n",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "portaera or jokaera",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Orria automatikoki kargatuko da iragazteko modua aldatuko denean",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/fa/messages.json b/platform/mv3/extension/_locales/fa/messages.json
new file mode 100644
index 0000000..b0ed662
--- /dev/null
+++ b/platform/mv3/extension/_locales/fa/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "تنظیمات",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "درباره",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "بیشتر",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "کمتر",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "پیش فرض",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "تبلیغات",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "حریم خصوصی",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "سایر",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/fi/messages.json b/platform/mv3/extension/_locales/fi/messages.json
new file mode 100644
index 0000000..44ac9b0
--- /dev/null
+++ b/platform/mv3/extension/_locales/fi/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Käyttöoikeudeton estotyökalu, joka estää välittömästi asennuksesta lähtien mm. mainokset, seurannat ja kryptolouhijat.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} sääntöä, muunnettu {{filterCount}} verkkosuodattimesta",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Hallintapaneeli",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Asetukset",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Tietoja",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Tietosuojakäytäntö",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "suodatustila",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Avaa hallintapaneeli",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Enemmän",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Vähemmän",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Oletus",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Mainokset",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Tietosuoja",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Haittaohjelmia jakelevat verkkotunnukset",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Ärsykkeet",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Sekalaiset",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Alueet, kielet",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Muutoshistoria",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Lähdekoodi (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Avustajat",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Lähdekoodi",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Käännökset",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Suodatinlistat",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Ulkopuoliset riippuvuudet (GPLv3-yhteensopiva):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Tervetuloa",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Olet juuri asentanut uBO Liten. Tästä voit valita kaikilla sivustoilla oletusarvoisesti käytettävän suodatustilan.\n\nOletusarvoinen valinta on <em>Perus</em>-tila, koska se ei vaadi tietojen luku- ja muokkausoikeutta. Jos luotat uBO Liteen, voit myöntää sille kaikki sivustot kattavan laajan tietojen luku- ja muokkausoikeuden, jolloin edistyneemmät suodatusominaisuudet ovat oletusarvoisesti käytössä kaikilla sivustoilla.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Oletusarvoinen suodatustila",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Sivustokohtaiset suodatustilat ohittavat oletusarvoisen suodatustilan. Voit säätää suodatustilan kullekin sivustolle parhaiten sopivaksi. Jokaisella tilalla on hyvät ja huonot puolensa.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "ei suodatusta",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "perus",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimaalinen",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "täysi",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Perustason verkkosuodatus valituilla suodatinlistoilla.\n\nEi edellytä sivustojen tietojen luku- ja muokkausoikeutta.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Edistynyt verkkosuodatus sekä kohdistettu laajempi suodatus valituilla suodatinlistoilla.\n\nEdellyttää kaikki sivustot kattavan, laajemman tietojen luku- ja muokkausoikeuden.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Edistynyt verkkosuodatus sekä kohdistettu ja yleinen laajempi suodatus valituilla suodatinlistoilla.\n\nEdellyttää kaikki sivustot kattavan, laajemman tietojen luku- ja muokkausoikeuden.\n\nYleinen laajempi suodatus saattaa kasvattaa sivukohtaista resurssien käyttöä.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Listaus osotteista, joita ei suodateta.",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Toiminta",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Lataa sivu suodatustilan vaihtuessa automaattisesti uudelleen",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/fil/messages.json b/platform/mv3/extension/_locales/fil/messages.json
new file mode 100644
index 0000000..68025d2
--- /dev/null
+++ b/platform/mv3/extension/_locales/fil/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Permission-less na tagaharang ng content. Hinaharang ang mga ad, tracker, miner, at higit pa pagka-install.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} (na) mga patakaran, mula sa {{filterCount}} (na) mga network filter",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Mga Setting",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Tungkol",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Patakaran sa pagkapribado",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "moda nang pagsasala",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Buksan ang dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Higit pa",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Mas konti",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Mga ad",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Pagkapribado",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Mga domain na may malware",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Mga nakakaabalang bagay",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Iba pa",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Mga rehiyon o wika",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Talaan ng mga pagbabago",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Mga nag-ambag",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Mga pagsasalin",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Listahan ng mga filter",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Mga panlabas na dependency (angkop sa GPLv3)",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Mabuhay",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Maligayang pagdating sa uBO Lite. Dito mo mapipili ang default na mode sa pagfi-filter para sa lahat ng mga website.\n\nAng default na mode ay <em>Basic</em> dahil hindi nito kailangan ng karagdagang pahintulot na magbasa at magbago ng datos. Pwede mong pahintulutan ang uBO Lite, kung nagtitiwala ka sa amin, na basahin at baguhin ang data ng lahat ng mga website para sa karagdagang kakayahan sa pagfi-filter.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default na mode sa pagfi-filter",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Mas sinusunod ang mode sa pagfi-filter na pinasadya para sa isang website kaysa sa pangkalahatang mode sa pagfi-filter. Mababago mo ang mode sa pagfi-filter sa isang website sa kung anong mode ang mas mainam. May kalakasan at kahinaan ang bawat mode.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "walang pagfi-filter",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "pinainam",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "kumpleto",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic na network filtering mula sa mga napiling listahan ng mga filter.\n\nHindi kailangan ng pahintulot na basahin at baguhin ang mga data ng website.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Pinainam na network filtering at partikular na karagdagang pagfi-filter mula sa mga napiling listahan ng mga filter.\n\nKailangan ang pahintulot na basahin at baguhin ang mga data ng lahat ng mga website.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Pinainam na network filtering, partikular at generikong karagdagang pagfi-filter mula sa mga napiling listahan ng mga filter.\n\nKailangan ang pahintulot na basahin at baguhin ang mga data ng lahat ng mga website.\n\nMaaaring mas malaki ang konsyumo sa resource dahil sa generikong karagdagang pagfi-filter.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Ugali",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Awtomatikong i-reload ang pahina kapag binago ang mode sa pagfi-filter",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/fr/messages.json b/platform/mv3/extension/_locales/fr/messages.json
new file mode 100644
index 0000000..2f6a837
--- /dev/null
+++ b/platform/mv3/extension/_locales/fr/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Un bloqueur de contenu sans permission requise. Bloque les publicités, pisteurs, mineurs et plus dès l'installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} règles converties depuis {{filterCount}} filtres de réseau",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Tableau de bord",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Paramètres",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "À propos",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Politique de confidentialité",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "Mode de filtrage",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Ouvrir le Tableau de bord",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Plus",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Moins",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Par défaut",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Publicités",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Confidentalité",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Domaines malveillants",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Nuisances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Divers",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Régions, langues",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Journal des changements",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Code Source (Licence GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributeurs",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Code Source",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Traductions",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Listes de filtres",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Dépendances externes (compatibles GPLv3) :",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Bienvenue",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Vous venez d'installer uBO Lite. À présent vous pouvez choisir le mode de filtrage par défaut à utiliser sur tous les sites Web.\n\nPar défaut, le mode <em>Basique</em> est sélectionné car il ne requiert pas de permissions pour lire et modifier les données. Si vous faites confiance à uBO Lite, vous pouvez lui donner des permissions étendues pour lire et modifier les données sur tous les sites Web pour activer des capacités de filtrage plus avancées pour tous les sites Web par défaut.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Mode de filtrage par défaut",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Le mode de filtrage par défaut sera remplacé par des modes de filtrage au cas par cas. Vous pouvez ajuster le mode de filtrage sur un site Web donné d'après le mode qui fonctionne le mieux sur ce dernier. Chaque mode a ses avantages et ses inconvénients.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "Pas de filtrage",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "Basique",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "Optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "Complet",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Du filtrage réseau basique issu de listes de filtres choisies.\n\nNe nécessite pas de permission pour lire et modifier les données sur les sites Web.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Du filtrage réseau avancé plus du filtrage étendu spécifique issu de listes de filtres choisies.\n\nNécessite des permissions étendues pour lire et modifier les données sur tous les sites Web.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Du filtrage réseau avancé plus du filtrage étendu générique et spécifique issu de listes de filtres choisies.\n\nNécessite des permissions étendues pour lire et modifier les données sur tous les sites Web.\n\nLe filtrage étendu générique peut augmenter l'utilisation des ressources pour la page Web.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Liste des noms de domaine pour lesquels aucun filtrage n'aura lieu",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Comportement",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Recharger automatiquement la page lors du changement de mode de filtrage",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/fy/messages.json b/platform/mv3/extension/_locales/fy/messages.json
new file mode 100644
index 0000000..606fc0a
--- /dev/null
+++ b/platform/mv3/extension/_locales/fy/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "In eksperimintele, tastimmingsleaze ynhâldsblokkearder. Blokkearret daliks nei ynstallaasje advertinsjes, trackers, miners en mear.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rigels, konvertearre út {{filterCount}} netwurkfilters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite – Dashboerd",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Ynstellingen",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Oer",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacybelied",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtermodus",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Dashboerd iepenje",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Mear",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Minder",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Standert",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Advertinsjes",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malwaredomeinen",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Steurende eleminten",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Diversken",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Gebieden, talen",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Wizigingslochboek",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Boarnekoade (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Meiwurkers",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Boarnekoade",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Oersettingen",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filterlisten",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Eksterne ôfhinklikheden (GPLv3-kompatibel):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Wolkom",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Jo hawwe sakrekt uBO Lite ynstallearre. Hjir kinne jo de standery filtermodus foar it gebrûk op alle websites kieze.\n\nStandert wurdt de modus <em>Basis</em> selektearre, omdat hjirfoar gjin tastimming foar it lêzen en wizigjen fan gegevens fereaske is. As jo uBO Lite fertrouwe, kinne jo it brede tastimming foar it lêzen en wizigjen fan gegevens op alle websites ferliene, sadat standert mear avansearre filtermooglikheden foar alle websites beskikber binne.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Standert filtermodus",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "De standert filtermodus wurdt negearre troch filtermodi per website. Jo kinne de filtermodus op in winske website oanpasse nei de modus dy’t op dy website it bêste wurket. Elke modus hat foar- en neidielen.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "gjin filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basis",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimaal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "folslein",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basale netwurkfiltering fan selektearre filterlisten út.\n\nFereasket gjin tastimming foar it lêzen en wizigjen fan gegevens op websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Avansearre netwurkfiltering plus spesifike wiidweidige filtering fan selektearre filterlisten út.\n\nFereasket brede tastimming foar it lêzen en wizigjen fan gegevens op alle websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Avansearre netwurkfiltering plus spesifike en algemiene wiidweidige filtering fan selektearre filterlisten út.\n\nFereasket brede tastimming foar it lêzen en wizigjen fan gegevens op alle websites.\n\nAlgemiene wiidweidige filtering kin in heger gebrûk fan websideboarnen feroarsaakje.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List fan hostnammen wêrfoar gjin filtering plakfynt.",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Gedrach",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Side automatysk fernije by wizigjen fan filtermodus",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/gl/messages.json b/platform/mv3/extension/_locales/gl/messages.json
new file mode 100644
index 0000000..460bdb8
--- /dev/null
+++ b/platform/mv3/extension/_locales/gl/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Un bloqueador de contido con menos permisos. Bloquea anuncios, rastreadores, criptomineiros e mais despois da instalación.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} regras, convertidas dende {{filterCount}} filtros de redes",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Panel de control",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Axustes",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Acerca de",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Política de privacidade",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "modo de filtrado",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Abrir o panel",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Máis",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Menos",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Por defecto",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Anuncios",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacidade",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Molestias",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Varios",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Rexións, linguaxes",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Rexistro de cambios",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Código fonte (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contribúen",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Código fonte",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Traducións",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Listas de filtrado",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Dependencias externas (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Benvida",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Acabas de instalar uBO Lite. Aquí podes escoller o modo de filtrado para usar en todas as páxinas web.\n\nPor defecto, o modo <em>Básico</em> está seleccionado porque non require o permiso de lectura ou modificar datos. Se confías en uBO Lite, podes darlle máis permisos para ler e modificar os datos de todos os sitios web para poder realizar por defecto un filtrado máis preciso en todas as páxinas web.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Modo de filtrado por defecto",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "O modo de filtrado por defecto vaise sobrescribir usando os filtros propios para cada web. Podes axustar o modo de filtrado para calquera web acorde ás túas preferencias para esa web. Cada modo ten as súas vantaxes e inconvintes.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "sen filtrar",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "básico",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "óptimo",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "completo",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Filtrado básico na rede usando listas de filtrado seleccionadas.\n\nNon require permiso de lectura ou modificar datos do sitio web.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Filtrado avanzado da rede e filtrado extendido específico usando listas de filtrado seleccionadas.\n\nRequire permisos máis amplos para ler e modificar datos en todas as webs.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Filtro avanzado na rede e filtrado extenso usando listas xenéricas e específicas para as webs.\n\nRequire permisos máis amplos para ler e modificar datos en todas as webs.\n\nO filtrado extendido xenérico podería facer que aumentasen os recursos que usa a páxina web.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Lista de nomes de host para os que non se fará filtrado",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Comportamento",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Recargar automáticamente a páxina a cambiar o modo de filtrado",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/gu/messages.json b/platform/mv3/extension/_locales/gu/messages.json
new file mode 100644
index 0000000..627857e
--- /dev/null
+++ b/platform/mv3/extension/_locales/gu/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/he/messages.json b/platform/mv3/extension/_locales/he/messages.json
new file mode 100644
index 0000000..3782fdb
--- /dev/null
+++ b/platform/mv3/extension/_locales/he/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "חוסם תוכן ניסיוני, נטול הרשאות. חוסם מודעות, עוקבים, כורים, ועוד מיד עם ההתקנה.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} כללים, הומרו מ־{{filterCount}} מסנני רשת",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "לוח־מחוונים – uBO Lite",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "הגדרות",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "אודות",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "מדיניות פרטיות",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "מצב מסנן",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "פתיחת לוח־המחוונים",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "עוד",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "פחות",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "ברירת מחדל",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "פרסומות",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "פרטיות",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "תחומי תוכנות זדוניות",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "מטרדים",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "שונות",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "אזורים, שפות",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "יומן שינויים",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "קוד מקור (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "תורמים",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "קוד מקור",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "תרגומים",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "רשימות סינון",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "תלויות חיצוניות (תואם GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "ברוך בואך",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "התקנת uBO Lite הסתיימה זה עתה. ניתן לבחור כאן את אופן סינון ברירת המחדל בכל אתרי הרשת.\n\nכברירת מחדל, ייבחר אופן סינון <em>בסיסי</em> מאחר והוא אינו דורש הרשאות קריאה ושינוי נתונים. 'סומכים על uBO Lite?' אם כן, ניתן להעניק ל־uBO Lite הרשאות נרחבות לכתיבה ושינוי נתונים בכלול אתרי הרשת על מנת לאפשר יכולות סינון מתקדמות יותר כבררת מחדל.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "מצב סינון ברירת מחדל",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "אופן סינון ברירת המחדל יעקף על ידי אופני סינון יעודיים לכל אתר. ניתן להתאים את אופן הסינון בכל אתר נתון, לאופן הסינון המיטבי באותו אחר. לכול אופן סינון, יתרונות וחסרונות משלו.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "ללא סינון",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "בסיסי",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "מיטבי",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "מלא",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "סינון רשת בסיסי מרשימות סינון נבחרות.\n\nאינו מצריך הרשאת קריאה ושינוי נתונים באתרי רשת.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "סינון רשת מתקדם פלוס, וסינון נרחב ייחודי מרשימות סינון נבחרות.\n\n מצריך הרשאה לקריאה ושינוי נתונים באתרי רשת.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "סינון רשת מתקדם וסינון נרחב ייחודי מרשימות סינון נבחרות.\n\n מצריך הרשאות נרחבות לקריאה ושינוי נתונים בכל אתרי הרשת.\n\nסינון כללי מורחב עלול לגרום לצריכת משאבי רשת מוגברת.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "רשימה של שמות אתרים שלא יתבצע עליהם סינון",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "התנהגות",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "טעינת עמוד באופן אוטומטי עם שינוי מצב סינון",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/hi/messages.json b/platform/mv3/extension/_locales/hi/messages.json
new file mode 100644
index 0000000..89ed004
--- /dev/null
+++ b/platform/mv3/extension/_locales/hi/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "एक प्रयोगात्मक, अनुमति-रहित सामग्री अवरोधक. इंस्टालेशन के तुरंत बाद विज्ञापनों, ट्रैकर्स, माइनर्स और बहुत कुछ को ब्लॉक कर देता है.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{filterCount}} नेटवर्क फ़िल्टर से रूपांतरित, {{ruleCount}} नियम",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — डैशबोर्ड",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "सेटिंग्स",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "परिचय",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "गोपनीयता नीति",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "फ़िल्टरिंग मोड",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "डैशबोर्ड खोलें",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "अधिक",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "कम",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "डिफॉल्ट",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "विज्ञापन",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "गोपनीयता",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "मैलवेयर डोमेन",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "झुंझलाहटें",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "विविध",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "क्षेत्र, भाषाएँ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "परिवर्तन लॉग",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "सोर्स कोड {GPLv3}",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "योगदानकर्ता",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "सोर्स कोड",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "अनुवाद",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "फिल्टर सूची",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "बाहरी निर्भरता (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "स्वागत",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "आपने अभी-अभी uBO Lite स्थापित किया है. आप यहां सभी वेबसाइटों पर उपयोग करने के लिए डिफ़ॉल्ट फ़िल्टरिंग मोड चुन सकते हैं.\n\nडिफ़ॉल्ट रूप से, <em>बुनियादी</em> मोड चुना जाता है क्योंकि इसमें डेटा को पढ़ने और बदलने की अनुमति की आवश्यकता नहीं होती है. यदि आप uBO Lite पर भरोसा करते हैं, तो आप डिफ़ॉल्ट रूप से सभी वेबसाइटों के लिए अधिक उन्नत फ़िल्टरिंग क्षमताओं को सक्षम करने के लिए सभी वेबसाइटों पर डेटा को पढ़ने और बदलने की व्यापक अनुमति दे सकते हैं.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "डिफ़ॉल्ट फ़िल्टरिंग मोड",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "डिफ़ॉल्ट फ़िल्टरिंग मोड प्रति-वेबसाइट फ़िल्टरिंग मोड द्वारा ओवरराइड किया जाएगा. आप किसी भी वेबसाइट पर फ़िल्टरिंग मोड को उस वेबसाइट पर सबसे अच्छा काम करने वाले मोड के अनुसार समायोजित कर सकते हैं. प्रत्येक मोड के अपने फायदे और नुकसान हैं.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "कोई फ़िल्टर नहीं",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "बुनियादी",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "अनुकूलतम",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "पूर्ण",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "चयनित फ़िल्टर सूचियों में से बुनियादी नेटवर्क फ़िल्टरिंग.\n\nवेबसाइटों पर डेटा पढ़ने और बदलने के लिए अनुमति की आवश्यकता नहीं हौती है.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "चयनित फ़िल्टर सूचियों में से उन्नत नेटवर्क फ़िल्टरिंग के साथ विशिष्ट विस्तारित फ़िल्टरिंग.\n\nसभी वेबसाइटों पर डेटा पढ़ने और बदलने के लिए व्यापक अनुमति की आवश्यकता है.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "चयनित फ़िल्टर सूचियों में से उन्नत नेटवर्क फ़िल्टरिंग के साथ विशिष्ट विस्तारित फ़िल्टरिंग.\n\nसभी वेबसाइटों पर डेटा पढ़ने और बदलने के लिए व्यापक अनुमति की आवश्यकता है.\n\nसामान्य विस्तारित फ़िल्टरिंग के कारण वेबपृष्ठ संसाधनों का अधिक उपयोग हो सकता है.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "होस्टनामों की सूची जिनके लिए कोई फ़िल्टरिंग नहीं होगी",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "व्यवहार",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "फ़िल्टरिंग मोड बदलते समय स्वचालित रूप से पृष्ठ पुनः लोड करें",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/hr/messages.json b/platform/mv3/extension/_locales/hr/messages.json
new file mode 100644
index 0000000..14c9401
--- /dev/null
+++ b/platform/mv3/extension/_locales/hr/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Bloker sadržaja bez dopuštenja. Blokira oglase, oglasne pratitelje, kripto \"rudare\" i ostalo odmah nakon instalacije.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} pravila, pretvoreno iz {{filterCount}} mrežnih filtera",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — nadzorna ploča",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Postavke",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "O aplikaciji",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Pravila privatnosti",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "Način filtriranja",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Otvori nadzornu ploču",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Više",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Manje",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Zadano",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Oglasi",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privatnost",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Zloćudne domene",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Smetnje",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Razno",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regije, jezici",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Popis promjena",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Izvorni kod (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Suradnici",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Izvorni kod",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Prijevodi",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Liste filtera",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Vanjski korišteni programi (GPLv3-kompatiblini):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Dobrodošli",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Upravo ste instalirali uBO Lite. Ovdje možete odabrati zadani način filtriranja za korištenje na svim web stranicama.\n\nPrema zadanim postavkama odabran je način rada <em>Osnovni</em> jer ne zahtijeva dopuštenje za čitanje i promjenu podataka. Ako vjerujete uBO Liteu, možete mu dati široko dopuštenje za čitanje i promjenu podataka na svim web stranicama kako biste prema zadanim postavkama omogućili naprednije mogućnosti filtriranja za sva web mjesta.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Zadani način filtriranja",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Zadani način filtriranja bit će nadjačan načinima filtriranja po web stranici. Možete prilagoditi način filtriranja na bilo kojoj web stranici u skladu s načinom koji najbolje funkcionira na toj web stranici. Svaki način ima svoje prednosti i nedostatke.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "bez filtriranja",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "osnovno",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimalno",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "kompletno",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Osnovno mrežno filtriranje s odabranih popisa filtera.\n\nNe zahtijeva dopuštenje za čitanje i promjenu podataka na web stranicama.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Napredno mrežno filtriranje plus posebno prošireno filtriranje s odabranih popisa filtera.\n\nZahtijeva široko dopuštenje za čitanje i promjenu podataka na svim web stranicama.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Napredno mrežno filtriranje plus specifično i generičko prošireno filtriranje s odabranih popisa filtera.\n\nZahtijeva široko dopuštenje za čitanje i promjenu podataka na svim web stranicama.\n\nGeneričko prošireno filtriranje može uzrokovati veće korištenje resursa web stranice.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Popis naziva hostova za koje se neće izvršiti filtriranje",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Ponašanje",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatski ponovno učitaj stranicu pri promjeni načina filtriranja",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/hu/messages.json b/platform/mv3/extension/_locales/hu/messages.json
new file mode 100644
index 0000000..5fe25d2
--- /dev/null
+++ b/platform/mv3/extension/_locales/hu/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Kísérleti, engedélyt nem igénylő nélküli tartalomblokkoló. A telepítés után azonnal blokkolja a hirdetéseket, nyomkövetőket, bányászokat és egyebeket.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} szabály, {{filterCount}} hálózati szűrőből átalakítva",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Vezérlőpult",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Beállítások",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Névjegy",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Adatvédelmi irányelvek",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "szűrési mód",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Vezérlőpult megnyitása",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Több",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Kevesebb",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Alapértelmezett",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Hirdetések",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Adatvédelem",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domainek",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Bosszúságok",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Egyéb",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Régiók, nyelvek",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Változások listája",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Forráskód (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Közreműködők",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Forráskód",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Fordítások",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Szűrőlisták",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Külső függőségek (GPLv3-kompatibilis):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Üdvözlünk",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Alapértelmezés szerint az <em>Alap</em> mód van kiválasztva, mert nem igényel engedélyt az adatok olvasásához és módosításához. Ha megbízik az uBO Lite-ban, széles körű engedélyt adhat neki az adatok olvasására és módosítására az összes webhelyen, hogy alapértelmezés szerint fejlettebb szűrési lehetőségeket tegyen lehetővé minden webhelyen.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Alapértelmezett szűrő",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Az alapértelmezett szűrési módot a webhelyenkénti szűrési módok felülírják. Bármely webhelyen beállíthatja a szűrési módot aszerint, hogy melyik mód működik a legjobban az adott webhelyen. Mindegyik módnak megvannak a maga előnyei és hátrányai.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "Nincs szűrés",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "alapok",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimális",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "elkészült",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Alapvető hálózati szűrés a kiválasztott szűrőlistákból.\n\nNem igényel engedélyt a webhelyeken található adatok olvasásához és módosításához.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Speciális hálózati szűrés plusz speciális kiterjesztett szűrés a kiválasztott szűrőlistákból.\n\nSzéles körű engedély szükséges az adatok olvasásához és módosításához az összes webhelyen.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Speciális hálózati szűrés, valamint speciális és általános kiterjesztett szűrés a kiválasztott szűrőlistákból.\n\nSzéles körű engedély szükséges az adatok olvasásához és módosításához az összes webhelyen.\n\nAz általános kiterjesztett szűrés nagyobb weboldal-erőforrás-felhasználást eredményezhet.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Azon hosztnevek listája, amelyek esetében nem történik szűrés",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Viselkedés",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Az oldal automatikus újratöltése a szűrési mód megváltoztatásakor",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/hy/messages.json b/platform/mv3/extension/_locales/hy/messages.json
new file mode 100644
index 0000000..50b9b44
--- /dev/null
+++ b/platform/mv3/extension/_locales/hy/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Փորձարարական բովանդակության արգելափակիչ, որը չի պահանջում թույլտվություններ։ Արգելափակում է ազդերը, հետագծիչները, մայներները և շատ ավելին։",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} կանոն, որոնք փոխարկված են {{filterCount}} ցանցային զտիչներից",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Կառավահան",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Կարգավորումներ",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Ընդլայնման մասին",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Գաղտնիության քաղաքականություն",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "զտման ռեժիմ",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Բացել կառավահանը",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Ավելին",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Պակաս",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Լռելյայն",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Գովազդ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Գաղտնիություն",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Վնասակար տիրույթներ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Ջղայնացնող տարրեր",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Տարբեր",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Տարածաշրջաններ, լեզուներ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Փոփոխությունների մատյան",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Աղբյուրի կոդ (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Մասնակիցներ",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Աղբյուրի կոդ",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Թարգմանություն",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Զտիչների ցանկեր",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Արտաքին կախվածություններ (GPLv3-համատեղելի)՝",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Բարի գալուստ",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Դուք հենց նոր տեղադրեցիք uBO Lite-ը։ Այստեղ Դուք կարող եք զտման ստանդարտ ռեժիմ ընտրել բոլոր կայքերի համար։\n\nԼռելյայն կերպով ընտրված է <em>հիմնական</em> ռեժիմը, քանի որ այն չի պահանջում տվյալների ընթերցման և փոփոխման թույլտվություն։ Եթե վստահում եք uBO Lite-ին, կարող եք տալ դրան բոլոր կայքերում տվյալների ընթերցման և փոփոխման լայն իրավունքներ՝ բոլոր կայքերի համար զտման ավելի առաջադեմ հնարավորությունները լռելյայն կերպով միացնելու համար։",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Զտման լռելյայն ռեժիմ",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Զտման լռելյայն ռեժիմը վերասահմանվում է յուրաքանչյուր կայքի զտման ռեժիմներով։ Դուք կարող եք ջոկել ամենաարդյունավետ զտման ռեժիմը ցանկացած կայքում։ Յուրաքանչյուր ռեժիմ ունի իր առավելություններն ու թերությունները։",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "առանց զտման",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "հիմնական",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "գերադասելի",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "ամբողջական",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Ցանցային հիմնական զտում ըստ ընտրված զտիչների ցուցակների։\n\nԿայքերում տվյալները կարդալու և փոփոխելու թույլտվություն չի պահանջում։",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Ընդլայնված ցանցային զտում՝ ընտրված զտիչների ցուցակների համաձայն հատուկ զտման հետ միասին։\n\nՊահանջում է տվյալները բոլոր կայքերում կարդալու և փոփոխելու թույլտվություն։",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Ընդլայնված ցանցային զտում, ինչպես նաև հատուկ և առաջադեմ ընդհանուր զտում ընտրված զտիչների ցանկերի համաձայն։\n\nՊահանջում է բոլոր կայքերում տվյալները կարդալու և փոփոխելու թույլտվություն։\n\nԱռաջադեմ ընդհանուր զտումը կարող է առաջացնել վեբ էջի կողմից ռեսուրսների սպառման ավելացում:",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Հյուրերի անունների ցանկ, որոնց համար զտում չի իրականացվի",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Վարք",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Զտման ռեժիմը փոխելիս ինքնաշխատորեն վերաբեռնել էջը",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/id/messages.json b/platform/mv3/extension/_locales/id/messages.json
new file mode 100644
index 0000000..e3c6f69
--- /dev/null
+++ b/platform/mv3/extension/_locales/id/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Pemblokir konten eksperimental yang cepat dan ringan -- memblokir iklan, pelacak, penambang kripto dan lainnya secara bawaan.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} aturan, dikonversi dari {{filterCount}} filter jaringan",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dasbor",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Pengaturan",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Tentang",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Kebijakan privasi",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "mode filter",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Buka dasbor",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Lainnya",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Lebih sedikit",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Bawaan",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Iklan",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privasi",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Domain malware",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Gangguan",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Lainnya",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Wilayah, bahasa",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Catatan perubahan",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Kode sumber (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Kontributor",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Kode sumber",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Terjemahan",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Daftar filter",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Dependensi eksternal (kompatibel GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Selamat datang",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Terima kasih telah memasang uBO Lite. Anda bisa memilih di sini mode filter bawaan untuk digunakan pada semua situs web.\n\nSecara bawaan, mode <em>Dasar</em> akan dipilih karena tidak membutuhkan izin untuk membaca dan mengubah data. Jika Anda mempercayai uBO Lite, Anda bisa memberikan izin tambahan untuk membaca dan mengubah data pada semua situs web untuk mengaktifkan kemampuan filter yang lebih luas untuk semua situs web secara bawaan.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Mode filter bawaan",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Mode filter bawaan akan ditimpa oleh mode filter per-situs web. Anda bisa menyesuaikan mode filter pada setiap situs web dengan mode yang bekerja paling baik pada situs web tersebut. Masing-masing mode memiliki kelebihan dan kekurangan.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "tanpa filter",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "dasar",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "lengkap",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Filter jaringan dasar dari daftar filter yang dipilih.\n\nTidak membutuhkan izin untuk membaca dan mengubah data pada situs web.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Filter jaringan lanjutan ditambah filter spesifik yang diperluas dari daftar filter yang dipilih.\n\nMembutuhkan banyak izin untuk membaca dan mengubah data pada semua situs web.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Filter jaringan lanjutan ditambah filter spesifik dan umum yang diperluas dari daftar filter yang dipilih.\n\nMembutuhkan banyak izin untuk membaca dan mengubah data pada semua situs web.\n\nFilter umum yang diperluas dapat menyebabkan penggunan sumber daya situs web menjadi lebih tinggi.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Perilaku",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Otomatis memuat ulang halaman web saat mengubah mode filter",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/it/messages.json b/platform/mv3/extension/_locales/it/messages.json
new file mode 100644
index 0000000..04c500e
--- /dev/null
+++ b/platform/mv3/extension/_locales/it/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Un blocco sperimentale che non richiede permessi. Blocca pubblicità, tracker, cryptominer e altro ancora.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} regole, convertite da {{filterCount}} filtri di rete",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Cruscotto",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Opzioni",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Informazioni",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Informativa sulla privacy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "Modalità di filtraggio",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Accedi al cruscotto",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Altro",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Nascondi",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Predefinite",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Pubblicità",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Domini che diffondono virus",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Scocciature",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Generici",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Lingue e regioni",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Registro delle modifiche",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Codice sorgente (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Collaboratori",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Codice sorgente",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Traduzioni",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Elenco filtri",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Dipendenze esterne (in linea con la GPL v3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Ciao",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Hai installato uBO Lite. Scegli il filtro predefinito da usare ovunque.\n\nL'impostazione di fabbrica prevede la modalità <em>Basilare</em> che non richiede alcun tipo di autorizzazione. Puoi decidere di attivare il filtro avanzato se vuoi concedere a uBO Lite l'autorizzazione a rielaborare i dati di tutti i siti web che visiti, in modo da avere un sistema di filtraggio con altre funzioni in più.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Filtro predefinito",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Il filtraggio predefinito può essere sostituito dal filtraggio personalizzato. Puoi regolare il filtraggio per ogni singolo sito web al fine di ottenere il risultato migliore. Ognuno dei filtraggi presenta vantaggi e svantaggi.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "nessun filtraggio",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "di base",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "ottimale",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "completo",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Filtro di rete basilare basato su un elenco selezionato di filtri.\n\nNon servono autorizzazioni per rielaborare i dati dei siti web.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Filtro di rete avanzato in aggiunta al filtro avanzato basato su un elenco selezionato di filtri.\n\nRichiede delle autorizzazioni specifiche per rielaborare i dati dei siti web.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Filtro avanzato di rete in aggiunta al filtro esteso basato su un elenco selezionato di filtri.\n\nRichiede autorizzazioni estese per leggere e modificare i dati da ogni sito web.\n\nIl filtro esteso richiede più memoria e un maggiore impegno del processore.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Elenco degli host per i quali non viene effettuato nessun filtraggio",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Comportamento",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Ricarica la pagina quando scegli un altro metodo di filtraggio",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/ja/messages.json b/platform/mv3/extension/_locales/ja/messages.json
new file mode 100644
index 0000000..fe679dc
--- /dev/null
+++ b/platform/mv3/extension/_locales/ja/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "実験的で要求権限の少ないコンテンツブロッカー - 広告・トラッカー・マイニングスクリプトなどをブロック",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} 件のルール ({{filterCount}} 件のネットワークフィルターから変換)",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — ダッシュボード",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "設定",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "uBO Lite について",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "プライバシーポリシー",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "フィルタリングモード",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "ダッシュボードを開く",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "さらに表示",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "折りたたむ",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "既定",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "広告",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "プライバシー",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "マルウェアドメイン",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "迷惑系",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "その他",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "地域・言語",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "更新履歴",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "ソースコード (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "貢献者",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "ソースコード",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "翻訳",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "フィルターリスト",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "外部依存関係 (GPLv3 と両立):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "ようこそ",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "uBO Lite のインストールが完了しました。すべてのサイトに適用するデフォルトのフィルタリングモードを選んでください。\n\nデフォルトでは、データの読み書きの権限が必要ない<em>基本</em>モードが選択されています。 uBO Lite を信用してもらえるなら、データの読み書きや変更の権限を許可してもらえればすべてのサイトに対してより詳細なフィルタリングを有効化できます。",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "デフォルトのフィルタリングモード",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "デフォルトのフィルタリングモードは、サイト毎のフィルタリングモードで上書きされます。サイトにぴったりなモードにいつでも好きなように調整できます。どのモードも一長一短です。",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "フィルタリングなし",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "基本",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "最適",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "完全",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "特定のフィルターリストを使った基本ネットワークフィルタリングです。\n\nウェブサイトのデータの読み書き権限を必要としません。",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "特定のフィルターリストを使ったネットワークフィルタリングとサイト別の拡張フィルタリングです。\n\nすべてのウェブサイトのデータの読み書き権限を必要とします。",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "特定のフィルターリストを使ったネットワークフィルタリングと一般的・サイト別の拡張フィルタリングです。\n\nすべてのウェブサイトのデータの読み書き権限を必要とします。\n\n一般的な拡張フィルタリングを使うとリソース消費量が増えるかもしれません。",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "フィルタリングを行わないホスト名のリスト",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "動作",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "フィルタリングモード変更時にページを自動再読み込みする",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/ka/messages.json b/platform/mv3/extension/_locales/ka/messages.json
new file mode 100644
index 0000000..298a736
--- /dev/null
+++ b/platform/mv3/extension/_locales/ka/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "საცდელი, ნებართვებისგან თავისუფალი მსუბუქი შემზღუდავი -- თავისთავად აცილებს რეკლამებს, მეთვალყურეებს, კრიპტოგამომმუშავებლებს და ა.შ.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} წესი, შედგენილი {{filterCount}} ქსელის ფილტრიდან",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — მაჩვენებლები",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "პარამეტრები",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "შესახებ",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "პირადულობის დებულება",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "გაფილტვრის რეჟიმი",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "სამართავის გახსნა",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "ვრცლად",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "მოკლედ",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "ნაგულისხმევი",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "რეკლამები",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "პირადულობა",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "მავნე დომენები",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "შემაწუხებელი შიგთავსი",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "სხვადასხვა",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "მხარეები, ენები",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "ცვლილებები",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "წყაროს კოდი (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "მოხალისეები",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "პირველწყარო კოდი",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "თარგმანები",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "ფილტრების სიები",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "ცალკეული დაქვემდებარებული პროექტები (GPLv3-თან თავსებადი):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "მოგესალმებით",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "თქვენ ახლახან დააყენეთ uBO Lite. აქვე შეგიძლიათ აირჩიოთ ფილტრაციის ნაგულისხმევი რეჟიმი ყველა საიტზე გამოსაყენებლად.\n\nნაგულისხმევად <em>ძირითადი</em> რეჟიმია შერჩეული, ვინაიდან არ საჭიროებს მონაცემთა წაკითხვისა და ცვლილების ნებართვებს. თუ თქვენთვის სანდოა uBO Lite, შეგიძლიათ დართოთ მონაცემთა წაკითხვისა და შეცვლის ნება ყველა საიტზე გაუმჯობესებული ფილტრაციის ნაგულისხმევად გამოყენებისთვის.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "გაფილტვრის ნაგულისხმევი რეჟიმი",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "ფილტრაციის ნაგულისხმევი რეჟიმი ჩაანაცვლებს ცალკეულ საიტზე მითითებულს. ფილტრაციის რეჟიმის მორგება შეგიძლიათ ნებისმიერ სასურველ საიტზე იმისდა მიხედვით, რა უფრო გამოსადეგი იქნება თქვენთვის იმ საიტზე. თითოეულ რეჟიმს თავისი დადებითი და უარყოფითი მხარეები აქვს.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "ფილტრის გარეშე",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "ძირითადი",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "წონასწორული",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "სრული",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "ქსელის ძირითადი ფილტრაცია შერჩეული სიებით.\n\nარ საჭიროებს საიტებზე მონაცემთა წაკითხვისა და ცვლილების ნებართვებს.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "ქსელის გაუმჯობესებულ ფილტრაციასთან ერთად გაფართოებული ფილტრები შერჩეული სიებიდან.\n\nსაჭიროებს ყველა საიტზე მონაცემთა სრულად წაკითხვისა და შეცვლის ნებართვას.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "ქსელის გაუმჯობესებულ ფილტრაციასთან ერთად საერთო გაფართოებული ფილტრები შერჩეული სიებიდან.\n\nსაჭიროებს ყველა საიტზე მონაცემთა სრულად წაკითხვისა და შეცვლის ნებართვას.\n\nგაფართოებული ფილტრაცია ზოგჯერ ზედმეტად ზრდის დატვირთვას ვებგვერდის მონახულებისას.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "სია მისამართებისა, რომლებზეც ფილტრები არ იმოქმედებს",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "მოქმედება",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "გვერდის ავტომატური განახლება ფილტრაციის რეჟიმის შეცვლისას",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/kk/messages.json b/platform/mv3/extension/_locales/kk/messages.json
new file mode 100644
index 0000000..627857e
--- /dev/null
+++ b/platform/mv3/extension/_locales/kk/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/kn/messages.json b/platform/mv3/extension/_locales/kn/messages.json
new file mode 100644
index 0000000..627857e
--- /dev/null
+++ b/platform/mv3/extension/_locales/kn/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/ko/messages.json b/platform/mv3/extension/_locales/ko/messages.json
new file mode 100644
index 0000000..5942fce
--- /dev/null
+++ b/platform/mv3/extension/_locales/ko/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "실험적이고 권한이 적은 콘텐츠 차단기. 광고, 추적기, 채굴기 등을 설치 직후 차단합니다.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "네트워크 필터 {{filterCount}}개로부터 변환된 규칙 {{ruleCount}}개",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — 대시보드",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "설정",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "정보",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "개인정보취급방침",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "필터링 모드",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "대시보드 열기",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "자세히",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "간단히",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "기본값",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "광고",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "개인 정보 보호",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "멀웨어 도메인",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "방해 요소",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "기타",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "지역, 언어",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "변경 로그",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "소스 코드 (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "기여자들",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "소스 코드",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "번역",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "필터 리스트",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "외부 의존성 (GPLv3 호환):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "환영합니다",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "uBO Lite를 설치하셨습니다. 모든 웹사이트에 적용할 기본 필터링 모드를 선택해주세요.\n\n데이터를 읽고 변경하는 권한이 필요 없는 <em>기본</em> 모드가 기본 설정입니다. uBO Lite를 신뢰하신다면, 모든 웹사이트에서 데이터를 읽고 변경하는 권한을 부여해서 모든 웹사이트에 대해 고급 필터링 기능을 기본적으로 켤 수 있습니다.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "기본 필터링 모드",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "웹사이트별 필터링 모드가 있다면 기본 필터링 모드를 덮어씁니다. 각 웹사이트에서 가장 잘 작동하는 필터링 모드로 조정할 수 있습니다. 각 모드에는 장단점이 있습니다.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "필터링 없음",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "기본",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "최적",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "완전",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "선택한 필터 목록을 바탕으로 기본적인 네트워크 필터링을 수행합니다.\n\n웹사이트의 데이터를 읽고 수정하는 권한이 필요하지 않습니다.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "고급 네트워크 필터링과 선택한 필터 목록을 바탕으로 특정한 확장 필터링을 수행합니다.\n\n모든 웹사이트에서 데이터를 읽고 쓸 수 있도록 하는 광범위한 권한이 필요합니다.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "고급 네트워크 필터링과 선택한 필터 목록을 바탕으로 특정한 확장 필터링 및 보편적인 확장 필터링을 수행합니다.\n\n모든 웹사이트에서 데이터를 읽고 쓸 수 있도록 하는 광범위한 권한이 필요합니다.\n\n보편적인 확장 필터링 기능을 이용하는 경우 웹페이지의 리소스 사용량이 증가할 수 있습니다.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "필터링을 비활성화할 호스트 이름 목록",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "동작",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "필터링 모드를 변경할 때 페이지 자동 새로고침",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/lt/messages.json b/platform/mv3/extension/_locales/lt/messages.json
new file mode 100644
index 0000000..0377c08
--- /dev/null
+++ b/platform/mv3/extension/_locales/lt/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Skydelis",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Nustatymai",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Apie",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privatumo politika",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Daugiau",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Mažiau",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklamos",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privatumas",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regionai, kalbos",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Pakeitimų žurnalas",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Programinis kodas",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Vertimai",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filtrų sąrašai",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Išorinės priklausomybės (suderinamos su „GPLv3“):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatiškai perkrauti sveitane keičiant filtro rėžimą",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/lv/messages.json b/platform/mv3/extension/_locales/lv/messages.json
new file mode 100644
index 0000000..bea58f2
--- /dev/null
+++ b/platform/mv3/extension/_locales/lv/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Izmēģinājuma, bezatļauju satura aizturētājs. Aiztur reklāmas, izsekotājus, kriptoracējus un vēl uzreiz pēc uzstādīšanas.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} nosacījumi, pārveidoti no {{filterCount}} tīkla aizturētājiem",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — infopanelis",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Iestatījumi",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Par",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privātuma nosacījumi",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "aizturēšanas veids",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Atvērt vadības paneli",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Vairāk",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Mazāk",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Noklusējums",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklāmas",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privātums",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Ļaunatūras domēni",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Traucējumi",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Dažādi",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Apgabali, valodas",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Izmaiņu žurnāls",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Pirmkods (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Līdzdalībnieki",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Pirmkods",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Tulkojumi",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Aizturēšanas saraksti",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Ārējās atkarības (GPLv3 saderīgas):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Sveicināti!",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Tikko ir uzstādīts uBO Lite. Šeit var izvēlēties noklusējuma aizturēšanas veidu, ko izmantot visām tīmekļa vietnēm.\n\nPēc noklusējuma ir atlasīts <em>Pamata</em>, jo tam nav nepieciešama atļauja lasīt un mainīt datus. Ja uBO Lite šķiet uzticams, ir iespējams piešķirt plašas atļaujas lasīt un mainīt datus visās tīmekļa vietnēs, lai pēc noklusējuma iespējotu pilnīgākas aizturēšanas spējas visās tīmekļa vietnēs.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Noklusējuma aizturēšanas veids",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Tīmekļa vietnes aizturēšanas veidi pārrakstīs noklusējumu. Katrā tīmekļa vietnē ir iespēja pielāgot aizturēšanu, vadoties pēc tā, kurš no veidiem vislabāk darbojas tieši tajā lapā. Katram veidam ir savas priekšrocības un trūkumi.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "bez aizturēšanas",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "pamata",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "labākais",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "pilnīgais",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Pamata tīkla aizturēšana, izmantojot atlasītos aizturēšanas sarakstus.\n\nNav nepieciešama atļauja lasīt un mainīt datus tīmekļa vietnēs.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Attīstītāka tīkla aizturēšana ar atsevišķu paplašinātu aizturēšanu, izmantojot atlasītos aizturēšanas sarakstus.\n\nNepieciešamas plašas atļaujas lasīt un mainīt visu tīmekļa vietņu datus.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Attīstītāka tīkla aizturēšana ar pamata un papildu paplašinātu aizturēšanu, izmantojot atlasītos aizturēšanas sarakstus.\n\nNepieciešamas plašas atļaujas lasīt un mainīt visu tīmekļa vietņu datus.\n\nPamata paplašinātā aizturēšana var izraisīt paaugstinātu tīmekļa vietnes resursu izmantošanu.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Saraksts ar saimniekdatoru nosaukumiem, kuriem netiks pielietota aizturēšana",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Uzvedība",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Pārlādēt lapu pēc aizturēšanas veida nomaiņas.",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/mk/messages.json b/platform/mv3/extension/_locales/mk/messages.json
new file mode 100644
index 0000000..0ef10c7
--- /dev/null
+++ b/platform/mv3/extension/_locales/mk/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} правила, добиени од {{filterCount}} мрежните филтри",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Контролна плоча",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Прилагодби",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Информации за...",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Заштита на личните податоци",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "Начини на прочистување",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Отварање на Контролна Табла",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Повеќе",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Почетни прилагодби",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Рекламни огласи",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Приватност",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Разно",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Региони, Јазици",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/ml/messages.json b/platform/mv3/extension/_locales/ml/messages.json
new file mode 100644
index 0000000..d272672
--- /dev/null
+++ b/platform/mv3/extension/_locales/ml/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "അനുമതി-കുറവ് ഉള്ളടക്ക ബ്ലോക്കർ. പരസ്യങ്ങൾ, ട്രാക്കറുകൾ, ക്രിപ്‌റ്റോ-മൈനർ എന്നിവയും മറ്റും ഇൻസ്റ്റാളുചെയ്യുമ്പോൾ ഉടനടി തടയുന്നു.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} നിയമങ്ങൾ, {{filterCount}} നെറ്റ്‌വർക്ക് ഫിൽട്ടറുകളിൽ നിന്ന് പരിവർത്തനം ചെയ്‌തു",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO ലൈറ്റ് - ഡാഷ്ബോർഡ്",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "ക്രമീകരണങ്ങൾ",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "കുറിച്ച്",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "സ്വകാര്യതാ നയം",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "ഫിൽട്ടറിംഗ് മോഡ്",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "ഡാഷ്ബോർഡ് തുറക്കുക",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "കൂടുതൽ",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "കുറവ്",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "സ്ഥിരസ്ഥിതി",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "പരസ്യങ്ങള്‍",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "പ്രൈവസി",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "ശല്യപ്പെടുത്തലുകൾ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "പലവക ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "പ്രാദേശികം, ഭാഷകള്‍",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "ചേഞ്ച് ലോഗ്",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "സോര്‍സ് കോഡ് (ജിപിഎല്‍ വി3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "സോഴ്സ് കോഡ്",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "ഫിൽട്ടർ ലിസ്റ്റ്",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "ബാഹ്യ ഡിപൻഡൻസികൾ (ജിപിൽവി3-അനുയോജ്യമായത്):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "സ്വാഗതം",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "നിങ്ങൾ ഇപ്പോൾ uBO Lite ഇൻസ്റ്റാൾ ചെയ്തു. എല്ലാ വെബ്‌സൈറ്റുകളിലും ഉപയോഗിക്കുന്നതിന് ഇവിടെ നിങ്ങൾക്ക് ഡിഫോൾട്ട് ഫിൽട്ടറിംഗ് മോഡ് തിരഞ്ഞെടുക്കാം.\n\nസ്ഥിരസ്ഥിതിയായി, <em>അടിസ്ഥാന</em> മോഡ് തിരഞ്ഞെടുത്തു, കാരണം ഡാറ്റ വായിക്കാനും പരിഷ്ക്കരിക്കാനും അനുമതി ആവശ്യമില്ല. നിങ്ങൾ uBO Lite-നെ വിശ്വസിക്കുന്നുവെങ്കിൽ, ഡിഫോൾട്ടായി എല്ലാ വെബ്‌സൈറ്റുകൾക്കും കൂടുതൽ വിപുലമായ ഫിൽട്ടറിംഗ് കഴിവുകൾ പ്രാപ്‌തമാക്കുന്നതിന് എല്ലാ വെബ്‌സൈറ്റുകളിലെയും ഡാറ്റ വായിക്കാനും പരിഷ്‌ക്കരിക്കാനും നിങ്ങൾക്ക് വിശാലമായ അനുമതി നൽകാം.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "ഡിഫോൾട്ട് ഫിൽട്ടറിംഗ് മോഡ്",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "ഓരോ വെബ്‌സൈറ്റിലും ഫിൽട്ടറിംഗ് മോഡുകൾ ഉപയോഗിച്ച് ഡിഫോൾട്ട് ഫിൽട്ടറിംഗ് മോഡ് അസാധുവാക്കപ്പെടും. ഏത് വെബ്‌സൈറ്റിൽ ഏറ്റവും മികച്ച രീതിയിൽ പ്രവർത്തിക്കുന്ന മോഡ് അനുസരിച്ച് നിങ്ങൾക്ക് ഏത് വെബ്‌സൈറ്റിലും ഫിൽട്ടറിംഗ് മോഡ് ക്രമീകരിക്കാൻ കഴിയും. ഓരോ മോഡിനും അതിന്റെ ഗുണങ്ങളും ദോഷങ്ങളുമുണ്ട്.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "ഫിൽട്ടറിംഗ് ഇല്ല",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "അടിസ്ഥാനം",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "ഒപ്റ്റിമൽ",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "പൂർണ്ണമായ",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "തിരഞ്ഞെടുത്ത ഫിൽട്ടർ ലിസ്റ്റുകളിൽ നിന്നുള്ള അടിസ്ഥാന നെറ്റ്‌വർക്ക് ഫിൽട്ടറിംഗ്.\n\nവെബ്‌സൈറ്റുകളിലെ ഡാറ്റ വായിക്കാനും പരിഷ്‌ക്കരിക്കാനും അനുമതി ആവശ്യമില്ല.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "തിരഞ്ഞെടുത്ത ഫിൽട്ടർ ലിസ്റ്റുകളിൽ നിന്നുള്ള വിപുലമായ നെറ്റ്‌വർക്ക് ഫിൽട്ടറിംഗും പ്രത്യേക വിപുലീകൃത ഫിൽട്ടറിംഗും.\n\nഎല്ലാ വെബ്‌സൈറ്റുകളിലെയും ഡാറ്റ വായിക്കാനും പരിഷ്‌ക്കരിക്കാനും വിശാലമായ അനുമതി ആവശ്യമാണ്.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "തിരഞ്ഞെടുത്ത ഫിൽട്ടർ ലിസ്റ്റുകളിൽ നിന്നുള്ള വിപുലമായ നെറ്റ്‌വർക്ക് ഫിൽട്ടറിംഗും നിർദ്ദിഷ്ടവും പൊതുവായതുമായ വിപുലീകൃത ഫിൽട്ടറിംഗും.\n\nഎല്ലാ വെബ്‌സൈറ്റുകളിലെയും ഡാറ്റ വായിക്കാനും പരിഷ്‌ക്കരിക്കാനും വിശാലമായ അനുമതി ആവശ്യമാണ്.\n\nപൊതുവായ വിപുലീകൃത ഫിൽട്ടറിംഗ് ഉയർന്ന വെബ്‌പേജ് ഉറവിട ഉപയോഗത്തിന് കാരണമായേക്കാം.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "ഫിൽട്ടറിംഗ് നടക്കാത്ത ഹോസ്റ്റ് നെയിമുകളുടെ ലിസ്റ്റ്",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "പെരുമാറ്റം",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "ഫിൽട്ടറിംഗ് മോഡ് മാറ്റുമ്പോൾ പേജ് സ്വയമേവ റീലോഡ് ചെയ്യുക",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/mr/messages.json b/platform/mv3/extension/_locales/mr/messages.json
new file mode 100644
index 0000000..627857e
--- /dev/null
+++ b/platform/mv3/extension/_locales/mr/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/ms/messages.json b/platform/mv3/extension/_locales/ms/messages.json
new file mode 100644
index 0000000..2128e9b
--- /dev/null
+++ b/platform/mv3/extension/_locales/ms/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Tetapan",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Mengenai",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Dasar privasi",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Lagi",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Kurangkan",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Lalai",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Iklan",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privasi",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Pelbagai",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Wilayah, bahasa",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Log perubahan",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Kod sumber (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Penyumbang",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Kod sumber",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Terjemahan",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Senarai penapis",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Pergantungan luaran (serasi dengan GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Selamat datang",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": " tiada penapisan",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "asas",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optima",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "selesai",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Tingkah laku",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/nb/messages.json b/platform/mv3/extension/_locales/nb/messages.json
new file mode 100644
index 0000000..5c47f65
--- /dev/null
+++ b/platform/mv3/extension/_locales/nb/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "En tillatelsesbegrenset innholdsblokkerer. Blokkerer reklame, sporere, minere og mer umiddelbart etter installering.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} regler, konvertert fra {{filterCount}} nettverksfiltre",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashbord",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Innstillinger",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Om",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Personvernpraksis",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtreringsmodus",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Åpne dashbordet",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Mer",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Mindre",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Standard",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklame",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Personvern",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Domener med skadelig programvare",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Irritasjonsmomenter",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Diverse",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regioner, språk",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Endringslogg",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Kildekode (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Bidragsytere",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Kildekode",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Oversettelser",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filterlister",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Eksterne avhengigheter (GPLv3-kompatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Velkommen",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Du har nettopp installert uBO Lite. Her kan du velge standard-filtreringsmodusen som skal brukes på alle nettsteder.\n\nSom standard er modusen <em>Grunnleggende</em> valgt fordi den ikke krever tillatelse til å lese og endre data. Hvis du stoler på uBO Lite, kan du gi uBO Lite bred tillatelse til å lese og endre data på alle nettsteder for å aktivere mer avanserte filtreringsfunksjoner for alle nettsteder som standard.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Standard filtreringsmodus",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Standard-filtreringsmodusen vil bli overstyrt av per-nettsted filtreringsmoduser. Du kan justere filtreringsmodusen på ethvert gitt nettsted etter hvilken modus som virker best på det nettstedet. Hver modus har sine fordeler og ulemper.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "ingen filtrering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "grunnleggende",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "fullstendig",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Grunnleggende nettverksfiltrering fra valgte filterlister.\n\nKrever ikke tillatelse til å lese og endre data på nettsteder.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Avansert nettverksfiltrering pluss spesifikk utvidet filtrering fra valgte filterlister.\n\nKrever bred tillatelse til å lese og endre data på alle nettsteder.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Avansert nettverksfiltrering pluss spesifikk og generell utvidet filtrering fra valgte filterlister.\n\nKrever bred tillatelse til å lese og endre data på alle nettsteder.\n\nGenerell utvidet filtrering kan forårsake høyere ressursbruk på nettsider.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Liste over vertsnavn der ingen filtrering vil finne sted",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Virkemåte",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatisk last side på nytt ved endring av filtreringsmodus",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/nl/messages.json b/platform/mv3/extension/_locales/nl/messages.json
new file mode 100644
index 0000000..b83eeae
--- /dev/null
+++ b/platform/mv3/extension/_locales/nl/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Een toestemmingsloze inhoudsblokkeerder. Blokkeert direct na installatie advertenties, trackers, miners en meer.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} regels, geconverteerd uit {{filterCount}} netwerkfilters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite – Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Instellingen",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Over",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacybeleid",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtermodus",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Dashboard openen",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Meer",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Minder",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Standaard",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Advertenties",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malwaredomeinen",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Storende elementen",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Diversen",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Gebieden, talen",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Wijzigingenlogboek",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Broncode (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Medewerkers",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Broncode",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Vertalingen",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filterlijsten",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Externe afhankelijkheden (GPLv3-compatibel):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welkom",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "U hebt zojuist uBO Lite geïnstalleerd. Hier kunt u de standaard filtermodus voor het gebruik op alle websites kiezen.\n\nStandaard wordt de modus <em>Basis</em> geselecteerd, omdat hiervoor geen toestemming voor het lezen en aanpassen van gegevens is vereist. Als u uBO Lite vertrouwt, kunt u het brede toestemming voor het lezen en aanpassen van gegevens op alle websites verlenen, zodat standaard meer geavanceerde filtermogelijkheden voor alle websites beschikbaar zijn.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Standaard filtermodus",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Filtermodi per website hebben voorrang op de standaard filtermodus. U kunt de filtermodus op een gewenste website aanpassen naar de modus die op die website het beste werkt. Elke modus heeft zijn voor- en nadelen.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "geen filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basis",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimaal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "volledig",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basale netwerkfiltering vanuit geselecteerde filterlijsten.\n\nVereist geen toestemming voor het lezen en aanpassen van gegevens op websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Geavanceerde netwerkfiltering plus specifieke uitgebreide filtering vanuit geselecteerde filterlijsten.\n\nVereist brede toestemming voor het lezen en aanpassen van gegevens op alle websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Geavanceerde netwerkfiltering plus specifieke en algemene uitgebreide filtering vanuit geselecteerde filterlijsten.\n\nVereist brede toestemming voor het lezen en aanpassen van gegevens op alle websites.\n\nAlgemene uitgebreide filtering kan een hoger gebruik van webpaginabronnen veroorzaken.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Lijst van hostnamen waarvoor geen filtering plaatsvindt.",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Gedrag",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Pagina automatisch vernieuwen bij wijzigen van filtermodus",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/oc/messages.json b/platform/mv3/extension/_locales/oc/messages.json
new file mode 100644
index 0000000..627857e
--- /dev/null
+++ b/platform/mv3/extension/_locales/oc/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/pa/messages.json b/platform/mv3/extension/_locales/pa/messages.json
new file mode 100644
index 0000000..753c355
--- /dev/null
+++ b/platform/mv3/extension/_locales/pa/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "ਮਨਜ਼ੂਰੀਆਂ ਤੋਂ ਬਿਨਾਂ ਵਾਲਾ ਸਮੱਗਰੀ ਬਲਾਕਰ ਹੈ। ਇਸ਼ਤਿਹਾਰ, ਟਰੈਕਰਾਂ, ਮਾਈਨਰਾਂ ਅਤੇ ਹੋਰਾਂ ਉੱਤੇ ਇੰਸਟਾਲ ਕਰਨ ਤੋਂ ਫ਼ੌਰਨ ਬਾਅਦ ਪਾਬੰਦੀ ਲਾਉਂਦਾ ਹੈ।",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} ਨਿਯਮ, {{filterCount}} ਨੈੱਟਵਰਕ ਫਿਲਟਰ ਤੋਂ ਬਦਲੇ ਗਏ",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — ਡੈਸ਼ਬੋਰਡ",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "ਸੈਟਿੰਗਾਂ",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "ਇਸ ਬਾਰੇ",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "ਪਰਦੇਦਾਰੀ ਨੀਤੀ",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "ਫਿਲਟਰ ਕਰਨ ਦਾ ਮੋਡ",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "ਡੈਸ਼ਬੋਰਡ ਨੂੰ ਖੋਲ੍ਹੋ",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "ਹੋਰ",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "ਘੱਟ",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "ਮੂਲ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "ਇਸ਼ਤਿਹਾਰ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "ਪਰਦੇਦਾਰੀ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "ਅਣਚਾਹੇ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "ਫੁਟਕਲ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "ਖੇਤਰ, ਭਾਸ਼ਾਵਾਂ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "ਤਬਦੀਲੀ-ਸੂਚੀ",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "ਸਰੋਤ ਕੋਡ (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "ਯੋਗਦਾਨੀ",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "ਸਰੋਤ ਕੋਡ",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "ਅਨੁਵਾਦ",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "ਫਿਲਟਰ ਸੂਚੀਆਂ",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "ਬਾਹਰੀ ਨਿਰਭਰਤਾਵਾਂ (GPLv3-ਅਨੁਕੂਲ):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "ਜੀ ਆਇਆਂ ਨੂੰ",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "ਤੁਸੀਂ ਹੁਣੇ ਹੀ uBO Lite ਨੂੰ ਇੰਸਟਾਲ ਕੀਤਾ ਹੈ। ਤੁਸੀਂ ਸਾਰੀਆਂ ਵੈੱਬਸਾਈਟਾਂ ਲਈ ਵਰਤਣ ਵਾਸਤੇ ਮੂਲ ਫਿਲਟਰ ਕਰਨ ਦੇ ਢੰਗ ਨੂੰ ਚੁਣ ਸਕਦੇ ਹੋ।\n\nਮੂਲ ਰੂਪ ਵਿੱਚ <em>ਮੁੱਢਲਾ (Basic)</em> ਢੰਗ ਚੁਣਿਆ ਜਾਂਦਾ ਹੈ, ਕਿਉਂਕਿ ਇਸ ਵਾਸਤੇ ਡਾਟਾ ਪੜ੍ਹਨ ਅਤੇ ਸੋਧਣ ਲਈ ਕਿਸੇ ਵੀ ਮਨਜ਼ੂਰੀ ਦੀ ਲੋੜ ਨਹੀਂ ਹੁੰਦੀ ਹੈ। ਜੇ ਤੁਹਾਨੂੰ uBO Lite ਉੱਤੇ ਭਰੋਸਾ ਹੋਵੇ ਤਾਂ ਤੁਸੀਂ ਇਸ ਨੂੰ ਸਾਰੀਆਂ ਵੈੱਬਸਾਈਟਾਂ ਉੱਤੇ ਮੂਲ ਰੂਪ ਵਿੱਚ ਹੀ ਵੱਧ ਤਕਨੀਕੀ ਫਿਲਟਰ ਸਮਰੱਥਾ ਨੂੰ ਚਾਲੂ ਕਰਨ ਲਈ ਸਭ ਵੈੱਬਸਾਈਟਾਂ ਉੱਤੇ ਡਾਟਾ ਪੜ੍ਹਨ ਅਤੇ ਸੋਧਣ ਲਈ ਜਿਆਦਾ ਮਨਜ਼ੂਰੀ ਦੇ ਸਕਦੇ ਹੋ।",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "ਮੂਲ ਫਿਲਟਰ ਕਰਨ ਦਾ ਢੰਗ",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "ਮੂਲ ਫਿਲਟਰਿੰਗ ਢੰਗ ਨੂੰ ਹਰ-ਵੈੱਬਸਾਈਟ ਫਿਲਟਰਿੰਗ ਢੰਗਾਂ ਰਾਹੀਂ ਅਣਡਿੱਠਾ ਕੀਤਾ ਜਾਵੇਗਾ। ਤੁਸੀਂ ਕਿਸੇ ਵੀ ਵੈੱਬਸਾਈਟ ਲਈ ਫਿਲਟਰ ਕਰਨ ਦੇ ਢੰਗ ਨੂੰ ਉਸ ਵੈੱਬਸਾਈਟ ਲਈ ਸਭ ਤੋਂ ਵਧੀਆ ਕੰਮ ਕਰਦੇ ਢੰਗ ਮੁਤਾਬਕ ਅਡਜੱਸਟ ਕਰ ਸਕਦੇ ਹੋ। ਹਰ ਢੰਗ ਦੇ ਆਪਣੇ ਫਾਇਦੇ ਅਤੇ ਆਪਣੇ ਨੁਕਸਾਨ ਹਨ।",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "ਕੋਈ ਫਿਲਟਰ ਨਹੀਂ ਕਰਨਾ",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "ਮੁੱਢਲਾ",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "ਅਨੁਕੂਲ",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "ਪੂਰਾ",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "ਚੁਣੀਆਂ ਫਿਲਟਰ ਸੂਚੀਆਂ ਤੋਂ ਮੁੱਢਲਾ ਨੈੱਟਵਰਕ ਫਿਲਟਰ ਕਰਨਾ।\n\nਵੈੱਬਸਾਈਟਾਂ ਤੋਂ ਡਾਟਾ ਪੜ੍ਹਨ ਅਤੇ ਸੋਧਣ ਲਈ ਮਨਜ਼ੂਰੀ ਨਹੀਂ ਚਾਹੀਦੀ ਹੈ।",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "ਤਕਨੀਕੀ ਨੈੱਟਵਰਕ ਫਿਲਟਰ ਕਰਨ ਤੋਂ ਨਾਲ ਨਾਲ ਚੁਣੀਆਂ ਫਿਲਟਰ ਸੂਚੀਆਂ ਤੋਂ ਖਾਸ ਵਾਧਾ ਕੀਤੇ ਫਿਲਟਰ।\n\nਸਾਰੀਆਂ ਵੈੱਬਸਾਈਟਾਂ ਲਈ ਡਾਟਾ ਪੜ੍ਹਨ ਅਤੇ ਸੋਧਣ ਲਈ ਜਿਆਦਾ ਮਨਜ਼ੂਰੀਆਂ ਚਾਹੀਦੀਆਂ ਹਨ।",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "ਤਕਨੀਕੀ ਨੈੱਟਵਰਕ ਫਿਲਟਰ ਕਰਨ ਤੋਂ ਨਾਲ ਨਾਲ ਚੁਣੀਆਂ ਫਿਲਟਰ ਸੂਚੀਆਂ ਤੋਂ ਖਾਸ ਵਾਧਾ ਕੀਤੇ ਫਿਲਟਰ।\n\nਸਾਰੀਆਂ ਵੈੱਬਸਾਈਟਾਂ ਲਈ ਡਾਟਾ ਪੜ੍ਹਨ ਅਤੇ ਸੋਧਣ ਲਈ ਜਿਆਦਾ ਮਨਜ਼ੂਰੀਆਂ ਚਾਹੀਦੀਆਂ ਹਨ।\n\nਆਮ ਵਾਧਾ ਕੀਤੀ ਫਿਲਟਰਿੰਗ ਵੱਧ ਵੈੱਬ-ਸਫ਼ਾ ਸਰੋਤ ਵਰਤੇ ਜਾਣ ਦਾ ਕਾਰਨ ਬਣ ਸਕਦੀ ਹੈ।",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "ਹੋਸਟ-ਨਾਵਾਂ ਦੀ ਸੂਚੀ, ਜਿਨ੍ਹਾਂ ਲਈ ਕੋਈ ਫਿਲਟਰ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "ਰਵੱਈਆ",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "ਫਿਲਫਰ ਕਰਨ ਦਾ ਢੰਗ ਬਦਲਣ ਦੇ ਬਾਅਦ ਸਫ਼ੇ ਨੂੰ ਆਪਣੇ-ਆਪ ਲੋਡ ਕਰੋ",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/pl/messages.json b/platform/mv3/extension/_locales/pl/messages.json
new file mode 100644
index 0000000..07c7543
--- /dev/null
+++ b/platform/mv3/extension/_locales/pl/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Eksperymentalny, niewymagający uprawnień bloker treści. Natychmiast po instalacji blokuje reklamy, moduły śledzące, koparki i nie tylko.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} reguł, przekonwertowanych z {{filterCount}} filtrów sieciowych",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Panel sterowania",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Ustawienia",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "O rozszerzeniu",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Polityka prywatności",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "Tryb filtrowania",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Otwórz panel sterowania",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Więcej",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Mniej",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Domyślne",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklamy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Prywatność",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Domeny ze złośliwym oprogramowaniem",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Elementy irytujące",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Różne",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regiony, języki",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Informacje o wydaniu",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Kod źródłowy (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Współtwórcy",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Kod źródłowy",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Tłumaczenia",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Listy filtrów",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Zewnętrzne zależności (kompatybilne z GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Witaj",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Właśnie został zainstalowany uBO Lite. Tutaj możesz wybrać domyślny tryb filtrowania, który będzie używany na wszystkich witrynach internetowych.\n\nDomyślnie wybrany jest tryb <em>Podstawowy</em>, ponieważ nie wymaga uprawnień do odczytu i zmiany danych. Jeśli ufasz uBO Lite, możesz nadać mu szerokie uprawnienia do odczytu i zmiany danych na wszystkich witrynach internetowych w celu domyślnego włączenia bardziej zaawansowanych funkcji filtrowania wszystkich witryn internetowych.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Domyślny tryb filtrowania",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Domyślny tryb filtrowania zostanie zastąpiony przez tryby filtrowania poszczególnych witryn. Możesz dostosować tryb filtrowania na dowolnej witrynie zgodnie z trybem, który najlepiej działa na tej witrynie. Każdy tryb ma swoje wady i zalety.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "bez filtrowania",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "podstawowy",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optymalny",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "kompletny",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Podstawowe filtrowanie sieciowe z wybranych list filtrów.\n\nNie wymaga uprawnień do odczytu i zmiany danych na witrynach internetowych.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Zaawansowane filtrowanie sieciowe oraz konkretne filtrowanie rozszerzone z wybranych list filtrów.\n\nWymaga szerokich uprawnień do odczytu i zmiany danych na wszystkich witrynach internetowych.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Zaawansowane filtrowanie sieciowe oraz konkretne i ogólne filtrowanie rozszerzone z wybranych list filtrów.\n\nWymaga szerokich uprawnień do odczytu i zmiany danych na wszystkich witrynach internetowych.\n\nOgólne filtrowanie rozszerzone może powodować większe zużycie zasobów przez witryny internetowe.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Lista nazw hostów, dla których nie będzie stosowane żadne filtrowanie",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Zachowanie",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatycznie wczytaj ponownie stronę po zmianie trybu filtrowania",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/pt_BR/messages.json b/platform/mv3/extension/_locales/pt_BR/messages.json
new file mode 100644
index 0000000..cb4e978
--- /dev/null
+++ b/platform/mv3/extension/_locales/pt_BR/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Um bloqueador de conteúdo com menos permissões - Bloqueie anúncios, rastreadores, mineradores e mais imediatamente após a instalação.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} regras, convertidas de {{filterCount}} filtros de rede",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Painel",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Configurações",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Sobre",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Política de privacidade",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "modo de filtragem",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Abrir painel",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Mais",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Menos",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Padrão",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Anúncios",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacidade",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Domínios de malware",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Aborrecimentos",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscelãnea",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regiões, idiomas",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Código fonte (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Colaboradores",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Código fonte",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Traduções",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Listas de filtros",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Dependências externas (compatíveis com GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Bem-vindo",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Você instalou o uBO Lite. Aqui você pode escolher o modo de filtragem padrão pra usar em todos os sites da web.\n\nPor padrão o modo <em>Básico</em> está selecionado porque não requer permissão pra ler e modificar os dados. Se você confia no uBO Lite você pode dar ampla permissão pra ler e modificar os dados em todos os sites da web de modo a ativar as capacidades de filtragem mais avançadas pra todos os sites da web por padrão.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Modo de filtragem padrão",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "O modo de filtragem padrão será substituído pelos modos de filtragem por site. Você pode ajustar o modo de filtragem em qualquer site dado da web de acordo com qualquer modo que funcionar melhor nesse site da web. Cada modo tem suas vantagens e desvantagens.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "sem filtragem",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "básico",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "otimizado",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "completo",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Filtragem básica da rede de listas de filtros selecionadas.\n\nNão requer permissão pra ler e modificar os dados nos sites da web.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Filtragem avançada da rede mais filtragem estendida específica das listas de filtros selecionadas.\n\nRequer ampla permissão pra ler e modificar os dados em todos os sites da web.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Filtragem avançada da rede mais filtragem estendida específica e genérica das listas de filtros selecionadas.\n\nRequer ampla permissão pra ler e modificar os dados em todos os sites da web.\n\nA filtragem estendida genérica pode causar maior uso de recursos da página da web.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Lista de nomes dos hospedeiros para os quais nenhuma filtragem acontecerá.",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Comportamento",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Recarregar a página automaticamente quando mudar o modo de filtragem",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/pt_PT/messages.json b/platform/mv3/extension/_locales/pt_PT/messages.json
new file mode 100644
index 0000000..4bd510a
--- /dev/null
+++ b/platform/mv3/extension/_locales/pt_PT/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Um bloqueador de conteúdo sem permissões. Bloqueia anúncios, rastreadores, mineradores de criptomoedas e muito mais, imediatamente após a instalação.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} regras, convertidas a partir de {{filterCount}} filtros de rede",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Painel de controlo",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Definições",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Acerca",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Política de privacidade",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "modo de filtragem",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Abrir o painel de controlo",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Mais",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Menos",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Predefinição",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Anúncios",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacidade",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Domínios maliciosos",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Inconveniências",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Diversos",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regiões, idiomas",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Registo de alterações",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Código fonte (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contribuidores",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Código fonte",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Traduções",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Listas de filtros",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Dependências externas (compatíveis com GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Bem-vindo(a)",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Acabou de instalar o uBO Lite. Pode escolher aqui o modo de filtragem predefinido para usar em todos os websites.\n\nPor predefinição, o modo <em>Básico</em> é selecionado porque não requer a permissão para ler e modificar dados. Se confiar no uBO Lite, pode dar-lhe ampla permissão para ler e modificar dados em todos os websites, a fim de ativar capacidades de filtragem mais avançadas para todos os websites por predefinição.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Modo de filtragem predefinido",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "O modo de filtragem predefinido será substituído pelos modos de filtragem por website. Pode ajustar o modo de filtragem em qualquer website conforme o modo que melhor funcionar nesse website. Cada modo tem as suas vantagens e desvantagens.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "sem filtragem",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "básico",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "ideal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "completo",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Filtragem de rede básica a partir de listas de filtros selecionadas.\n\nNão requer permissão para ler e modificar dados em websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Filtragem de rede avançada mais filtragem alargada específica a partir de listas de filtros selecionadas.\n\nRequer ampla permissão para ler e modificar dados em todos os websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Filtragem de rede avançada mais filtragem alargada específica e genérica a partir de listas de filtros selecionadas.\n\nRequer ampla permissão para ler e modificar dados em todos os websites.\n\nA filtragem alargada genérica pode causar uma maior utilização de recursos das páginas web.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Lista de nomes de anfitriões para os quais não será efetuada qualquer filtragem",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Comportamento",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Recarrega automaticamente a página ao mudar o modo de filtragem",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/ro/messages.json b/platform/mv3/extension/_locales/ro/messages.json
new file mode 100644
index 0000000..fb01a05
--- /dev/null
+++ b/platform/mv3/extension/_locales/ro/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Un blocant experimental, fără permisiuni. Blochează reclamele, urmăritorii, minerii de monede virtuale și multe altele imediat [...]",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} de reguli convertite din {{filterCount}} filtre de rețea",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Panou de control",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Opțiuni",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Despre",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Politică de confidențialitate",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "Mod de filtrare",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Deschide panoul de control",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Mai mult",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Mai puțin",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Implicit",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reclame",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Confidențialitate",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Domenii malițioase",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Neplăceri",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Diverse",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regiuni, limbi",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Jurnal de modificări",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Cod sursă (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contribuitori",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Cod sursă",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Traduceri",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Liste de filtre",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Dependențe externe (compatibile GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Bun venit",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Tocmai ați instalat uBO Lite. Aici puteți alege modul de filtrare implicit pe toate site-urile.\n\nImplicit, modul de <em>bază</em> este selectat întrucât nu necesită permisiuni pentru a citi și modifica date. Dacă aveți încredere în uBO Lite, puteți să-i acordați permisiuni sporite pentru a citi și modifica datele tututor sitte-rilor pentru a activa capabilități mai avansate de filtrare.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Mod de filtrare implicit",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Modul implicit de filtrare va fi suprascris de modul specific fiecărui site. Puteți ajusta filtrarea pentru fiecare site conform caracteristicilor acestuia. Modurile au avantaje și dezavantaje.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "fără filtrare",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "de bază",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optim",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complet",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Filtrare de rețea de bază de la liste de filtre selectate.\n\nNu necesită permisiune pentru a citi și vedea date pe site-uri web.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Filtrare de rețea avansată plus filtrare extinsă specifică de la liste de filtre selectate.\n\nNecesită permisiune vagă pentru a citi și vedea date pe toate site-urile web.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Filtrare de rețea avansată plus filtrare extinsă specifică și generică de la liste de filtre selectate.\n\nNecesită permisiune vagă pentru a citi și modifica date pe toate site-urile web.\n\nFiltrarea generică extinsă poate cauza mai multă utilizare de resurse la nivel de pagină web.\n",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Lista numelor mașinilor pentru care nu se va face filtrare",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Comportament",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Reîncărcare automată a paginii la schimbarea modului de filtrare",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/ru/messages.json b/platform/mv3/extension/_locales/ru/messages.json
new file mode 100644
index 0000000..0104b7c
--- /dev/null
+++ b/platform/mv3/extension/_locales/ru/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Блокировщик контента, не требующий разрешений. Сразу после инсталляции блокирует рекламу, трекеры, майнеры и многое другое.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} правил, преобразованных из {{filterCount}} сетевых фильтров",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Панель управления",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Настройки",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "О расширении",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Политика конфиденциальности",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "режим фильтрации",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Открыть панель управления",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Больше",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Меньше",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "По умолчанию",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Реклама",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Приватность",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Вредоносные домены",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Раздражающие элементы",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Разное",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Регионы, языки",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Список изменений",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Исходный код (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Участники",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Исходный код",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Перевод",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Списки фильтров",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Внешние зависимости (совместимые с GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Добро пожаловать",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Вы только что установили uBO Lite. Здесь вы можете выбрать стандартный режим фильтрации для всех веб-сайтов.\n\nПо умолчанию выбран режим <em>Базовый</em>, так как он не требует разрешения на чтение и изменение данных. Если вы доверяете uBO Lite, вы можете дать ему широкие права на чтение и изменение данных на всех веб-сайтах, чтобы включить более продвинутые возможности фильтрации для всех веб-сайтов по умолчанию.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Режим фильтрации по умолчанию",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Стандартный режим фильтрации перезаписывается режимом настройки конкретного сайта. Вы можете подобрать наиболее эффективный режим фильтрации на любом сайте. У каждого режима есть свои преимущества и недостатки.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "без фильтрации",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "базовый",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "оптимальный",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "полный",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Основная сетевая фильтрация по выбранным спискам фильтров.\n\nНе требует разрешения на чтение и изменение данных на веб-сайтах.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Расширенная сетевая фильтрация вместе со специальной фильтрацией по выбранным спискам фильтров.\n\nТребуется разрешение на чтение и изменение данных на всех веб-сайтах.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Расширенная сетевая фильтрация вместе со специальной, и усиленной общей фильтрацией по выбранным спискам фильтров.\n\nТребуется разрешение на чтение и изменение данных на всех веб-сайтах.\n\nУсиленная общая фильтрация может стать причиной повышенного потребления ресурсов веб-страницей.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Список имён хостов, для которых не будет производиться фильтрация",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Поведение",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Автоматически перезагружать страницу при изменении режима фильтрации",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/si/messages.json b/platform/mv3/extension/_locales/si/messages.json
new file mode 100644
index 0000000..627857e
--- /dev/null
+++ b/platform/mv3/extension/_locales/si/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/sk/messages.json b/platform/mv3/extension/_locales/sk/messages.json
new file mode 100644
index 0000000..997c759
--- /dev/null
+++ b/platform/mv3/extension/_locales/sk/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Experimentálny blokátor obsahu bez povolení. Okamžite po inštalácii zablokuje reklamy, sledovacie programy, minery a ďalšie.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} pravidiel, prevedené z {{filterCount}} sieťových filtrov",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Ovládací panel",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Nastavenia",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "O doplnku",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Zásady ochrany osobných údajov",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "Režim filtrovania",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Otvoriť ovládací panel",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Viac",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Menej",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Predvolené",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklamy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Súkromie",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Domény malvéru",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Obťažujúce",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Rôzne",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regióny, jazyky",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Zoznam zmien",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Zdrojový kód (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Prispievatelia",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Zdrojový kód",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Preklady",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Zoznam filtrov",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Externé závislosti (kompatibilné s GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Vitajte",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Práve ste nainštalovali uBO Lite. Tu si môžete vybrať predvolený režim filtrovania, ktorý sa má používať na všetkých webových stránkach.\n\nV predvolenom nastavení je zvolený režim <em>Základný</em>, pretože nevyžaduje povolenie na čítanie a zmenu údajov. Ak dôverujete rozšíreniu uBO Lite, môžete mu udeliť všeobecné oprávnenie na čítanie a zmenu údajov na všetkých webových stránkach, aby ste v predvolenom nastavení umožnili pokročilejšie možnosti filtrovania všetkých webových stránok.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Predvolený režim filtrovania",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Predvolený režim filtrovania bude nahradený režimami filtrovania pre jednotlivé webové stránky. Režim filtrovania na ktorejkoľvek webovej stránke môžete upraviť podľa toho, ktorý režim je na danej webovej lokalite najlepší. Každý režim má svoje výhody a nevýhody.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "nefiltrované",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "základný",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimálny",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "kompletný",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Základné sieťové filtrovanie z vybraných zoznamov filtrov.\n\nNevyžaduje povolenie na čítanie a zmenu údajov na webových stránkach.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Pokročilé sieťové filtrovanie a špecifické rozšírené filtrovanie z vybraných zoznamov filtrov.\n\nVyžaduje všeobecné oprávnenie na čítanie a zmenu údajov na všetkých webových stránkach.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Pokročilé sieťové filtrovanie plus špecifické a generické rozšírené filtrovanie z vybraných zoznamov filtrov.\n\nVyžaduje všeobecné oprávnenie na čítanie a zmenu údajov na všetkých webových stránkach.\n\nGenerické rozšírené filtrovanie môže spôsobiť vyššie využitie zdrojov webovej stránky.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Správanie",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automaticky znova načítať stránku pri zmene režimu filtrovania",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/sl/messages.json b/platform/mv3/extension/_locales/sl/messages.json
new file mode 100644
index 0000000..627857e
--- /dev/null
+++ b/platform/mv3/extension/_locales/sl/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/so/messages.json b/platform/mv3/extension/_locales/so/messages.json
new file mode 100644
index 0000000..627857e
--- /dev/null
+++ b/platform/mv3/extension/_locales/so/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/sq/messages.json b/platform/mv3/extension/_locales/sq/messages.json
new file mode 100644
index 0000000..d390d8d
--- /dev/null
+++ b/platform/mv3/extension/_locales/sq/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Një bllokues që bllokon në mënyrë të pavarur reklamat, gjurmuesit, kriptominatorët etj. menjëherë pas instalimit.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rregulla sipas {{filterCount}} filtrave",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Paneli i kontrollit",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Parametrat",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Info",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Politika e privatësisë",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "mënyra e filtrimit",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Hapni panelin e kontrollit",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Më shumë",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Më pak",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Standarde",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklamat",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privatësia",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Domenet e dëmshme",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Elementet e bezdisshme",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Të ndryshme",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Sipas rajonit, gjuhës",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Ditari i ndryshimeve",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Materiali burimor (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Kontribuesit",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Materiali burimor",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Përkthimet",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Listat e filtrave",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Programet kushtëzuese (sipas GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Përshëndetje",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Sapo instaluat uBO Lite. Këtu mund të zgjidhni mënyrën e filtrimit që duhet përdorur për të gjitha uebsajtet.\n\n<em>E thjeshta</em> është mënyra standarde, sepse nuk ju merret leje për leximin dhe modifikimin e të dhënave. Nëse keni besim te uBO Lite, mund t'i jepni leje shtesë për leximin dhe modifikimin e të dhënave në të gjitha uebsajtet, në mënyrë që të kryeni një filtrim më të avancuar.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Mënyra standarde e filtrimit",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Mënyra standarde e filtrimit mund të ndryshohet në çdo kohë sipas kërkesave që kanë uebsajte të caktuara. Secila mënyrë ka avantazhet dhe disavantazhet e veta.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "pa filtër",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "thjesht",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "komplet",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Filtrat kryesorë të rrjetit nga listat e përzgjedhura.\n\nNuk merret leje për leximin dhe modifikimin e të dhënave në uebsajte.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Filtrat e avancuar të rrjetit plus filtrat e posaçëm nga listat e përzgjedhura.\n\nDuhen leje shtesë për leximin dhe modifikimin e të dhënave në të gjitha uebsajtet.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Filtrat e avancuar të rrjetit plus filtrat e posaçëm jospecifikë nga listat e përzgjedhura.\n\nDuhen leje shtesë për leximin dhe modifikimin e të dhënave në të gjitha uebsajtet.\n\nFiltrat e posaçëm jospecifikë mund ta rëndojnë hapjen e faqeve në internet.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Emrat e hosteve që nuk do të kalojnë në filtër",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Sjellja",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Freskoj automatikisht faqen kur ndryshoj mënyrën e filtrimit",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/sr/messages.json b/platform/mv3/extension/_locales/sr/messages.json
new file mode 100644
index 0000000..0900785
--- /dev/null
+++ b/platform/mv3/extension/_locales/sr/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Блокатор садржаја који не захтева дозволе. Блокира рекламе, праћења, рударе криптовалута и друго.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} правила, конвертована из {{filterCount}} мрежних филтера",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Контролна табла",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Подешавања",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "О апликацији",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Политика приватности",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "режим филтрирања",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Отвори контролну таблу",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Више",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Мање",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Подразумевано",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Рекламе",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Приватност",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Злонамерни домени",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Сметње",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Разно",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Регионални, језички",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Списак измена",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Изворни код (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Сарадници",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Изворни код",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Преводи",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Листе филтера",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Спољне зависности (компатибилно са GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Добродошли",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Управо сте инсталирали uBO Lite. Овде можете изабрати подразумевани режим филтрирања који ће се користити на свим сајтовима.\n\nПодразумевано је изабран <em>основни</em> режим јер он не захтева дозволу за читање и мењање података. Ако верујете uBO Lite-у, можете му дати широку дозволу за читање и мењање података како би се подразумевано омогућиле напредније могућности филтрирања за све сајтове.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Подразумевани режим филтрирања",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Подразумевани режим филтрирања ће бити замењен режимима филтрирања по појединачним веб сајтовима. Можете прилагодити режим филтрирања на било ком веб сајту у складу са режимом који најбоље функционише на том веб сајту. Сваки режим има своје предности и мане.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "без филтрирања",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "основно",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "оптимално",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "комплетно",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Основно мрежно филтрирање са изабраних листа филтера.\n\nНе захтева дозволу за читање и мењање података на сајтовима.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Напредно мрежно и специфично проширено филтрирање са изабраних листа филтера.\n\nЗахтева широку дозволу за читање и мењање података на свим сајтовима.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Напредно мрежно и специфично и генеричко проширено филтрирање са изабраних листа филтера.\n\nЗахтева широку дозволу за читање и мењање података на свим сајтовима.\n\nГенеричко филтрирање може довести до већег коришћења ресурса веб странице.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Листа имена хостова за које се неће вршити филтрирање",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Понашање",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Аутоматски поново учитај страницу при промени режима филтрирања",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/sv/messages.json b/platform/mv3/extension/_locales/sv/messages.json
new file mode 100644
index 0000000..fddcca5
--- /dev/null
+++ b/platform/mv3/extension/_locales/sv/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "En behörighetslös innehållsblockerare. Blockerar annonser, spårare, miners och mer omedelbart efter installationen.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} regler, konverterat från {{filterCount}} nätverksfilter",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Kontrollpanel",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Inställningar",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Om",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Integritetspolicy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtreringsläge",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Öppna kontrollpanelen",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Mer",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Mindre",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Standard",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Annonser",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Integritet",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Domäner med skadlig kod (malware)",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Störande",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Diverse",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regioner, språk",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Ändringslogg",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Källkod (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Bidragsgivare",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Källkod",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Översättningar",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filterlistor",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Externa beroenden (GPLv3-kompatibla):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Välkommen",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Du har precis installerat uBO Lite. Här kan du välja standardfiltreringsläget som ska användas på alla webbplatser.\n\nSom standard är läget <em>Grundläggande</em> valt eftersom det inte kräver behörighet att läsa och ändra data. Om du litar på uBO Lite kan du ge den högre behörighet att läsa och ändra data på alla webbplatser för att som standard aktivera mer avancerade filtreringsmöjligheter för alla webbplatser.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Standardfiltreringsläge",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Standardfiltreringsläget kommer att åsidosättas av filtreringslägen per webbplats. Du kan justera filtreringsläget på vilken webbplats som helst enligt vilket läge som fungerar bäst på den webbplatsen. Varje läge har sina fördelar och nackdelar.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "ingen filtrering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "grundläggande",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "fullständig",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Grundläggande nätverksfiltrering från valda filterlistor.\n\nKräver ingen behörighet för att läsa och ändra data på webbplatser.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Avancerad nätverksfiltrering plus specifik utökad filtrering från utvalda filterlistor.\n\nKräver högre behörighet för att läsa och ändra data på alla webbplatser.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Avancerad nätverksfiltrering plus specifik och allmän utökad filtrering från utvalda filterlistor.\n\nKräver bred behörighet för att läsa och ändra data på alla webbplatser.\n\nAllmän utökad filtrering kan orsaka högre användning av webbsidors resurser.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Lista över värdnamn för vilka ingen filtrering kommer att äga rum",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Beteende",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Ladda automatiskt om sidan när du byter filtreringsläge",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/sw/messages.json b/platform/mv3/extension/_locales/sw/messages.json
new file mode 100644
index 0000000..627857e
--- /dev/null
+++ b/platform/mv3/extension/_locales/sw/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/ta/messages.json b/platform/mv3/extension/_locales/ta/messages.json
new file mode 100644
index 0000000..627857e
--- /dev/null
+++ b/platform/mv3/extension/_locales/ta/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/te/messages.json b/platform/mv3/extension/_locales/te/messages.json
new file mode 100644
index 0000000..e263523
--- /dev/null
+++ b/platform/mv3/extension/_locales/te/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "ఐచ్చికాలు",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "మా గురించి",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "గోప్యతా",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "వడపోత మోడ్",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "డాష్‌బోర్డ్‌ను తెరవండి",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "మరిన్ని",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "తక్కువ",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "డిఫాల్ట్",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "ప్రకటనలు",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "గోప్యత",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Translations",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "ఎంచుకున్న ఫిల్టర్ జాబితాల నుండి ప్రాథమిక నెట్‌వర్క్ ఫిల్టరింగ్.\n\nవెబ్‌సైట్‌లలో డేటాను చదవడానికి మరియు సవరించడానికి అనుమతి అవసరం లేదు.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "ఎంచుకున్న ఫిల్టర్ జాబితాల నుండి అధునాతన నెట్‌వర్క్ ఫిల్టరింగ్ మరియు నిర్దిష్ట పొడిగించిన ఫిల్టరింగ్.\n\nఅన్ని వెబ్‌సైట్‌లలోని డేటాను చదవడానికి మరియు సవరించడానికి విస్తృత అనుమతి అవసరం.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "ఎంచుకున్న ఫిల్టర్ జాబితాల నుండి అధునాతన నెట్‌వర్క్ ఫిల్టరింగ్ మరియు నిర్దిష్ట మరియు సాధారణ పొడిగించిన ఫిల్టరింగ్.\n\nఅన్ని వెబ్‌సైట్‌లలోని డేటాను చదవడానికి మరియు సవరించడానికి విస్తృత అనుమతి అవసరం.\n\nసాధారణ పొడిగించిన వడపోత అధిక వెబ్‌పేజీ వనరుల వినియోగానికి కారణం కావచ్చు.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "వడపోత జరగని హోస్ట్ పేర్ల జాబితా",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "ప్రవర్తన",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "ఫిల్టరింగ్ మోడ్‌ను మార్చేటప్పుడు పేజీని స్వయంచాలకంగా రీలోడ్ చేయండి",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/th/messages.json b/platform/mv3/extension/_locales/th/messages.json
new file mode 100644
index 0000000..75f992a
--- /dev/null
+++ b/platform/mv3/extension/_locales/th/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "A permission-less content blocker. Blocks ads, trackers, miners, and more immediately upon installation.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Dashboard",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Settings",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "About",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Privacy policy",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "การแปลภาษา",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "complete",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/tr/messages.json b/platform/mv3/extension/_locales/tr/messages.json
new file mode 100644
index 0000000..b40f8a0
--- /dev/null
+++ b/platform/mv3/extension/_locales/tr/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "İzin gerektirmeyen içerik engelleyicisi. Kurulumdan hemen sonra reklamları, izleyicileri, kripto madencilerini, ve daha fazlasını engeller.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{filterCount}} ağ filtresinden {{ruleCount}} adet kural dönüştürüldü",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Kontrol Paneli",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Ayarlar",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Hakkında",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Gizlilik ilkesi",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "Filtreleme modu",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Kontrol panelini aç",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Daha Fazla",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Daha Az",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Varsayılan",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Reklamlar",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Gizlilik",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Zararlı alan adları",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Rahatsız ediciler",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Diğer",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Bölgeler, diller",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Değişiklik günlüğü",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Kaynak kodu (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Katkıda bulunanlar",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Kaynak kodu",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Çeviriler",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filtre listeleri",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Dış bağlılıklar (GPLv3-uyumlu):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Hoş geldiniz",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Az önce uBO Lite'ı indirdiniz. Buradan tüm siteler için varsayılan filtreleme modlarını seçebilirsiniz.\n\nVarsayılan olarak, <em>Basit</em> mod seçilidir çünkü verileri okuma ve yazma izni gerektirmez. uBO Lite'a güveniyorsanız, tüm sitelerde verileri okuma ve yazma izni verebilirsiniz ve daha gelişmiş filtreleme yeteneklerine tüm sitelerde sahip olabilirsiniz.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Varsayılan filtreleme modu",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Varsayılan filtreleme modu, web sitesi başına filtreleme modları tarafından geçersiz kılınacaktır. Herhangi bir web sitesindeki filtreleme modunu, o web sitesinde en iyi çalışan moda göre ayarlayabilirsiniz. Her modun avantajları ve dezavantajları vardır.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "filtreleme yok",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "temel",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "ideal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "kapsamlı",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Seçilen filtre listelerinden temel ağ filtrelemesi.\n\nWeb sitelerindeki verileri okumak ve değiştirmek için izin gerektirmez.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Gelişmiş ağ filtrelemesi ve seçilen filtre listelerinden genişletilmiş filtreleme.\n\nTüm web sitelerindeki verileri okumak ve değiştirmek için izin gerektirir.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Gelişmiş ağ filtrelemenin yanı sıra seçilen filtre listelerinden özel ve genel genişletilmiş filtreleme.\n\nTüm web sitelerindeki verileri okumak ve değiştirmek için geniş izin gerektirir.\n\nGenel genişletilmiş filtreleme daha yüksek kaynak kullanımına neden olabilir.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Filtreleme yapılmayacak alan alarının listesi",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Davranış",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Filtreleme modu değiştirildiğinde sayfayı yenile",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/uk/messages.json b/platform/mv3/extension/_locales/uk/messages.json
new file mode 100644
index 0000000..b48a9bb
--- /dev/null
+++ b/platform/mv3/extension/_locales/uk/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Експериментальний, бездозвільний блокувальник вмісту. Блокує рекламу, трекери, майнери та інше одразу після встановлення.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} правил, конвертованих з {{filterCount}} мережних фільтрів",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Панель керування",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Налаштування",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Про застосунок",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Політика конфіденційності",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "режим фільтрації",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Відкрити панель керування",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Більше",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Менше",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Типово",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Реклама",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Приватність",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Домени шкідливих програм",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Надокучливості",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Різне",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Регіони, мови",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Журнал змін",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Код програми (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Учасники",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Вихідний код",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Переклади",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Списки фільтрів",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Зовнішні залежності (Сумісні з GPLv3)",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Ласкаво просимо",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Ви щойно встановили uBO Lite. Тут ви можете вибрати режим фільтрації за замовчуванням, який буде використовуватися на всіх вебсайтах.\n\nЗа замовчуванням вибрано <em>Базовий</em> режим, оскільки він не вимагає дозволу на читання та зміну даних. Якщо ви довіряєте uBO Lite, ви можете надати йому широкий дозвіл на читання та зміну даних на всіх вебсайтах, щоб увімкнути більш розширені можливості фільтрації для всіх вебсайтів за замовчуванням.\n",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Типовий режим фільтрування",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Режим фільтрації за замовчуванням буде замінений режимами фільтрації для кожного вебсайту. Ви можете налаштувати режим фільтрації на будь-якому вебсайті відповідно до того, який режим найкраще працює на цьому вебсайті. Кожен режим має свої переваги та недоліки.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "без фільтрування",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "базовий",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "оптимальний",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "повний",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Базова мережева фільтрація з обраних списків фільтрів.\n\nНе вимагає дозволу на читання і зміну даних на вебсайтах.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Розширена мережева фільтрація плюс спеціальна розширена фільтрація з вибраних списків фільтрів.\n\nВимагає широкого дозволу на читання та зміну даних на всіх вебсайтах.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Розширена мережева фільтрація плюс специфічна та загальна розширена фільтрація з вибраних списків фільтрів.\n\nПотребує широкого дозволу на читання та зміну даних на всіх сайтах.\n\nЗагальна розширена фільтрація може призвести до збільшення використання ресурсів веб-сторінки.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Список імен хостів, для яких буде відбуватись фільтрування",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Поведінка",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Автоматично оновити сторінку при зміні режиму фільтрування",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/ur/messages.json b/platform/mv3/extension/_locales/ur/messages.json
new file mode 100644
index 0000000..e66c4a9
--- /dev/null
+++ b/platform/mv3/extension/_locales/ur/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "ایک تجرباتی، اجازت سے کم مواد بلاکر۔ انسٹال ہونے پر اشتہارات، ٹریکرز، کان کنوں اور مزید کو فوری طور پر روکتا ہے۔",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "یو بلاکر لائٹ-ڈیش بورڈ",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "ترتیبات",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "تعارف",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "پرائیویسی پالیسی",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "filtering mode",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Open the dashboard",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "More",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Less",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Default",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Ads",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Privacy",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Malware domains",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Annoyances",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Miscellaneous",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Regions, languages",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Changelog",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Source code (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Contributors",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Source code",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "ترجمہ",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Filter lists",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "External dependencies (GPLv3-compatible):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Welcome",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "You have just installed uBO Lite. Here you can choose the default filtering mode to use on all websites.\n\nBy default, <em>Basic</em> mode is selected because it does not require the permission to read and modify data. If you trust uBO Lite, you can give it broad permission to read and modify data on all websites in order to enable more advanced filtering capabilities for all websites by default.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Default filtering mode",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "The default filtering mode will be overridden by per-website filtering modes. You can adjust the filtering mode on any given website according to whichever mode works best on that website. Each mode has its advantages and disadvantages.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "no filtering",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "basic",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "optimal",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "مکمل",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Basic network filtering from selected filter lists.\n\nDoes not require permission to read and modify data on websites.",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Advanced network filtering plus specific and generic extended filtering from selected filter lists.\n\nRequires broad permission to read and modify data on all websites.\n\nGeneric extended filtering may cause higher webpage resources usage.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "List of hostnames for which no filtering will take place",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Behavior",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Automatically reload page when changing filtering mode",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/vi/messages.json b/platform/mv3/extension/_locales/vi/messages.json
new file mode 100644
index 0000000..6ecd8cb
--- /dev/null
+++ b/platform/mv3/extension/_locales/vi/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "Trình chặn nội dung không cần quyền. Chặn quảng cáo, trình theo dõi, công cụ khai thác tiền số và hơn thế nữa ngay sau khi cài đặt.",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} quy tắc, được chuyển đổi từ {{filterCount}} bộ lọc mạng",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — Bảng điều khiển",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "Cài đặt",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "Giới thiệu",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "Chính sách bảo mật",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "chế độ lọc",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "Mở bảng điều khiển",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "Mở rộng",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "Ít hơn",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "Mặc định",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "Quảng cáo",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "Riêng tư",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "Các tên miền nguy hiểm",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "Phiền toái",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "Linh tinh",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "Khu vực, ngôn ngữ",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "Nhật ký thay đổi",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "Mã nguồn (GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "Những người đóng góp",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "Mã nguồn",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "Bản dịch",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "Danh sách bộ lọc",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "Các phụ thuộc bên ngoài (tương thích GPLv3):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "Chào mừng",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "Bạn vừa cài đặt uBO Lite. Bạn có thể chọn chế độ của bộ lọc mặc định để sử dụng trên tất cả các trang web. Theo mặc định, chế độ <em>Cơ bản</em> được chọn vì chế độ này không yêu cầu quyền đọc và thay đổi dữ liệu. Nếu bạn tin tưởng uBO Lite, bạn có thể cấp cho bộ lọc với quyền rộng rãi để đọc và thay đổi dữ liệu trên tất cả các trang web để kích hoạt khả năng lọc nâng cao hơn cho tất cả các trang web theo mặc định.",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "Chế độ bộ lọc mặc định",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "Chế độ lọc mặc định sẽ ghi đè các chế độ bộ lọc trên mỗi trang web. Bạn có thể điều chỉnh chế độ lọc trên bất kỳ trang web nào và theo bất kỳ chế độ nào hoạt động tốt nhất trên trang web đó. Mỗi chế độ đều có ưu điểm và nhược điểm riêng.",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "Không có bộ lọc",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "Cơ bản",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "tối ưu",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "Hoàn toàn",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "Chế độ lọc mạng cơ bản từ các danh sách bộ lọc đã chọn.\n\nKhông yêu cầu quyền về đọc và thay đổi dữ liệu trên các trang web.\n",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "Lọc mạng với mức nâng cao cộng với lọc mở rộng cụ thể từ danh sách bộ lọc đã chọn.\n\nYêu cầu cấp quyền để đọc và thay đổi dữ liệu trên tất cả các trang web.",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "Lọc mạng với chế độ nâng cao cộng với lọc mở rộng cụ thể và chung chung từ danh sách bộ lọc đã chọn.\n\nYêu cầu cấp quyền để đọc và thay đổi dữ liệu trên tất cả các trang web.\n\nVới chế độ lọc chung có thể gây ra tốn tài nguyên trang web cao hơn.",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "Danh sách tên máy chủ sẽ không được lọc",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "Hành vi",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "Tự động tải lại trang khi thay đổi chế độ bộ lọc",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/zh_CN/messages.json b/platform/mv3/extension/_locales/zh_CN/messages.json
new file mode 100644
index 0000000..c11fdf7
--- /dev/null
+++ b/platform/mv3/extension/_locales/zh_CN/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "一个不需要权限的内容屏蔽工具。安装即可屏蔽广告、跟踪器、挖矿脚本等网页内容。",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} 条规则,转换自 {{filterCount}} 条网络共享规则",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — 控制面板",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "设置",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "关于",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "隐私政策",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "过滤模式",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "在仪表板中",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "更多",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "更少",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "预设",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "广告",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "隐私",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "恶意网站",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "骚扰",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "其他",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "区域、语言",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "更新日志",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "源代码(GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "贡献者",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "源代码",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "翻译",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "过滤规则列表",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "外部依赖(与 GPLv3 协议兼容):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "欢迎使用",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "uBO Lite 现已安装完毕。接下来您可以为网页内容选择默认过滤模式。\n\n预设过滤模式为“基础”,该模式不需要读取或修改网页内容的权限。如果您信任 uBO Lite,您可以授予其用以读取或修改所有网页数据的额外权限,进而默认为所有网站开启高级过滤模式。",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "默认过滤模式",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "默认过滤模式会被为各网站专设的过滤模式覆盖。您可以为任何网站设置最为合适的过滤模式,每种模式都有各自的优点和缺点。",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "不过滤",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "基础",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "优化",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "完全",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "用选定的过滤规则列表进行基本网页过滤。\n\n不需要读取或修改网页数据的权限。",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "用选定的过滤规则列表进行进阶网页过滤,会使用有针对性的过滤规则。\n\n需要读取或修改网页数据的权限。",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "用选定的过滤规则列表进行进阶网页过滤,会同时使用有针对性的以及通用的过滤规则。\n\n需要读取或修改网页数据的权限。\n\n通用过滤功能可能会占用更多系统资源。",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "不进行过滤的主机名列表",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "操作设置",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "更改过滤模式后自动刷新网页",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/_locales/zh_TW/messages.json b/platform/mv3/extension/_locales/zh_TW/messages.json
new file mode 100644
index 0000000..7825bd5
--- /dev/null
+++ b/platform/mv3/extension/_locales/zh_TW/messages.json
@@ -0,0 +1,158 @@
+{
+ "extName": {
+ "message": "uBlock Origin Lite",
+ "description": "extension name."
+ },
+ "extShortDesc": {
+ "message": "一個無須任何權限的內容阻擋器。安裝即可阻擋廣告、追蹤器、挖礦程式等網頁內容。",
+ "description": "this will be in the Chrome web store: must be 132 characters or less"
+ },
+ "perRulesetStats": {
+ "message": "{{ruleCount}} 條規則,轉換自 {{filterCount}} 條網路過濾規則",
+ "description": "Appears aside each filter list in the _3rd-party filters_ pane"
+ },
+ "dashboardName": {
+ "message": "uBO Lite — 控制台",
+ "description": "English: uBO Lite — Dashboard"
+ },
+ "settingsPageName": {
+ "message": "設定",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPageName": {
+ "message": "關於",
+ "description": "appears as tab name in dashboard"
+ },
+ "aboutPrivacyPolicy": {
+ "message": "隱私權政策",
+ "description": "Link to privacy policy on GitHub (English)"
+ },
+ "popupFilteringModeLabel": {
+ "message": "過濾模式",
+ "description": "Label in the popup panel for the current filtering mode"
+ },
+ "popupTipDashboard": {
+ "message": "開啟控制台",
+ "description": "English: Click to open the dashboard"
+ },
+ "popupMoreButton": {
+ "message": "更多",
+ "description": "Label to be used to show popup panel sections"
+ },
+ "popupLessButton": {
+ "message": "更少",
+ "description": "Label to be used to hide popup panel sections"
+ },
+ "3pGroupDefault": {
+ "message": "預設",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAds": {
+ "message": "廣告",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupPrivacy": {
+ "message": "隱私",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMalware": {
+ "message": "惡意網域",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupAnnoyances": {
+ "message": "嫌惡元素",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupMisc": {
+ "message": "其他",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "3pGroupRegions": {
+ "message": "地區及語言",
+ "description": "Header for a ruleset section in 'Filter lists pane'"
+ },
+ "aboutChangelog": {
+ "message": "變更日誌",
+ "description": ""
+ },
+ "aboutCode": {
+ "message": "原始碼(GPLv3)",
+ "description": "English: Source code (GPLv3)"
+ },
+ "aboutContributors": {
+ "message": "貢獻者",
+ "description": "English: Contributors"
+ },
+ "aboutSourceCode": {
+ "message": "原始碼",
+ "description": "Link text to source code repo"
+ },
+ "aboutTranslations": {
+ "message": "翻譯",
+ "description": "Link text to translations repo"
+ },
+ "aboutFilterLists": {
+ "message": "過濾規則清單",
+ "description": "Link text to uBO's own filter lists repo"
+ },
+ "aboutDependencies": {
+ "message": "外部相依套件(與 GPLv3 相容):",
+ "description": "Shown in the About pane"
+ },
+ "firstRunSectionLabel": {
+ "message": "歡迎",
+ "description": "The header text for the welcome message section"
+ },
+ "firstRunDescription": {
+ "message": "您剛安裝了 uBO Lite。您可以在此處選擇要在所有網站上使用的預設過濾模式。\n\n預設情況下將會選取<em>基礎</em>模式,因為其不需要讀取與變更資料的權限。若您信任 uBO Lite,您可以給予其讀取並變更在所有網站上資料的廣泛權限,以便為所有網站啟用更進階的過濾功能。",
+ "description": "Descriptive text shown at first install time only "
+ },
+ "defaultFilteringModeSectionLabel": {
+ "message": "預設過濾模式",
+ "description": "The header text for the default filtering mode section"
+ },
+ "defaultFilteringModeDescription": {
+ "message": "預設過濾模式將被每個網站的過濾模式覆寫。您可以根據在該網站上最有效的模式為任何指定的網站調整過濾模式。每種模式都有其優缺點。",
+ "description": "This describes the default filtering mode setting"
+ },
+ "filteringMode0Name": {
+ "message": "不過濾",
+ "description": "Name of blocking mode 0"
+ },
+ "filteringMode1Name": {
+ "message": "基礎",
+ "description": "Name of blocking mode 1"
+ },
+ "filteringMode2Name": {
+ "message": "最佳化",
+ "description": "Name of blocking mode 2"
+ },
+ "filteringMode3Name": {
+ "message": "完整",
+ "description": "Name of blocking mode 3"
+ },
+ "basicFilteringModeDescription": {
+ "message": "來自選定過濾條件清單的基礎網路過濾。\n\n不需要讀取與變更網站資料的權限。",
+ "description": "This describes the 'basic' filtering mode"
+ },
+ "optimalFilteringModeDescription": {
+ "message": "進階網路過濾以及來自選定過濾條件清單的特定擴展過濾條件。\n\n需要在所有網站上讀取並變更資料的廣泛權限。",
+ "description": "This describes the 'optimal' filtering mode"
+ },
+ "completeFilteringModeDescription": {
+ "message": "進階網路過濾以及來自選定過濾條件清單的特定與通用擴展過濾條件。\n\n需要在所有網站上讀取並變更資料的廣泛權限。\n\n通用擴展過濾可能會導致更高的網頁資源使用率。",
+ "description": "This describes the 'complete' filtering mode"
+ },
+ "noFilteringModeDescription": {
+ "message": "不進行過濾的主機名稱列表",
+ "description": "A short description for the editable field which lists trusted sites"
+ },
+ "behaviorSectionLabel": {
+ "message": "行為",
+ "description": "The header text for the 'Behavior' section"
+ },
+ "autoReloadLabel": {
+ "message": "變更過濾模式時自動重新載入頁面",
+ "description": "Label for a checkbox in the options page"
+ }
+}
diff --git a/platform/mv3/extension/css/dashboard-common.css b/platform/mv3/extension/css/dashboard-common.css
new file mode 100644
index 0000000..41d6416
--- /dev/null
+++ b/platform/mv3/extension/css/dashboard-common.css
@@ -0,0 +1,52 @@
+h2, h3 {
+ margin: 1em 0;
+ }
+h2 {
+ font-size: 18px;
+ }
+h3 {
+ font-size: 16px;
+ }
+a {
+ text-decoration: none;
+ }
+.fa-icon.info {
+ color: var(--info0-ink);
+ fill: var(--info0-ink);
+ font-size: 115%;
+ }
+.fa-icon.info:hover {
+ transform: scale(1.25);
+ }
+.fa-icon.info.important {
+ color: var(--info2-ink);
+ fill: var(--info2-ink);
+ }
+.fa-icon.info.very-important {
+ color: var(--info3-ink);
+ fill: var(--info3-ink);
+ }
+input[type="number"] {
+ width: 5em;
+ }
+@media (max-height: 640px), (max-height: 800px) and (max-width: 480px) {
+ .body > p,
+ .body > ul {
+ margin: 0.5em 0;
+ }
+ .vverbose {
+ display: none !important;
+ }
+ }
+/**
+ On mobile device, the on-screen keyboard may take up
+ so much space that it overlaps the content being edited.
+ The rule below makes it possible to scroll the edited
+ content within view.
+*/
+:root.mobile {
+ overflow: auto;
+ }
+:root.mobile body {
+ min-height: 600px;
+ }
diff --git a/platform/mv3/extension/css/dashboard.css b/platform/mv3/extension/css/dashboard.css
new file mode 100644
index 0000000..e98bcc9
--- /dev/null
+++ b/platform/mv3/extension/css/dashboard.css
@@ -0,0 +1,74 @@
+body {
+ align-items: center;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ }
+body > * {
+ width: min(640px, 100%);
+ }
+#dashboard-nav {
+ background-color: var(--surface-1);
+ border: 0;
+ border-bottom: 1px solid var(--border-1);
+ display: flex;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+ overflow-x: hidden;
+ padding: 0;
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ }
+.tabButton {
+ background-color: transparent;
+ border: 0;
+ border-bottom: 3px solid transparent;
+ border-radius: 0;
+ color: var(--dashboard-tab-ink);
+ fill: var(--dashboard-tab-ink);
+ font-family: var(--font-family);
+ font-size: var(--font-size);
+ padding: 0.7em 1.4em calc(0.7em - 3px);
+ text-decoration: none;
+ white-space: nowrap;
+ }
+.tabButton:focus {
+ outline: 0;
+ }
+.tabButton:hover {
+ background-color: var(--dashboard-tab-hover-surface);
+ border-bottom-color: var(--dashboard-tab-hover-border);
+ }
+
+body[data-pane="settings"] #dashboard-nav .tabButton[data-pane="settings"],
+body[data-pane="about"] #dashboard-nav .tabButton[data-pane="about"] {
+ background-color: var(--dashboard-tab-active-surface);
+ border-bottom: 3px solid var(--dashboard-tab-active-ink);
+ color: var(--dashboard-tab-active-ink);
+ fill: var(--dashboard-tab-active-ink);
+ }
+
+body > section {
+ display: none;
+ }
+body[data-pane="settings"] > section[data-pane="settings"],
+body[data-pane="about"] > section[data-pane="about"] {
+ display: block;
+ }
+
+/* high dpi devices */
+:root.hidpi .tabButton {
+ font-family: Metropolis, sans-serif;
+ font-weight: 600;
+ letter-spacing: 0.5px;
+ }
+
+/* touch-screen devices */
+:root.mobile #dashboard-nav {
+ flex-wrap: nowrap;
+ overflow-x: auto;
+ }
+:root.mobile #dashboard-nav .logo {
+ display: none;
+ }
diff --git a/platform/mv3/extension/css/filtering-mode.css b/platform/mv3/extension/css/filtering-mode.css
new file mode 100644
index 0000000..fecb1ac
--- /dev/null
+++ b/platform/mv3/extension/css/filtering-mode.css
@@ -0,0 +1,92 @@
+.filteringModeSlider {
+ align-items: center;
+ display: flex;
+ height: 60px;
+ justify-content: center;
+ position: relative;
+ width: 240px;
+ }
+
+.filteringModeButton {
+ background-color: var(--surface-1);
+ box-sizing: border-box;
+ border-radius: 30% 15% / 15% 30%;
+ height: 100%;
+ position: absolute;
+ width: 25%;
+ z-index: 10;
+ }
+
+.filteringModeButton > div {
+ background-color: var(--accent-surface-1);
+ border: 4px solid var(--accent-surface-1);
+ border-radius: inherit;
+ box-sizing: border-box;
+ height: calc(100% - 2px);
+ margin: 1px;
+ width: calc(100% - 2px);
+ }
+
+.filteringModeSlider.moving .filteringModeButton > div,
+.filteringModeButton > div:hover {
+ filter: brightness(0.9);
+ }
+
+.filteringModeSlider[data-level="0"] .filteringModeButton > div {
+ background-color: var(--surface-2);
+ border-color: var(--surface-2);
+ }
+
+.filteringModeSlider span[data-level] {
+ background-color: var(--accent-surface-1);
+ display: inline-flex;
+ height: 30%;
+ margin-left: 1px;
+ width: 25%;
+ }
+
+.filteringModeSlider.moving span[data-level] {
+ pointer-events: none;
+ }
+
+.filteringModeSlider[data-level="0"] .filteringModeButton {
+ left: 0;
+ }
+.filteringModeSlider[data-level="1"] .filteringModeButton {
+ left: 25%;
+ }
+.filteringModeSlider[data-level="2"] .filteringModeButton {
+ left: 50%;
+ }
+.filteringModeSlider[data-level="3"] .filteringModeButton {
+ left: 75%;
+ }
+[dir="rtl"] .filteringModeSlider[data-level="0"] .filteringModeButton {
+ left: 75%;
+ }
+[dir="rtl"] .filteringModeSlider[data-level="1"] .filteringModeButton {
+ left: 50%;
+ }
+[dir="rtl"] .filteringModeSlider[data-level="2"] .filteringModeButton {
+ left: 25%;
+ }
+[dir="rtl"] .filteringModeSlider[data-level="3"] .filteringModeButton {
+ left: 0;
+ }
+
+
+.filteringModeSlider[data-level="0"] span[data-level] {
+ background-color: var(--surface-2);
+ }
+
+.filteringModeSlider[data-level="1"] span[data-level]:nth-of-type(1) ~ span[data-level] {
+ background-color: var(--surface-2);
+ }
+
+.filteringModeSlider[data-level="2"] span[data-level]:nth-of-type(2) ~ span[data-level] {
+ background-color: var(--surface-2);
+ }
+
+.filteringModeSlider[data-level]:not(.moving) span[data-level]:hover {
+ filter: brightness(0.9);
+ }
diff --git a/platform/mv3/extension/css/popup.css b/platform/mv3/extension/css/popup.css
new file mode 100644
index 0000000..385a051
--- /dev/null
+++ b/platform/mv3/extension/css/popup.css
@@ -0,0 +1,276 @@
+ /* External CSS values override */
+.fa-icon.fa-icon-badged > .fa-icon-badge {
+ bottom: auto;
+ top: -20%;
+ }
+
+:root body,
+:root.mobile body {
+ --font-size: 14px;
+ --popup-gap: var(--font-size);
+ --popup-gap-thin: calc(0.5 * var(--popup-gap));
+ --popup-gap-extra-thin: calc(0.25 * var(--popup-gap));
+ --popup-main-min-width: 18em;
+ --popup-firewall-min-width: 30em;
+ --popup-rule-cell-width: 5em;
+ font-size: var(--font-size);
+ line-height: 20px;
+ min-width: 100%;
+ }
+:root body.loading {
+ opacity: 0;
+ }
+a {
+ color: var(--ink-1);
+ fill: var(--ink-1);
+ text-decoration: none;
+ }
+:focus {
+ outline: 0;
+ }
+
+#main {
+ align-self: flex-start;
+ display: flex;
+ flex-direction: column;
+ max-width: 340px;
+ min-width: 100%;
+ }
+:root.portrait #main {
+ align-self: inherit;
+ }
+hr {
+ border: 0;
+ border-top: 1px solid var(--hr-ink);
+ margin: 0;
+ padding: 0;
+ }
+
+#hostname {
+ align-items: center;
+ background-color: var(--popup-toolbar-surface);
+ display: flex;
+ justify-content: center;
+ min-height: calc(var(--font-size) * 3);
+ padding: 0 var(--popup-gap-extra-thin);
+ text-align: center;
+ word-break: break-all;
+ }
+#hostname > span {
+ word-break: break-all;
+ }
+#hostname > span + span {
+ font-weight: 600;
+ }
+
+#filteringModeText {
+ color: var(--ink-3);
+ margin: var(--default-gap-small);
+ margin-top: 0;
+ text-align: center;
+ text-transform: lowercase;
+ }
+#filteringModeText > span {
+ color: var(--accent-surface-1);
+ }
+#filteringModeText > span:nth-of-type(2) {
+ display: none;
+ }
+#filteringModeText > span:nth-of-type(2):not(:empty) {
+ display: inline;
+ }
+#filteringModeText > span:nth-of-type(2):not(:empty)::before {
+ content: '\2002\2192\2002';
+ }
+[dir="rtl"] #filteringModeText > span:nth-of-type(2):not(:empty)::before {
+ content: '\2002\2190\2002';
+ }
+
+.filteringModeSlider {
+ align-self: center;
+ margin: var(--popup-gap);
+ width: calc(var(--popup-main-min-width) - 1em);
+ }
+
+.rulesetTools {
+ background-color: transparent;
+ border: 0;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-evenly;
+ width: 25%;
+ }
+.rulesetTools [id] {
+ background-color: var(--popup-ruleset-tool-surface);
+ border-radius: 4px;
+ cursor: pointer;
+ fill: var(--popup-ruleset-tool-ink);
+ flex-grow: 1;
+ font-size: 2.2em;
+ padding: 0;
+ visibility: hidden;
+ }
+.rulesetTools [id]:not(:first-of-type) {
+ margin-block-start: 1px;
+ }
+.rulesetTools [id] > svg {
+ fill: var(--ink-4);
+ }
+body.needReload #refresh {
+ visibility: visible;
+ }
+
+#rulesetStats {
+ padding: 0 var(--popup-gap-thin);
+ }
+#rulesetStats .rulesetDetails h1 {
+ font-size: 1em;
+ font-weight: normal;
+ margin: 0.5em 0 0.25em 0;
+ }
+#rulesetStats .rulesetDetails p {
+ color: var(--ink-2);
+ font-size: var(--font-size-smaller);
+ margin: 0.25em 0 0.5em 0.5em;
+ }
+
+.itemRibbon {
+ column-gap: var(--popup-gap);
+ display: grid;
+ grid-auto-columns: 1fr;
+ grid-auto-flow: column;
+ grid-template: auto / 1fr 1fr;
+ margin: var(--popup-gap);
+ }
+.itemRibbon > span + span {
+ text-align: end;
+ }
+
+.toolRibbon {
+ align-items: center;
+ background-color: var(--popup-toolbar-surface);
+ display: grid;
+ grid-auto-columns: 1fr;
+ grid-auto-flow: column;
+ grid-template: auto / repeat(5, 1fr);
+ justify-items: center;
+ white-space: normal;
+ }
+.toolRibbon .tool {
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ font-size: 1.4em;
+ min-width: 32px;
+ padding: var(--popup-gap)
+ var(--popup-gap-thin);
+ unicode-bidi: embed;
+ visibility: hidden;
+ }
+.toolRibbon .tool:hover {
+ color: var(--ink-1);
+ fill: var(--ink-1);
+ }
+.toolRibbon .tool.enabled {
+ visibility: visible;
+ }
+.toolRibbon .tool .caption {
+ font: 10px/12px sans-serif;
+ margin-top: 6px;
+ text-align: center;
+ }
+body.mobile.no-tooltips .toolRibbon .tool {
+ font-size: 1.6em;
+ }
+.toolRibbon.genericTools {
+ margin-bottom: 0;
+ }
+
+#moreOrLess {
+ column-gap: 0;
+ display: grid;
+ grid-template: auto / 1fr 1fr;
+ justify-items: stretch;
+ margin: 1px 0 0 0;
+ }
+#moreOrLess > span {
+ cursor: pointer;
+ margin: 0;
+ padding: var(--popup-gap-thin) var(--popup-gap);
+ user-select: none;
+ white-space: nowrap;
+ }
+#moreButton .fa-icon {
+ transform: rotate(180deg);
+ }
+#lessButton {
+ border-inline-start: 1px solid var(--surface-1);
+ text-align: end;
+ }
+body[data-section="a b"] #moreButton {
+ pointer-events: none;
+ visibility: hidden;
+ }
+body[data-section=""] #lessButton {
+ pointer-events: none;
+ visibility: hidden;
+ }
+body:not([data-section~="a"]) [data-section="a"] {
+ display: none;
+ }
+body:not([data-section~="b"]) [data-section="b"] {
+ display: none;
+ }
+
+/* configurable UI elements */
+:root:not(.mobile) .toolRibbon .caption,
+:root.mobile body.no-tooltips .toolRibbon .caption,
+:root.mobile body[data-ui~="-captions"] .toolRibbon .caption {
+ display: none;
+ }
+:root.mobile .toolRibbon .caption,
+:root:not(.mobile) body[data-ui~="+captions"] .toolRibbon .caption {
+ display: inherit;
+ }
+:root:not(.mobile) .toolRibbon .tool,
+:root.mobile body.no-tooltips .toolRibbon .tool,
+:root.mobile body[data-ui~="-captions"] .toolRibbon .tool {
+ padding: var(--popup-gap) var(--popup-gap-thin);
+ }
+:root.mobile #moreOrLess > span {
+ padding: var(--popup-gap);
+ }
+
+/* horizontally-constrained viewport */
+:root.portrait body {
+ overflow-y: auto;
+ width: 100%;
+ }
+:root.portrait #main {
+ max-width: unset;
+ }
+/* mouse-driven devices */
+:root.desktop {
+ display: flex;
+ }
+:root.desktop body {
+ --popup-gap: calc(var(--font-size) * 0.875);
+ }
+:root.desktop .rulesetTools [id]:hover {
+ background-color: var(--popup-ruleset-tool-surface-hover);
+ }
+:root.desktop .rulesetTools [id]:hover > svg {
+ fill: var(--ink-2);
+ }
+:root.desktop .tool:hover {
+ background-color: var(--popup-toolbar-surface-hover);
+ }
+:root.desktop #moreOrLess > span:hover {
+ background-color: var(--surface-2);
+ /* background-color: var(--popup-toolbar-surface-hover); */
+ }
+
+#templates {
+ display: none;
+ }
diff --git a/platform/mv3/extension/css/settings.css b/platform/mv3/extension/css/settings.css
new file mode 100644
index 0000000..f03e276
--- /dev/null
+++ b/platform/mv3/extension/css/settings.css
@@ -0,0 +1,192 @@
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+legend {
+ color: var(--ink-3);
+ font-size: var(--font-size-smaller);
+ padding: var(--default-gap-xxsmall);
+ }
+body .firstRun {
+ display: none;
+ }
+body.firstRun .firstRun {
+ background-color: rgb(var(--dashboard-highlight-surface-rgb));
+ display: block;
+ padding: 8px;
+ }
+h3 {
+ margin: 1em 0;
+ }
+p {
+ white-space: pre-line;
+ }
+
+#defaultFilteringMode {
+ display: grid;
+ gap: 1em;
+ grid: auto-flow dense / 1fr 1fr 1fr;
+ }
+.filteringModeCard {
+ border: 1px solid var(--surface-3);
+ border-radius: 4px;
+ display: flex;
+ flex-direction: column;
+ }
+.filteringModeCard:has(.radio > [type="radio"]:checked) {
+ background-color: var(--surface-0);
+ }
+.filteringModeCard .input.radio ~ [data-i18n] {
+ text-transform: capitalize;
+ }
+.filteringModeCard span:has(> .input) {
+ align-items: center;
+ display: inline-flex;
+ }
+.filteringModeCard > div {
+ align-items: center;
+ box-sizing: border-box;
+ display: flex;
+ padding: 0.5em;
+ width: 100%;
+ }
+.filteringModeCard > div:nth-of-type(2) {
+ justify-content: center;
+ }
+.filteringModeCard > div:nth-of-type(3) {
+ border-top: 1px solid var(--surface-2);
+ font-size: var(--font-size-smaller);
+ white-space: pre-line;
+ }
+.filteringModeSlider {
+ height: calc(60px / 2);
+ pointer-events: none;
+ width: calc(240px / 2);
+ }
+
+h3[data-i18n="filteringMode0Name"]::first-letter {
+ text-transform: capitalize;
+ }
+#trustedSites {
+ box-sizing: border-box;
+ height: 6rem;
+ resize: vertical;
+ width: 100%;
+ }
+
+#lists {
+ margin: 0.5em 0 0 0;
+ padding: 0;
+ }
+.groupEntry:not([data-groupkey="user"]) .geDetails::before {
+ color: var(--ink-3);
+ content: '\2212';
+ font-family: monospace;
+ font-size: large;
+ margin-inline-end: 0.25em;
+ -webkit-margin-end: 0.25em;
+ }
+.groupEntry.hideUnused:not([data-groupkey="user"]) .geDetails::before {
+ content: '+';
+ }
+.groupEntry {
+ margin: 0.5em 0;
+ }
+.groupEntry .geDetails {
+ cursor: pointer;
+ }
+.groupEntry .geName {
+ pointer-events: none;
+ }
+.groupEntry .geCount {
+ color: var(--ink-3);
+ font-size: 90%;
+ pointer-events: none;
+ }
+.listEntries {
+ margin-inline-start: 0.6em;
+ -webkit-margin-start: 0.6em;
+ }
+.groupEntry:not([data-groupkey="user"]) .listEntry:not(.isDefault).unused {
+ display: none;
+ }
+.listEntry > * {
+ margin-left: 0;
+ margin-right: 0;
+ unicode-bidi: embed;
+ }
+.listEntry .listname {
+ white-space: nowrap;
+ }
+.listEntry a,
+.listEntry .fa-icon {
+ color: var(--info0-ink);
+ fill: var(--info0-ink);
+ display: none;
+ font-size: 120%;
+ margin: 0 0.2em 0 0;
+ }
+.listEntry .fa-icon:hover {
+ transform: scale(1.25);
+ }
+.listEntry .content {
+ display: inline-flex;
+ }
+.listEntry a.towiki {
+ display: inline-flex;
+ }
+.listEntry.support a.support {
+ display: inline-flex;
+ }
+.listEntry.mustread a.mustread {
+ color: var(--info1-ink);
+ fill: var(--info1-ink);
+ display: inline-flex;
+ }
+.listEntry .status {
+ cursor: default;
+ display: none;
+}
+
+body.noMoreRuleset .listEntry:not(.checked) {
+ opacity: 0.5;
+ pointer-events: none;
+}
+
+/* touch-screen devices */
+:root.mobile .listEntry .fa-icon {
+ font-size: 120%;
+ margin: 0 0.5em 0 0;
+ }
+:root.mobile .listEntries {
+ margin-inline-start: 0;
+ -webkit-margin-start: 0;
+ }
+:root.mobile .li.listEntry {
+ overflow-x: auto;
+ }
+:root.mobile .li.listEntry label > span:not([class]) {
+ flex-grow: 1;
+ }
+:root.mobile .li.listEntry .listname,
+:root.mobile .li.listEntry .iconbar {
+ align-items: flex-start;
+ display: flex;
+ white-space: nowrap;
+ }
+:root.mobile .li.listEntry .iconbar {
+ margin-top: 0.2em;
+ }
+
+#templates {
+ display: none;
+ }
+
+@media (max-width: 480px) {
+ #defaultFilteringMode {
+ grid: 1fr 1fr 1fr / auto-flow dense;
+ }
+ .filteringModeCard > div:nth-of-type(2) {
+ justify-content: flex-start;
+ }
+}
diff --git a/platform/mv3/extension/dashboard.html b/platform/mv3/extension/dashboard.html
new file mode 100644
index 0000000..698e1fb
--- /dev/null
+++ b/platform/mv3/extension/dashboard.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
+<title data-i18n="dashboardName"></title>
+<link rel="stylesheet" href="css/default.css">
+<link rel="stylesheet" href="css/common.css">
+<link rel="stylesheet" href="css/fa-icons.css">
+<link rel="stylesheet" href="css/dashboard.css">
+
+<link rel="stylesheet" href="css/dashboard-common.css">
+<link rel="stylesheet" href="css/filtering-mode.css">
+<link rel="stylesheet" href="css/settings.css">
+
+<link rel="shortcut icon" type="image/png" href="img/icon_64.png"/>
+</head>
+
+<body data-pane="settings">
+ <!-- -------- -->
+ <div id="dashboard-nav">
+ <span class="logo"><img data-i18n-title="extName" src="img/ublock.svg" alt="uBO Lite"></span><!--
+ --><button class="tabButton" type="button" data-pane="settings" data-i18n="settingsPageName" tabindex="0"></button><!--
+ --><button class="tabButton" type="button" data-pane="about" data-i18n="aboutPageName" tabindex="0"></button>
+ </div>
+ <!-- -------- -->
+ <section data-pane="settings">
+ <div class="firstRun">
+ <h3 data-i18n="firstRunSectionLabel"></h3>
+ <p data-i18n="firstRunDescription"></p>
+ </div>
+
+ <div>
+ <h3 data-i18n="defaultFilteringModeSectionLabel"></h3>
+ <p data-i18n="defaultFilteringModeDescription"></p>
+ <div id="defaultFilteringMode">
+ <label class="filteringModeCard">
+ <div>
+ <span><span class="input radio"><input type="radio" name="filteringMode" value="1"><svg viewBox="0 0 24 24"><path d="M 12 0 A 12 12 0 0 0 0 12 A 12 12 0 0 0 12 24 A 12 12 0 0 0 24 12 A 12 12 0 0 0 12 0 z M 12 2.5 A 9.5 9.5 0 0 1 21.5 12 A 9.5 9.5 0 0 1 12 21.5 A 9.5 9.5 0 0 1 2.5 12 A 9.5 9.5 0 0 1 12 2.5 z"/><circle cx="12" cy="12" r="7"/></svg></span><span data-i18n="filteringMode1Name">_</span></span>
+ </div>
+ <div>
+ <div class="filteringModeSlider" data-level="1">
+ <div class="filteringModeButton"><div></div></div>
+ <span data-level="0"></span>
+ <span data-level="1"></span>
+ <span data-level="2"></span>
+ <span data-level="3"></span>
+ </div>
+ </div>
+ <div data-i18n="basicFilteringModeDescription"></div>
+ </label>
+ <label class="filteringModeCard">
+ <div>
+ <span><span class="input radio"><input type="radio" name="filteringMode" value="2"><svg viewBox="0 0 24 24"><path d="M 12 0 A 12 12 0 0 0 0 12 A 12 12 0 0 0 12 24 A 12 12 0 0 0 24 12 A 12 12 0 0 0 12 0 z M 12 2.5 A 9.5 9.5 0 0 1 21.5 12 A 9.5 9.5 0 0 1 12 21.5 A 9.5 9.5 0 0 1 2.5 12 A 9.5 9.5 0 0 1 12 2.5 z"/><circle cx="12" cy="12" r="7"/></svg></span><span data-i18n="filteringMode2Name">_</span></span>
+ </div>
+ <div>
+ <div class="filteringModeSlider" data-level="2">
+ <div class="filteringModeButton"><div></div></div>
+ <span data-level="0"></span>
+ <span data-level="1"></span>
+ <span data-level="2"></span>
+ <span data-level="3"></span>
+ </div>
+ </div>
+ <div data-i18n="optimalFilteringModeDescription"></div>
+ </label>
+ <label class="filteringModeCard">
+ <div>
+ <span><span class="input radio"><input type="radio" name="filteringMode" value="3"><svg viewBox="0 0 24 24"><path d="M 12 0 A 12 12 0 0 0 0 12 A 12 12 0 0 0 12 24 A 12 12 0 0 0 24 12 A 12 12 0 0 0 12 0 z M 12 2.5 A 9.5 9.5 0 0 1 21.5 12 A 9.5 9.5 0 0 1 12 21.5 A 9.5 9.5 0 0 1 2.5 12 A 9.5 9.5 0 0 1 12 2.5 z"/><circle cx="12" cy="12" r="7"/></svg></span><span data-i18n="filteringMode3Name">_</span></span>
+ </div>
+ <div>
+ <div class="filteringModeSlider" data-level="3">
+ <div class="filteringModeButton"><div></div></div>
+ <span data-level="0"></span>
+ <span data-level="1"></span>
+ <span data-level="2"></span>
+ <span data-level="3"></span>
+ </div>
+ </div>
+ <div data-i18n="completeFilteringModeDescription"></div>
+ </label>
+ </div>
+ </div>
+
+ <div>
+ <h3 data-i18n="filteringMode0Name"></h3>
+ <p data-i18n="noFilteringModeDescription">_</p>
+ <p><textarea id="trustedSites" spellcheck="false"></textarea>
+ </p>
+ </div>
+
+ <div>
+ <h3 data-i18n="behaviorSectionLabel"></h3>
+ <p><label id="autoReload" data-i18n="autoReloadLabel"><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span>_</label>
+ </p>
+ </div>
+
+ <div>
+ <h3 data-i18n="aboutFilterLists"></h3>
+ <div>
+ <p id="listsOfBlockedHostsPrompt"></p>
+ </div>
+ <div>
+ <div id="lists"></div>
+ </div>
+ </div>
+
+ <div id="templates">
+ <div class="groupEntry">
+ <div class="geDetails"><span class="geName"></span> <span class="geCount"></span></div>
+ <div class="listEntries"></div>
+ </div>
+ <div class="li listEntry">
+ <label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span><span class="listname forinput"></span> <span class="iconbar"><!--
+ --><a class="fa-icon support" href="#" target="_blank">home</a><!--
+ --><a class="fa-icon mustread" href="#" target="_blank">info-circle</a><!--
+ --></span></span></label>
+ </div>
+ </div>
+ </section>
+ <!-- -------- -->
+ <section data-pane="about">
+ <div class="body">
+ <div id="aboutNameVer" class="li"></div>
+ <div class="liul">
+ <div class="li">Copyright (c) Raymond Hill 2014-present</div>
+ </div>
+ <div class="li"><a href="https://github.com/gorhill/uBlock/wiki/Privacy-policy" data-i18n="aboutPrivacyPolicy"></a></div>
+ <div class="li"><a href="https://github.com/uBlockOrigin/uBOL-home/releases" data-i18n="aboutChangelog"></a></div>
+ <div class="li"><a href="https://github.com/gorhill/uBlock" data-i18n="aboutCode"></a></div>
+ <div class="li"><span data-i18n="aboutContributors"></span></div>
+ <div class="liul">
+ <div class="li"><a href="https://github.com/gorhill/uBlock/graphs/contributors" data-i18n="aboutSourceCode"></a></div>
+ <div class="li"><a href="https://crowdin.com/project/ublock" data-i18n="aboutTranslations"></a></div>
+ <div class="li"><a href="https://github.com/uBlockOrigin/uAssets/graphs/contributors" data-i18n="aboutFilterLists"></a></div>
+ </div>
+ <div class="li"><span data-i18n="aboutDependencies"></span></div>
+ <div class="liul">
+ <div class="li"><span><a href="https://github.com/chrismsimpson/Metropolis" target="_blank">Metropolis font family</a> by <a href="https://github.com/chrismsimpson">Chris Simpson</a></span></div>
+ <div class="li"><span><a href="https://github.com/rsms/inter" target="_blank">Inter font family</a> by <a href="https://github.com/rsms">Rasmus Andersson</a></span></div>
+ <div class="li"><span><a href="https://fontawesome.com/" target="_blank">FontAwesome font family</a> by <a href="https://github.com/davegandy">Dave Gandy</a></span></div>
+ <div class="li"><span><a href="https://github.com/mathiasbynens/punycode.js" target="_blank">Punycode.js</a> by <a href="https://github.com/mathiasbynens">Mathias Bynens</a></span></div>
+ <div class="li"><span><a href="https://flagpedia.net/" target="_blank">Flags of the World</a> by <a href="https://www.davidkrmela.com/">David Krmela</a></span></div>
+ </div>
+ </div>
+ </section>
+ <!-- -------- -->
+<script src="js/theme.js" type="module"></script>
+<script src="js/fa-icons.js" type="module"></script>
+<script src="js/i18n.js" type="module"></script>
+<script src="js/dashboard.js" type="module"></script>
+<script src="js/settings.js" type="module"></script>
+
+</body>
+</html>
diff --git a/platform/mv3/extension/img/icon_128.png b/platform/mv3/extension/img/icon_128.png
new file mode 100644
index 0000000..9824fa8
--- /dev/null
+++ b/platform/mv3/extension/img/icon_128.png
Binary files differ
diff --git a/platform/mv3/extension/img/icon_16.png b/platform/mv3/extension/img/icon_16.png
new file mode 100644
index 0000000..2bf29ef
--- /dev/null
+++ b/platform/mv3/extension/img/icon_16.png
Binary files differ
diff --git a/platform/mv3/extension/img/icon_32.png b/platform/mv3/extension/img/icon_32.png
new file mode 100644
index 0000000..7c8a455
--- /dev/null
+++ b/platform/mv3/extension/img/icon_32.png
Binary files differ
diff --git a/platform/mv3/extension/img/icon_64.png b/platform/mv3/extension/img/icon_64.png
new file mode 100644
index 0000000..2cf0a62
--- /dev/null
+++ b/platform/mv3/extension/img/icon_64.png
Binary files differ
diff --git a/platform/mv3/extension/img/ublock.svg b/platform/mv3/extension/img/ublock.svg
new file mode 100644
index 0000000..28e8f06
--- /dev/null
+++ b/platform/mv3/extension/img/ublock.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ viewBox="0 0 128 128"
+ height="128"
+ width="128"
+ id="svg86"
+ sodipodi:docname="ublock.svg"
+ inkscape:export-filename="../../platform/mv3/extension/img/icon_16.png"
+ inkscape:export-xdpi="12"
+ inkscape:export-ydpi="12"
+ inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs90" />
+ <sodipodi:namedview
+ id="namedview88"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ showgrid="true"
+ inkscape:zoom="5.6734271"
+ inkscape:cx="-1.6744729"
+ inkscape:cy="76.232583"
+ inkscape:window-width="2560"
+ inkscape:window-height="1377"
+ inkscape:window-x="0"
+ inkscape:window-y="40"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg86">
+ <inkscape:grid
+ type="xygrid"
+ id="grid250"
+ spacingx="1"
+ spacingy="1"
+ empspacing="8" />
+ </sodipodi:namedview>
+ <g
+ style="display:inline;opacity:1"
+ id="g76">
+ <g
+ style="fill:#800000;fill-opacity:1;stroke:#ffffff;stroke-width:1.62100744;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+ transform="matrix(0.6778654,0,0,0.56141828,-241.07537,-247.27712)"
+ id="g70" />
+ <g
+ transform="matrix(-0.6945203,0,0,0.56109687,375.02964,-247.42947)"
+ style="fill:#800000;fill-opacity:1;stroke:#ffffff;stroke-width:1.60191178000000001;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline;stroke-linejoin:round"
+ id="g74">
+ <path
+ d="m 447.83376,669.09921 c -80.63119,-57.03115 -80.63119,-57.03115 -80.63119,-199.60903 34.55623,0 46.07497,0 80.63119,-28.51558 m 0,228.12461 c 80.6312,-57.03115 80.6312,-57.03115 80.6312,-199.60903 -34.55623,0 -46.07497,0 -80.6312,-28.51558"
+ style="fill:#800000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1.60191178;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path72" />
+ </g>
+ </g>
+ <rect
+ style="fill:#fefefe;fill-opacity:1;stroke-width:0.550132"
+ id="rect304"
+ width="63.999996"
+ height="12"
+ x="32"
+ y="58" />
+</svg>
diff --git a/platform/mv3/extension/js/background.js b/platform/mv3/extension/js/background.js
new file mode 100644
index 0000000..5ceacc4
--- /dev/null
+++ b/platform/mv3/extension/js/background.js
@@ -0,0 +1,354 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2022-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+import {
+ browser,
+ dnr,
+ runtime,
+ localRead, localWrite,
+ sessionRead, sessionWrite,
+ adminRead,
+} from './ext.js';
+
+import {
+ getRulesetDetails,
+ defaultRulesetsFromLanguage,
+ enableRulesets,
+ getEnabledRulesetsDetails,
+ updateDynamicRules,
+} from './ruleset-manager.js';
+
+import {
+ registerInjectables,
+} from './scripting-manager.js';
+
+import {
+ getFilteringMode,
+ setFilteringMode,
+ getDefaultFilteringMode,
+ setDefaultFilteringMode,
+ getTrustedSites,
+ setTrustedSites,
+ syncWithBrowserPermissions,
+} from './mode-manager.js';
+
+import {
+ broadcastMessage,
+ ubolLog,
+} from './utils.js';
+
+/******************************************************************************/
+
+const rulesetConfig = {
+ version: '',
+ enabledRulesets: [ 'default' ],
+ autoReload: true,
+};
+
+const UBOL_ORIGIN = runtime.getURL('').replace(/\/$/, '');
+
+let firstRun = false;
+let wakeupRun = false;
+
+/******************************************************************************/
+
+function getCurrentVersion() {
+ return runtime.getManifest().version;
+}
+
+async function loadRulesetConfig() {
+ let data = await sessionRead('rulesetConfig');
+ if ( data ) {
+ rulesetConfig.version = data.version;
+ rulesetConfig.enabledRulesets = data.enabledRulesets;
+ rulesetConfig.autoReload = data.autoReload && true || false;
+ wakeupRun = true;
+ return;
+ }
+ data = await localRead('rulesetConfig');
+ if ( data ) {
+ rulesetConfig.version = data.version;
+ rulesetConfig.enabledRulesets = data.enabledRulesets;
+ rulesetConfig.autoReload = data.autoReload && true || false;
+ sessionWrite('rulesetConfig', rulesetConfig);
+ return;
+ }
+ rulesetConfig.enabledRulesets = await defaultRulesetsFromLanguage();
+ sessionWrite('rulesetConfig', rulesetConfig);
+ localWrite('rulesetConfig', rulesetConfig);
+ firstRun = true;
+}
+
+async function saveRulesetConfig() {
+ sessionWrite('rulesetConfig', rulesetConfig);
+ return localWrite('rulesetConfig', rulesetConfig);
+}
+
+/******************************************************************************/
+
+async function hasGreatPowers(origin) {
+ if ( /^https?:\/\//.test(origin) === false ) { return false; }
+ return browser.permissions.contains({
+ origins: [ `${origin}/*` ],
+ });
+}
+
+function hasOmnipotence() {
+ return browser.permissions.contains({
+ origins: [ '<all_urls>' ],
+ });
+}
+
+async function onPermissionsRemoved() {
+ const beforeMode = await getDefaultFilteringMode();
+ const modified = await syncWithBrowserPermissions();
+ if ( modified === false ) { return false; }
+ const afterMode = await getDefaultFilteringMode();
+ if ( beforeMode > 1 && afterMode <= 1 ) {
+ updateDynamicRules();
+ }
+ registerInjectables();
+ return true;
+}
+
+/******************************************************************************/
+
+function onMessage(request, sender, callback) {
+
+ // Does not require trusted origin.
+
+ switch ( request.what ) {
+
+ case 'insertCSS': {
+ const tabId = sender?.tab?.id ?? false;
+ const frameId = sender?.frameId ?? false;
+ if ( tabId === false || frameId === false ) { return; }
+ browser.scripting.insertCSS({
+ css: request.css,
+ origin: 'USER',
+ target: { tabId, frameIds: [ frameId ] },
+ }).catch(reason => {
+ console.log(reason);
+ });
+ return false;
+ }
+
+ default:
+ break;
+ }
+
+ // Does require trusted origin.
+
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/MessageSender
+ // Firefox API does not set `sender.origin`
+ if ( sender.origin !== undefined && sender.origin !== UBOL_ORIGIN ) { return; }
+
+ switch ( request.what ) {
+
+ case 'applyRulesets': {
+ enableRulesets(request.enabledRulesets).then(( ) => {
+ rulesetConfig.enabledRulesets = request.enabledRulesets;
+ return saveRulesetConfig();
+ }).then(( ) => {
+ registerInjectables();
+ callback();
+ broadcastMessage({ enabledRulesets: rulesetConfig.enabledRulesets });
+ });
+ return true;
+ }
+
+ case 'getOptionsPageData': {
+ Promise.all([
+ getDefaultFilteringMode(),
+ getTrustedSites(),
+ getRulesetDetails(),
+ dnr.getEnabledRulesets(),
+ ]).then(results => {
+ const [
+ defaultFilteringMode,
+ trustedSites,
+ rulesetDetails,
+ enabledRulesets,
+ ] = results;
+ callback({
+ defaultFilteringMode,
+ trustedSites: Array.from(trustedSites),
+ enabledRulesets,
+ maxNumberOfEnabledRulesets: dnr.MAX_NUMBER_OF_ENABLED_STATIC_RULESETS,
+ rulesetDetails: Array.from(rulesetDetails.values()),
+ autoReload: rulesetConfig.autoReload,
+ firstRun,
+ });
+ firstRun = false;
+ });
+ return true;
+ }
+
+ case 'setAutoReload':
+ rulesetConfig.autoReload = request.state && true || false;
+ saveRulesetConfig().then(( ) => {
+ callback();
+ broadcastMessage({ autoReload: rulesetConfig.autoReload });
+ });
+ return true;
+
+ case 'popupPanelData': {
+ Promise.all([
+ getFilteringMode(request.hostname),
+ hasOmnipotence(),
+ hasGreatPowers(request.origin),
+ getEnabledRulesetsDetails(),
+ ]).then(results => {
+ callback({
+ level: results[0],
+ autoReload: rulesetConfig.autoReload,
+ hasOmnipotence: results[1],
+ hasGreatPowers: results[2],
+ rulesetDetails: results[3],
+ });
+ });
+ return true;
+ }
+
+ case 'getFilteringMode': {
+ getFilteringMode(request.hostname).then(actualLevel => {
+ callback(actualLevel);
+ });
+ return true;
+ }
+
+ case 'setFilteringMode': {
+ getFilteringMode(request.hostname).then(actualLevel => {
+ if ( request.level === actualLevel ) { return actualLevel; }
+ return setFilteringMode(request.hostname, request.level);
+ }).then(actualLevel => {
+ registerInjectables();
+ callback(actualLevel);
+ });
+ return true;
+ }
+
+ case 'setDefaultFilteringMode': {
+ getDefaultFilteringMode().then(beforeLevel =>
+ setDefaultFilteringMode(request.level).then(afterLevel =>
+ ({ beforeLevel, afterLevel })
+ )
+ ).then(({ beforeLevel, afterLevel }) => {
+ if ( beforeLevel === 1 || afterLevel === 1 ) {
+ updateDynamicRules();
+ }
+ if ( afterLevel !== beforeLevel ) {
+ registerInjectables();
+ }
+ callback(afterLevel);
+ });
+ return true;
+ }
+
+ case 'setTrustedSites':
+ setTrustedSites(request.hostnames).then(( ) => {
+ registerInjectables();
+ return Promise.all([
+ getDefaultFilteringMode(),
+ getTrustedSites(),
+ ]);
+ }).then(results => {
+ callback({
+ defaultFilteringMode: results[0],
+ trustedSites: Array.from(results[1]),
+ });
+ });
+ return true;
+
+ default:
+ break;
+ }
+}
+
+/******************************************************************************/
+
+async function start() {
+ await loadRulesetConfig();
+
+ if ( wakeupRun === false ) {
+ await enableRulesets(rulesetConfig.enabledRulesets);
+ }
+
+ // We need to update the regex rules only when ruleset version changes.
+ if ( wakeupRun === false ) {
+ const currentVersion = getCurrentVersion();
+ if ( currentVersion !== rulesetConfig.version ) {
+ ubolLog(`Version change: ${rulesetConfig.version} => ${currentVersion}`);
+ updateDynamicRules().then(( ) => {
+ rulesetConfig.version = currentVersion;
+ saveRulesetConfig();
+ });
+ }
+ }
+
+ // Permissions may have been removed while the extension was disabled
+ const permissionsChanged = await onPermissionsRemoved();
+
+ // Unsure whether the browser remembers correctly registered css/scripts
+ // after we quit the browser. For now uBOL will check unconditionally at
+ // launch time whether content css/scripts are properly registered.
+ if ( wakeupRun === false || permissionsChanged ) {
+ registerInjectables();
+
+ const enabledRulesets = await dnr.getEnabledRulesets();
+ ubolLog(`Enabled rulesets: ${enabledRulesets}`);
+
+ dnr.getAvailableStaticRuleCount().then(count => {
+ ubolLog(`Available static rule count: ${count}`);
+ });
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest
+ // Firefox API does not support `dnr.setExtensionActionOptions`
+ if ( wakeupRun === false && dnr.setExtensionActionOptions ) {
+ dnr.setExtensionActionOptions({ displayActionCountAsBadgeText: true });
+ }
+
+ runtime.onMessage.addListener(onMessage);
+
+ browser.permissions.onRemoved.addListener(
+ ( ) => { onPermissionsRemoved(); }
+ );
+
+ if ( firstRun ) {
+ const disableFirstRunPage = await adminRead('disableFirstRunPage');
+ if ( disableFirstRunPage !== true ) {
+ runtime.openOptionsPage();
+ }
+ }
+}
+
+try {
+ start();
+} catch(reason) {
+ console.trace(reason);
+}
diff --git a/platform/mv3/extension/js/dashboard.js b/platform/mv3/extension/js/dashboard.js
new file mode 100644
index 0000000..363f301
--- /dev/null
+++ b/platform/mv3/extension/js/dashboard.js
@@ -0,0 +1,40 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+import { runtime } from './ext.js';
+import { dom } from './dom.js';
+
+/******************************************************************************/
+
+{
+ const manifest = runtime.getManifest();
+ dom.text('#aboutNameVer', `${manifest.name} ${manifest.version}`);
+}
+
+dom.attr('a', 'target', '_blank');
+
+dom.on('#dashboard-nav', 'click', '.tabButton', ev => {
+ dom.body.dataset.pane = ev.target.dataset.pane;
+});
+
+/******************************************************************************/
diff --git a/platform/mv3/extension/js/ext.js b/platform/mv3/extension/js/ext.js
new file mode 100644
index 0000000..185fa9c
--- /dev/null
+++ b/platform/mv3/extension/js/ext.js
@@ -0,0 +1,119 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2022-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+export const browser =
+ self.browser instanceof Object &&
+ self.browser instanceof Element === false
+ ? self.browser
+ : self.chrome;
+
+export const dnr = browser.declarativeNetRequest;
+export const i18n = browser.i18n;
+export const runtime = browser.runtime;
+
+/******************************************************************************/
+
+// The extension's service worker can be evicted at any time, so when we
+// send a message, we try a few more times when the message fails to be sent.
+
+export function sendMessage(msg) {
+ return new Promise((resolve, reject) => {
+ let i = 5;
+ const send = ( ) => {
+ runtime.sendMessage(msg).then(response => {
+ resolve(response);
+ }).catch(reason => {
+ i -= 1;
+ if ( i <= 0 ) {
+ reject(reason);
+ } else {
+ setTimeout(send, 200);
+ }
+ });
+ };
+ send();
+ });
+}
+
+/******************************************************************************/
+
+export async function localRead(key) {
+ if ( browser.storage instanceof Object === false ) { return; }
+ if ( browser.storage.local instanceof Object === false ) { return; }
+ try {
+ const bin = await browser.storage.local.get(key);
+ if ( bin instanceof Object === false ) { return; }
+ return bin[key] ?? undefined;
+ } catch(ex) {
+ }
+}
+
+export async function localWrite(key, value) {
+ if ( browser.storage instanceof Object === false ) { return; }
+ if ( browser.storage.local instanceof Object === false ) { return; }
+ return browser.storage.local.set({ [key]: value });
+}
+
+export async function localRemove(key) {
+ if ( browser.storage instanceof Object === false ) { return; }
+ if ( browser.storage.local instanceof Object === false ) { return; }
+ return browser.storage.local.remove(key);
+}
+
+/******************************************************************************/
+
+export async function sessionRead(key) {
+ if ( browser.storage instanceof Object === false ) { return; }
+ if ( browser.storage.session instanceof Object === false ) { return; }
+ try {
+ const bin = await browser.storage.session.get(key);
+ if ( bin instanceof Object === false ) { return; }
+ return bin[key] ?? undefined;
+ } catch(ex) {
+ }
+}
+
+export async function sessionWrite(key, value) {
+ if ( browser.storage instanceof Object === false ) { return; }
+ if ( browser.storage.session instanceof Object === false ) { return; }
+ return browser.storage.session.set({ [key]: value });
+}
+
+/******************************************************************************/
+
+export async function adminRead(key) {
+ if ( browser.storage instanceof Object === false ) { return; }
+ if ( browser.storage.local instanceof Object === false ) { return; }
+ try {
+ const bin = await browser.storage.managed.get(key);
+ if ( bin instanceof Object === false ) { return; }
+ return bin[key] ?? undefined;
+ } catch(ex) {
+ }
+}
+
+/******************************************************************************/
diff --git a/platform/mv3/extension/js/fetch.js b/platform/mv3/extension/js/fetch.js
new file mode 100644
index 0000000..5570159
--- /dev/null
+++ b/platform/mv3/extension/js/fetch.js
@@ -0,0 +1,38 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2022-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+function fetchJSON(path) {
+ return fetch(`${path}.json`).then(response =>
+ response.json()
+ ).catch(reason => {
+ console.info(reason);
+ });
+}
+
+/******************************************************************************/
+
+export { fetchJSON };
diff --git a/platform/mv3/extension/js/mode-manager.js b/platform/mv3/extension/js/mode-manager.js
new file mode 100644
index 0000000..bc76199
--- /dev/null
+++ b/platform/mv3/extension/js/mode-manager.js
@@ -0,0 +1,426 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2022-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+import {
+ browser,
+ dnr,
+ localRead, localWrite, localRemove,
+ sessionRead, sessionWrite,
+ adminRead,
+} from './ext.js';
+
+import {
+ broadcastMessage,
+ hostnamesFromMatches,
+ isDescendantHostnameOfIter,
+ toBroaderHostname,
+} from './utils.js';
+
+import {
+ TRUSTED_DIRECTIVE_BASE_RULE_ID,
+ getDynamicRules
+} from './ruleset-manager.js';
+
+/******************************************************************************/
+
+// 0: no filtering
+// 1: basic filtering
+// 2: optimal filtering
+// 3: complete filtering
+
+export const MODE_NONE = 0;
+export const MODE_BASIC = 1;
+export const MODE_OPTIMAL = 2;
+export const MODE_COMPLETE = 3;
+
+/******************************************************************************/
+
+const pruneDescendantHostnamesFromSet = (hostname, hnSet) => {
+ for ( const hn of hnSet ) {
+ if ( hn.endsWith(hostname) === false ) { continue; }
+ if ( hn === hostname ) { continue; }
+ if ( hn.at(-hostname.length-1) !== '.' ) { continue; }
+ hnSet.delete(hn);
+ }
+};
+
+const pruneHostnameFromSet = (hostname, hnSet) => {
+ let hn = hostname;
+ for (;;) {
+ hnSet.delete(hn);
+ hn = toBroaderHostname(hn);
+ if ( hn === '*' ) { break; }
+ }
+};
+
+/******************************************************************************/
+
+const eqSets = (setBefore, setAfter) => {
+ for ( const hn of setAfter ) {
+ if ( setBefore.has(hn) === false ) { return false; }
+ }
+ for ( const hn of setBefore ) {
+ if ( setAfter.has(hn) === false ) { return false; }
+ }
+ return true;
+};
+
+/******************************************************************************/
+
+const serializeModeDetails = details => {
+ return {
+ none: Array.from(details.none),
+ basic: Array.from(details.basic),
+ optimal: Array.from(details.optimal),
+ complete: Array.from(details.complete),
+ };
+};
+
+const unserializeModeDetails = details => {
+ return {
+ none: new Set(details.none),
+ basic: new Set(details.basic ?? details.network),
+ optimal: new Set(details.optimal ?? details.extendedSpecific),
+ complete: new Set(details.complete ?? details.extendedGeneric),
+ };
+};
+
+/******************************************************************************/
+
+function lookupFilteringMode(filteringModes, hostname) {
+ const { none, basic, optimal, complete } = filteringModes;
+ if ( hostname === 'all-urls' ) {
+ if ( filteringModes.none.has('all-urls') ) { return MODE_NONE; }
+ if ( filteringModes.basic.has('all-urls') ) { return MODE_BASIC; }
+ if ( filteringModes.optimal.has('all-urls') ) { return MODE_OPTIMAL; }
+ if ( filteringModes.complete.has('all-urls') ) { return MODE_COMPLETE; }
+ return MODE_BASIC;
+ }
+ if ( none.has(hostname) ) { return MODE_NONE; }
+ if ( none.has('all-urls') === false ) {
+ if ( isDescendantHostnameOfIter(hostname, none) ) { return MODE_NONE; }
+ }
+ if ( basic.has(hostname) ) { return MODE_BASIC; }
+ if ( basic.has('all-urls') === false ) {
+ if ( isDescendantHostnameOfIter(hostname, basic) ) { return MODE_BASIC; }
+ }
+ if ( optimal.has(hostname) ) { return MODE_OPTIMAL; }
+ if ( optimal.has('all-urls') === false ) {
+ if ( isDescendantHostnameOfIter(hostname, optimal) ) { return MODE_OPTIMAL; }
+ }
+ if ( complete.has(hostname) ) { return MODE_COMPLETE; }
+ if ( complete.has('all-urls') === false ) {
+ if ( isDescendantHostnameOfIter(hostname, complete) ) { return MODE_COMPLETE; }
+ }
+ return lookupFilteringMode(filteringModes, 'all-urls');
+}
+
+/******************************************************************************/
+
+function applyFilteringMode(filteringModes, hostname, afterLevel) {
+ const defaultLevel = lookupFilteringMode(filteringModes, 'all-urls');
+ if ( hostname === 'all-urls' ) {
+ if ( afterLevel === defaultLevel ) { return afterLevel; }
+ switch ( afterLevel ) {
+ case MODE_NONE:
+ filteringModes.none.clear();
+ filteringModes.none.add('all-urls');
+ break;
+ case MODE_BASIC:
+ filteringModes.basic.clear();
+ filteringModes.basic.add('all-urls');
+ break;
+ case MODE_OPTIMAL:
+ filteringModes.optimal.clear();
+ filteringModes.optimal.add('all-urls');
+ break;
+ case MODE_COMPLETE:
+ filteringModes.complete.clear();
+ filteringModes.complete.add('all-urls');
+ break;
+ }
+ switch ( defaultLevel ) {
+ case MODE_NONE:
+ filteringModes.none.delete('all-urls');
+ break;
+ case MODE_BASIC:
+ filteringModes.basic.delete('all-urls');
+ break;
+ case MODE_OPTIMAL:
+ filteringModes.optimal.delete('all-urls');
+ break;
+ case MODE_COMPLETE:
+ filteringModes.complete.delete('all-urls');
+ break;
+ }
+ return lookupFilteringMode(filteringModes, 'all-urls');
+ }
+ const beforeLevel = lookupFilteringMode(filteringModes, hostname);
+ if ( afterLevel === beforeLevel ) { return afterLevel; }
+ const { none, basic, optimal, complete } = filteringModes;
+ switch ( beforeLevel ) {
+ case MODE_NONE:
+ pruneHostnameFromSet(hostname, none);
+ break;
+ case MODE_BASIC:
+ pruneHostnameFromSet(hostname, basic);
+ break;
+ case MODE_OPTIMAL:
+ pruneHostnameFromSet(hostname, optimal);
+ break;
+ case MODE_COMPLETE:
+ pruneHostnameFromSet(hostname, complete);
+ break;
+ }
+ if ( afterLevel !== defaultLevel ) {
+ switch ( afterLevel ) {
+ case MODE_NONE:
+ if ( isDescendantHostnameOfIter(hostname, none) === false ) {
+ filteringModes.none.add(hostname);
+ pruneDescendantHostnamesFromSet(hostname, none);
+ }
+ break;
+ case MODE_BASIC:
+ if ( isDescendantHostnameOfIter(hostname, basic) === false ) {
+ filteringModes.basic.add(hostname);
+ pruneDescendantHostnamesFromSet(hostname, basic);
+ }
+ break;
+ case MODE_OPTIMAL:
+ if ( isDescendantHostnameOfIter(hostname, optimal) === false ) {
+ filteringModes.optimal.add(hostname);
+ pruneDescendantHostnamesFromSet(hostname, optimal);
+ }
+ break;
+ case MODE_COMPLETE:
+ if ( isDescendantHostnameOfIter(hostname, complete) === false ) {
+ filteringModes.complete.add(hostname);
+ pruneDescendantHostnamesFromSet(hostname, complete);
+ }
+ break;
+ }
+ }
+ return lookupFilteringMode(filteringModes, hostname);
+}
+
+/******************************************************************************/
+
+async function readFilteringModeDetails() {
+ if ( readFilteringModeDetails.cache ) {
+ return readFilteringModeDetails.cache;
+ }
+ const sessionModes = await sessionRead('filteringModeDetails');
+ if ( sessionModes instanceof Object ) {
+ readFilteringModeDetails.cache = unserializeModeDetails(sessionModes);
+ return readFilteringModeDetails.cache;
+ }
+ let [ userModes, adminNoFiltering ] = await Promise.all([
+ localRead('filteringModeDetails'),
+ localRead('adminNoFiltering'),
+ ]);
+ if ( userModes === undefined ) {
+ userModes = { basic: [ 'all-urls' ] };
+ }
+ userModes = unserializeModeDetails(userModes);
+ if ( Array.isArray(adminNoFiltering) ) {
+ for ( const hn of adminNoFiltering ) {
+ applyFilteringMode(userModes, hn, 0);
+ }
+ }
+ filteringModesToDNR(userModes);
+ sessionWrite('filteringModeDetails', serializeModeDetails(userModes));
+ readFilteringModeDetails.cache = userModes;
+ adminRead('noFiltering').then(results => {
+ if ( results ) {
+ localWrite('adminNoFiltering', results);
+ } else {
+ localRemove('adminNoFiltering');
+ }
+ });
+ return userModes;
+}
+
+/******************************************************************************/
+
+async function writeFilteringModeDetails(afterDetails) {
+ await filteringModesToDNR(afterDetails);
+ const data = serializeModeDetails(afterDetails);
+ localWrite('filteringModeDetails', data);
+ sessionWrite('filteringModeDetails', data);
+ readFilteringModeDetails.cache = unserializeModeDetails(data);
+
+ Promise.all([
+ getDefaultFilteringMode(),
+ getTrustedSites(),
+ ]).then(results => {
+ broadcastMessage({
+ defaultFilteringMode: results[0],
+ trustedSites: Array.from(results[1]),
+ });
+ });
+}
+
+/******************************************************************************/
+
+async function filteringModesToDNR(modes) {
+ const dynamicRuleMap = await getDynamicRules();
+ const presentRule = dynamicRuleMap.get(TRUSTED_DIRECTIVE_BASE_RULE_ID);
+ const presentNone = new Set(
+ presentRule && presentRule.condition.requestDomains
+ );
+ if ( eqSets(presentNone, modes.none) ) { return; }
+ const removeRuleIds = [];
+ if ( presentRule !== undefined ) {
+ removeRuleIds.push(TRUSTED_DIRECTIVE_BASE_RULE_ID);
+ dynamicRuleMap.delete(TRUSTED_DIRECTIVE_BASE_RULE_ID);
+ }
+ const addRules = [];
+ if ( modes.none.size !== 0 ) {
+ const rule = {
+ id: TRUSTED_DIRECTIVE_BASE_RULE_ID,
+ action: { type: 'allowAllRequests' },
+ condition: {
+ resourceTypes: [ 'main_frame' ],
+ },
+ priority: 100,
+ };
+ if (
+ modes.none.size !== 1 ||
+ modes.none.has('all-urls') === false
+ ) {
+ rule.condition.requestDomains = Array.from(modes.none);
+ }
+ addRules.push(rule);
+ dynamicRuleMap.set(TRUSTED_DIRECTIVE_BASE_RULE_ID, rule);
+ }
+ const updateOptions = {};
+ if ( addRules.length ) {
+ updateOptions.addRules = addRules;
+ }
+ if ( removeRuleIds.length ) {
+ updateOptions.removeRuleIds = removeRuleIds;
+ }
+ await dnr.updateDynamicRules(updateOptions);
+}
+
+/******************************************************************************/
+
+export async function getFilteringModeDetails() {
+ const actualDetails = await readFilteringModeDetails();
+ return {
+ none: new Set(actualDetails.none),
+ basic: new Set(actualDetails.basic),
+ optimal: new Set(actualDetails.optimal),
+ complete: new Set(actualDetails.complete),
+ };
+}
+
+/******************************************************************************/
+
+export async function getFilteringMode(hostname) {
+ const filteringModes = await getFilteringModeDetails();
+ return lookupFilteringMode(filteringModes, hostname);
+}
+
+export async function setFilteringMode(hostname, afterLevel) {
+ const filteringModes = await getFilteringModeDetails();
+ const level = applyFilteringMode(filteringModes, hostname, afterLevel);
+ await writeFilteringModeDetails(filteringModes);
+ return level;
+}
+
+/******************************************************************************/
+
+export function getDefaultFilteringMode() {
+ return getFilteringMode('all-urls');
+}
+
+export function setDefaultFilteringMode(afterLevel) {
+ return setFilteringMode('all-urls', afterLevel);
+}
+
+/******************************************************************************/
+
+export async function getTrustedSites() {
+ const filteringModes = await getFilteringModeDetails();
+ return filteringModes.none;
+}
+
+export async function setTrustedSites(hostnames) {
+ const filteringModes = await getFilteringModeDetails();
+ const { none } = filteringModes;
+ const hnSet = new Set(hostnames);
+ let modified = false;
+ for ( const hn of none ) {
+ if ( hnSet.has(hn) ) {
+ hnSet.delete(hn);
+ } else {
+ none.delete(hn);
+ modified = true;
+ }
+ }
+ for ( const hn of hnSet ) {
+ const level = applyFilteringMode(filteringModes, hn, MODE_NONE);
+ if ( level !== MODE_NONE ) { continue; }
+ modified = true;
+ }
+ if ( modified === false ) { return; }
+ return writeFilteringModeDetails(filteringModes);
+}
+
+/******************************************************************************/
+
+export async function syncWithBrowserPermissions() {
+ const [ permissions, beforeMode ] = await Promise.all([
+ browser.permissions.getAll(),
+ getDefaultFilteringMode(),
+ ]);
+ const allowedHostnames = new Set(hostnamesFromMatches(permissions.origins || []));
+ let modified = false;
+ if ( beforeMode > MODE_BASIC && allowedHostnames.has('all-urls') === false ) {
+ await setDefaultFilteringMode(MODE_BASIC);
+ modified = true;
+ }
+ const afterMode = await getDefaultFilteringMode();
+ if ( afterMode > MODE_BASIC ) { return false; }
+ const filteringModes = await getFilteringModeDetails();
+ const { optimal, complete } = filteringModes;
+ for ( const hn of optimal ) {
+ if ( allowedHostnames.has(hn) ) { continue; }
+ optimal.delete(hn);
+ modified = true;
+ }
+ for ( const hn of complete ) {
+ if ( allowedHostnames.has(hn) ) { continue; }
+ complete.delete(hn);
+ modified = true;
+ }
+ await writeFilteringModeDetails(filteringModes);
+ return modified;
+}
+
+/******************************************************************************/
diff --git a/platform/mv3/extension/js/popup.js b/platform/mv3/extension/js/popup.js
new file mode 100644
index 0000000..29b993b
--- /dev/null
+++ b/platform/mv3/extension/js/popup.js
@@ -0,0 +1,351 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2022-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+import {
+ browser,
+ runtime,
+ sendMessage,
+ localRead, localWrite,
+} from './ext.js';
+
+import { dom, qs$ } from './dom.js';
+import { i18n, i18n$ } from './i18n.js';
+import punycode from './punycode.js';
+
+/******************************************************************************/
+
+const popupPanelData = {};
+const currentTab = {};
+let tabHostname = '';
+
+/******************************************************************************/
+
+function normalizedHostname(hn) {
+ return hn.replace(/^www\./, '');
+}
+
+/******************************************************************************/
+
+const BLOCKING_MODE_MAX = 3;
+
+function setFilteringMode(level, commit = false) {
+ const modeSlider = qs$('.filteringModeSlider');
+ modeSlider.dataset.level = level;
+ if ( qs$('.filteringModeSlider.moving') === null ) {
+ dom.text(
+ '#filteringModeText > span:nth-of-type(1)',
+ i18n$(`filteringMode${level}Name`)
+ );
+ }
+ if ( commit !== true ) { return; }
+ commitFilteringMode();
+}
+
+async function commitFilteringMode() {
+ if ( tabHostname === '' ) { return; }
+ const targetHostname = normalizedHostname(tabHostname);
+ const modeSlider = qs$('.filteringModeSlider');
+ const afterLevel = parseInt(modeSlider.dataset.level, 10);
+ const beforeLevel = parseInt(modeSlider.dataset.levelBefore, 10);
+ if ( afterLevel > 1 ) {
+ let granted = false;
+ try {
+ granted = await browser.permissions.request({
+ origins: [ `*://*.${targetHostname}/*` ],
+ });
+ } catch(ex) {
+ }
+ if ( granted !== true ) {
+ setFilteringMode(beforeLevel);
+ return;
+ }
+ }
+ dom.text(
+ '#filteringModeText > span:nth-of-type(1)',
+ i18n$(`filteringMode${afterLevel}Name`)
+ );
+ const actualLevel = await sendMessage({
+ what: 'setFilteringMode',
+ hostname: targetHostname,
+ level: afterLevel,
+ });
+ if ( actualLevel !== afterLevel ) {
+ setFilteringMode(actualLevel);
+ }
+ if ( actualLevel !== beforeLevel && popupPanelData.autoReload ) {
+ browser.tabs.reload(currentTab.id);
+ }
+}
+
+{
+ let mx0 = 0;
+ let mx1 = 0;
+ let l0 = 0;
+ let lMax = 0;
+ let timer;
+
+ const move = ( ) => {
+ timer = undefined;
+ const l1 = Math.min(Math.max(l0 + mx1 - mx0, 0), lMax);
+ let level = Math.floor(l1 * BLOCKING_MODE_MAX / lMax);
+ if ( qs$('body[dir="rtl"]') !== null ) {
+ level = 3 - level;
+ }
+ const modeSlider = qs$('.filteringModeSlider');
+ if ( `${level}` === modeSlider.dataset.level ) { return; }
+ dom.text(
+ '#filteringModeText > span:nth-of-type(2)',
+ i18n$(`filteringMode${level}Name`)
+ );
+ setFilteringMode(level);
+ };
+
+ const moveAsync = ev => {
+ if ( timer !== undefined ) { return; }
+ mx1 = ev.pageX;
+ timer = self.requestAnimationFrame(move);
+ };
+
+ const stop = ev => {
+ if ( ev.button !== 0 ) { return; }
+ const modeSlider = qs$('.filteringModeSlider');
+ if ( dom.cl.has(modeSlider, 'moving') === false ) { return; }
+ dom.cl.remove(modeSlider, 'moving');
+ self.removeEventListener('mousemove', moveAsync, { capture: true });
+ self.removeEventListener('mouseup', stop, { capture: true });
+ dom.text('#filteringModeText > span:nth-of-type(2)', '');
+ commitFilteringMode();
+ ev.stopPropagation();
+ ev.preventDefault();
+ if ( timer !== undefined ) {
+ self.cancelAnimationFrame(timer);
+ timer = undefined;
+ }
+ };
+
+ const startSliding = ev => {
+ if ( ev.button !== 0 ) { return; }
+ const modeButton = qs$('.filteringModeButton');
+ if ( ev.currentTarget !== modeButton ) { return; }
+ const modeSlider = qs$('.filteringModeSlider');
+ if ( dom.cl.has(modeSlider, 'moving') ) { return; }
+ modeSlider.dataset.levelBefore = modeSlider.dataset.level;
+ mx0 = ev.pageX;
+ const buttonRect = modeButton.getBoundingClientRect();
+ l0 = buttonRect.left + buttonRect.width / 2;
+ const sliderRect = modeSlider.getBoundingClientRect();
+ lMax = sliderRect.width - buttonRect.width ;
+ dom.cl.add(modeSlider, 'moving');
+ self.addEventListener('mousemove', moveAsync, { capture: true });
+ self.addEventListener('mouseup', stop, { capture: true });
+ ev.stopPropagation();
+ ev.preventDefault();
+ };
+
+ dom.on('.filteringModeButton', 'mousedown', startSliding);
+}
+
+dom.on(
+ '.filteringModeSlider',
+ 'click',
+ '.filteringModeSlider span[data-level]',
+ ev => {
+ const modeSlider = qs$('.filteringModeSlider');
+ modeSlider.dataset.levelBefore = modeSlider.dataset.level;
+ const span = ev.target;
+ const level = parseInt(span.dataset.level, 10);
+ setFilteringMode(level, true);
+ }
+);
+
+dom.on(
+ '.filteringModeSlider',
+ 'mouseenter',
+ '.filteringModeSlider span[data-level]',
+ ev => {
+ const span = ev.target;
+ const level = parseInt(span.dataset.level, 10);
+ dom.text(
+ '#filteringModeText > span:nth-of-type(2)',
+ i18n$(`filteringMode${level}Name`)
+ );
+ }
+);
+
+dom.on(
+ '.filteringModeSlider',
+ 'mouseleave',
+ '.filteringModeSlider span[data-level]',
+ ( ) => {
+ dom.text('#filteringModeText > span:nth-of-type(2)', '');
+ }
+);
+
+/******************************************************************************/
+
+// The popup panel is made of sections. Visibility of sections can be
+// toggled on/off.
+
+const maxNumberOfSections = 2;
+
+const sectionBitsFromAttribute = function() {
+ const value = dom.body.dataset.section;
+ if ( value === '' ) { return 0; }
+ let bits = 0;
+ for ( const c of value.split(' ') ) {
+ bits |= 1 << (c.charCodeAt(0) - 97);
+ }
+ return bits;
+};
+
+const sectionBitsToAttribute = function(bits) {
+ if ( typeof bits !== 'number' ) { return; }
+ if ( isNaN(bits) ) { return; }
+ const value = [];
+ for ( let i = 0; i < maxNumberOfSections; i++ ) {
+ const bit = 1 << i;
+ if ( (bits & bit) === 0 ) { continue; }
+ value.push(String.fromCharCode(97 + i));
+ }
+ dom.body.dataset.section = value.join(' ');
+};
+
+async function toggleSections(more) {
+ let currentBits = sectionBitsFromAttribute();
+ let newBits = currentBits;
+ for ( let i = 0; i < maxNumberOfSections; i++ ) {
+ const bit = 1 << (more ? i : maxNumberOfSections - i - 1);
+ if ( more ) {
+ newBits |= bit;
+ } else {
+ newBits &= ~bit;
+ }
+ if ( newBits !== currentBits ) { break; }
+ }
+ if ( newBits === currentBits ) { return; }
+ sectionBitsToAttribute(newBits);
+ localWrite('popupPanelSections', newBits);
+}
+
+localRead('popupPanelSections').then(bits => {
+ sectionBitsToAttribute(bits || 0);
+});
+
+dom.on('#moreButton', 'click', ( ) => {
+ toggleSections(true);
+});
+
+dom.on('#lessButton', 'click', ( ) => {
+ toggleSections(false);
+});
+
+/******************************************************************************/
+
+dom.on('[data-i18n-title="popupTipDashboard"]', 'click', ev => {
+ if ( ev.isTrusted !== true ) { return; }
+ if ( ev.button !== 0 ) { return; }
+ runtime.openOptionsPage();
+});
+
+/******************************************************************************/
+
+async function init() {
+ const [ tab ] = await browser.tabs.query({
+ active: true,
+ currentWindow: true,
+ });
+ if ( tab instanceof Object === false ) { return true; }
+ Object.assign(currentTab, tab);
+
+ let url;
+ try {
+ url = new URL(currentTab.url);
+ tabHostname = url.hostname || '';
+ } catch(ex) {
+ }
+
+ if ( url !== undefined ) {
+ const response = await sendMessage({
+ what: 'popupPanelData',
+ origin: url.origin,
+ hostname: normalizedHostname(tabHostname),
+ });
+ if ( response instanceof Object ) {
+ Object.assign(popupPanelData, response);
+ }
+ }
+
+ setFilteringMode(popupPanelData.level);
+
+ dom.text('#hostname', punycode.toUnicode(tabHostname));
+
+ const parent = qs$('#rulesetStats');
+ for ( const details of popupPanelData.rulesetDetails || [] ) {
+ const div = dom.clone('#templates .rulesetDetails');
+ qs$(div, 'h1').append(i18n.patchUnicodeFlags(details.name));
+ const { rules, filters, css } = details;
+ let ruleCount = rules.plain + rules.regex;
+ if ( popupPanelData.hasOmnipotence ) {
+ ruleCount += rules.removeparam + rules.redirect + rules.modifyHeaders;
+ }
+ let specificCount = 0;
+ if ( typeof css.specific === 'number' ) {
+ specificCount += css.specific;
+ }
+ if ( typeof css.declarative === 'number' ) {
+ specificCount += css.declarative;
+ }
+ if ( typeof css.procedural === 'number' ) {
+ specificCount += css.procedural;
+ }
+ dom.text(
+ qs$(div, 'p'),
+ i18n$('perRulesetStats')
+ .replace('{{ruleCount}}', ruleCount.toLocaleString())
+ .replace('{{filterCount}}', filters.accepted.toLocaleString())
+ .replace('{{cssSpecificCount}}', specificCount.toLocaleString())
+ );
+ parent.append(div);
+ }
+
+ dom.cl.remove(dom.body, 'loading');
+
+ return true;
+}
+
+async function tryInit() {
+ try {
+ await init();
+ } catch(ex) {
+ setTimeout(tryInit, 100);
+ }
+}
+
+tryInit();
+
+/******************************************************************************/
+
diff --git a/platform/mv3/extension/js/ruleset-manager.js b/platform/mv3/extension/js/ruleset-manager.js
new file mode 100644
index 0000000..a484e1d
--- /dev/null
+++ b/platform/mv3/extension/js/ruleset-manager.js
@@ -0,0 +1,539 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2022-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+import { browser, dnr, i18n } from './ext.js';
+import { fetchJSON } from './fetch.js';
+import { ubolLog } from './utils.js';
+
+/******************************************************************************/
+
+const RULE_REALM_SIZE = 1000000;
+const REGEXES_REALM_START = 1000000;
+const REGEXES_REALM_END = REGEXES_REALM_START + RULE_REALM_SIZE;
+const REMOVEPARAMS_REALM_START = REGEXES_REALM_END;
+const REMOVEPARAMS_REALM_END = REMOVEPARAMS_REALM_START + RULE_REALM_SIZE;
+const REDIRECT_REALM_START = REMOVEPARAMS_REALM_END;
+const REDIRECT_REALM_END = REDIRECT_REALM_START + RULE_REALM_SIZE;
+const MODIFYHEADERS_REALM_START = REDIRECT_REALM_END;
+const MODIFYHEADERS_REALM_END = MODIFYHEADERS_REALM_START + RULE_REALM_SIZE;
+const TRUSTED_DIRECTIVE_BASE_RULE_ID = 8000000;
+
+/******************************************************************************/
+
+function getRulesetDetails() {
+ if ( getRulesetDetails.rulesetDetailsPromise !== undefined ) {
+ return getRulesetDetails.rulesetDetailsPromise;
+ }
+ getRulesetDetails.rulesetDetailsPromise = fetchJSON('/rulesets/ruleset-details').then(entries => {
+ const rulesMap = new Map(
+ entries.map(entry => [ entry.id, entry ])
+ );
+ return rulesMap;
+ });
+ return getRulesetDetails.rulesetDetailsPromise;
+}
+
+/******************************************************************************/
+
+function getDynamicRules() {
+ if ( getDynamicRules.dynamicRuleMapPromise !== undefined ) {
+ return getDynamicRules.dynamicRuleMapPromise;
+ }
+ getDynamicRules.dynamicRuleMapPromise = dnr.getDynamicRules().then(rules => {
+ const rulesMap = new Map(rules.map(rule => [ rule.id, rule ]));
+ ubolLog(`Dynamic rule count: ${rulesMap.size}`);
+ ubolLog(`Available dynamic rule count: ${dnr.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES - rulesMap.size}`);
+ return rulesMap;
+ });
+ return getDynamicRules.dynamicRuleMapPromise;
+}
+
+/******************************************************************************/
+
+async function pruneInvalidRegexRules(realm, rulesIn) {
+ // Avoid testing already tested regexes
+ const dynamicRules = await dnr.getDynamicRules();
+ const validRegexSet = new Set(
+ dynamicRules.filter(rule =>
+ rule.condition?.regexFilter && true || false
+ ).map(rule =>
+ rule.condition.regexFilter
+ )
+ );
+
+ // Validate regex-based rules
+ const toCheck = [];
+ const rejectedRegexRules = [];
+ for ( const rule of rulesIn ) {
+ if ( rule.condition?.regexFilter === undefined ) {
+ toCheck.push(true);
+ continue;
+ }
+ const {
+ regexFilter: regex,
+ isUrlFilterCaseSensitive: isCaseSensitive
+ } = rule.condition;
+ if ( validRegexSet.has(regex) ) {
+ toCheck.push(true);
+ continue;
+ }
+ toCheck.push(
+ dnr.isRegexSupported({ regex, isCaseSensitive }).then(result => {
+ if ( result.isSupported ) { return true; }
+ rejectedRegexRules.push(`\t${regex} ${result.reason}`);
+ return false;
+ })
+ );
+ }
+
+ // Collate results
+ const isValid = await Promise.all(toCheck);
+
+ if ( rejectedRegexRules.length !== 0 ) {
+ ubolLog(
+ `${realm} realm: rejected regexes:\n`,
+ rejectedRegexRules.join('\n')
+ );
+ }
+
+ return rulesIn.filter((v, i) => isValid[i]);
+}
+
+/******************************************************************************/
+
+async function updateRegexRules() {
+ const rulesetDetails = await getEnabledRulesetsDetails();
+
+ // Fetch regexes for all enabled rulesets
+ const toFetch = [];
+ for ( const details of rulesetDetails ) {
+ if ( details.rules.regex === 0 ) { continue; }
+ toFetch.push(fetchJSON(`/rulesets/regex/${details.id}`));
+ }
+ const regexRulesets = await Promise.all(toFetch);
+
+ // Collate all regexes rules
+ const allRules = [];
+ let regexRuleId = REGEXES_REALM_START;
+ for ( const rules of regexRulesets ) {
+ if ( Array.isArray(rules) === false ) { continue; }
+ for ( const rule of rules ) {
+ rule.id = regexRuleId++;
+ allRules.push(rule);
+ }
+ }
+
+ const validatedRules = await pruneInvalidRegexRules('regexes', allRules);
+
+ // Add validated regex rules to dynamic ruleset without affecting rules
+ // outside regex rules realm.
+ const dynamicRuleMap = await getDynamicRules();
+ const newRuleMap = new Map(validatedRules.map(rule => [ rule.id, rule ]));
+ const addRules = [];
+ const removeRuleIds = [];
+
+ for ( const oldRule of dynamicRuleMap.values() ) {
+ if ( oldRule.id < REGEXES_REALM_START ) { continue; }
+ if ( oldRule.id >= REGEXES_REALM_END ) { continue; }
+ const newRule = newRuleMap.get(oldRule.id);
+ if ( newRule === undefined ) {
+ removeRuleIds.push(oldRule.id);
+ dynamicRuleMap.delete(oldRule.id);
+ } else if ( JSON.stringify(oldRule) !== JSON.stringify(newRule) ) {
+ removeRuleIds.push(oldRule.id);
+ addRules.push(newRule);
+ dynamicRuleMap.set(oldRule.id, newRule);
+ }
+ }
+
+ for ( const newRule of newRuleMap.values() ) {
+ if ( dynamicRuleMap.has(newRule.id) ) { continue; }
+ addRules.push(newRule);
+ dynamicRuleMap.set(newRule.id, newRule);
+ }
+
+ if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; }
+
+ if ( removeRuleIds.length !== 0 ) {
+ ubolLog(`Remove ${removeRuleIds.length} DNR regex rules`);
+ }
+ if ( addRules.length !== 0 ) {
+ ubolLog(`Add ${addRules.length} DNR regex rules`);
+ }
+
+ return dnr.updateDynamicRules({ addRules, removeRuleIds }).catch(reason => {
+ console.error(`updateRegexRules() / ${reason}`);
+ });
+}
+
+/******************************************************************************/
+
+async function updateRemoveparamRules() {
+ const [
+ hasOmnipotence,
+ rulesetDetails,
+ dynamicRuleMap,
+ ] = await Promise.all([
+ browser.permissions.contains({ origins: [ '<all_urls>' ] }),
+ getEnabledRulesetsDetails(),
+ getDynamicRules(),
+ ]);
+
+ // Fetch removeparam rules for all enabled rulesets
+ const toFetch = [];
+ for ( const details of rulesetDetails ) {
+ if ( details.rules.removeparam === 0 ) { continue; }
+ toFetch.push(fetchJSON(`/rulesets/removeparam/${details.id}`));
+ }
+ const removeparamRulesets = await Promise.all(toFetch);
+
+ // Removeparam rules can only be enforced with omnipotence
+ const allRules = [];
+ if ( hasOmnipotence ) {
+ let removeparamRuleId = REMOVEPARAMS_REALM_START;
+ for ( const rules of removeparamRulesets ) {
+ if ( Array.isArray(rules) === false ) { continue; }
+ for ( const rule of rules ) {
+ rule.id = removeparamRuleId++;
+ allRules.push(rule);
+ }
+ }
+ }
+
+ const validatedRules = await pruneInvalidRegexRules('removeparam', allRules);
+
+ // Add removeparam rules to dynamic ruleset without affecting rules
+ // outside removeparam rules realm.
+ const newRuleMap = new Map(validatedRules.map(rule => [ rule.id, rule ]));
+ const addRules = [];
+ const removeRuleIds = [];
+
+ for ( const oldRule of dynamicRuleMap.values() ) {
+ if ( oldRule.id < REMOVEPARAMS_REALM_START ) { continue; }
+ if ( oldRule.id >= REMOVEPARAMS_REALM_END ) { continue; }
+ const newRule = newRuleMap.get(oldRule.id);
+ if ( newRule === undefined ) {
+ removeRuleIds.push(oldRule.id);
+ dynamicRuleMap.delete(oldRule.id);
+ } else if ( JSON.stringify(oldRule) !== JSON.stringify(newRule) ) {
+ removeRuleIds.push(oldRule.id);
+ addRules.push(newRule);
+ dynamicRuleMap.set(oldRule.id, newRule);
+ }
+ }
+
+ for ( const newRule of newRuleMap.values() ) {
+ if ( dynamicRuleMap.has(newRule.id) ) { continue; }
+ addRules.push(newRule);
+ dynamicRuleMap.set(newRule.id, newRule);
+ }
+
+ if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; }
+
+ if ( removeRuleIds.length !== 0 ) {
+ ubolLog(`Remove ${removeRuleIds.length} DNR removeparam rules`);
+ }
+ if ( addRules.length !== 0 ) {
+ ubolLog(`Add ${addRules.length} DNR removeparam rules`);
+ }
+
+ return dnr.updateDynamicRules({ addRules, removeRuleIds }).catch(reason => {
+ console.error(`updateRemoveparamRules() / ${reason}`);
+ });
+}
+
+/******************************************************************************/
+
+async function updateRedirectRules() {
+ const [
+ hasOmnipotence,
+ rulesetDetails,
+ dynamicRuleMap,
+ ] = await Promise.all([
+ browser.permissions.contains({ origins: [ '<all_urls>' ] }),
+ getEnabledRulesetsDetails(),
+ getDynamicRules(),
+ ]);
+
+ // Fetch redirect rules for all enabled rulesets
+ const toFetch = [];
+ for ( const details of rulesetDetails ) {
+ if ( details.rules.redirect === 0 ) { continue; }
+ toFetch.push(fetchJSON(`/rulesets/redirect/${details.id}`));
+ }
+ const redirectRulesets = await Promise.all(toFetch);
+
+ // Redirect rules can only be enforced with omnipotence
+ const allRules = [];
+ if ( hasOmnipotence ) {
+ let redirectRuleId = REDIRECT_REALM_START;
+ for ( const rules of redirectRulesets ) {
+ if ( Array.isArray(rules) === false ) { continue; }
+ for ( const rule of rules ) {
+ rule.id = redirectRuleId++;
+ allRules.push(rule);
+ }
+ }
+ }
+
+ const validatedRules = await pruneInvalidRegexRules('redirect', allRules);
+
+ // Add redirect rules to dynamic ruleset without affecting rules
+ // outside redirect rules realm.
+ const newRuleMap = new Map(validatedRules.map(rule => [ rule.id, rule ]));
+ const addRules = [];
+ const removeRuleIds = [];
+
+ for ( const oldRule of dynamicRuleMap.values() ) {
+ if ( oldRule.id < REDIRECT_REALM_START ) { continue; }
+ if ( oldRule.id >= REDIRECT_REALM_END ) { continue; }
+ const newRule = newRuleMap.get(oldRule.id);
+ if ( newRule === undefined ) {
+ removeRuleIds.push(oldRule.id);
+ dynamicRuleMap.delete(oldRule.id);
+ } else if ( JSON.stringify(oldRule) !== JSON.stringify(newRule) ) {
+ removeRuleIds.push(oldRule.id);
+ addRules.push(newRule);
+ dynamicRuleMap.set(oldRule.id, newRule);
+ }
+ }
+
+ for ( const newRule of newRuleMap.values() ) {
+ if ( dynamicRuleMap.has(newRule.id) ) { continue; }
+ addRules.push(newRule);
+ dynamicRuleMap.set(newRule.id, newRule);
+ }
+
+ if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; }
+
+ if ( removeRuleIds.length !== 0 ) {
+ ubolLog(`Remove ${removeRuleIds.length} DNR redirect rules`);
+ }
+ if ( addRules.length !== 0 ) {
+ ubolLog(`Add ${addRules.length} DNR redirect rules`);
+ }
+
+ return dnr.updateDynamicRules({ addRules, removeRuleIds }).catch(reason => {
+ console.error(`updateRedirectRules() / ${reason}`);
+ });
+}
+
+/******************************************************************************/
+
+async function updateModifyHeadersRules() {
+ const [
+ hasOmnipotence,
+ rulesetDetails,
+ dynamicRuleMap,
+ ] = await Promise.all([
+ browser.permissions.contains({ origins: [ '<all_urls>' ] }),
+ getEnabledRulesetsDetails(),
+ getDynamicRules(),
+ ]);
+
+ // Fetch modifyHeaders rules for all enabled rulesets
+ const toFetch = [];
+ for ( const details of rulesetDetails ) {
+ if ( details.rules.modifyHeaders === 0 ) { continue; }
+ toFetch.push(fetchJSON(`/rulesets/modify-headers/${details.id}`));
+ }
+ const rulesets = await Promise.all(toFetch);
+
+ // Redirect rules can only be enforced with omnipotence
+ const allRules = [];
+ if ( hasOmnipotence ) {
+ let ruleId = MODIFYHEADERS_REALM_START;
+ for ( const rules of rulesets ) {
+ if ( Array.isArray(rules) === false ) { continue; }
+ for ( const rule of rules ) {
+ rule.id = ruleId++;
+ allRules.push(rule);
+ }
+ }
+ }
+
+ const validatedRules = await pruneInvalidRegexRules('modify-headers', allRules);
+
+ // Add modifyHeaders rules to dynamic ruleset without affecting rules
+ // outside modifyHeaders realm.
+ const newRuleMap = new Map(validatedRules.map(rule => [ rule.id, rule ]));
+ const addRules = [];
+ const removeRuleIds = [];
+
+ for ( const oldRule of dynamicRuleMap.values() ) {
+ if ( oldRule.id < MODIFYHEADERS_REALM_START ) { continue; }
+ if ( oldRule.id >= MODIFYHEADERS_REALM_END ) { continue; }
+ const newRule = newRuleMap.get(oldRule.id);
+ if ( newRule === undefined ) {
+ removeRuleIds.push(oldRule.id);
+ dynamicRuleMap.delete(oldRule.id);
+ } else if ( JSON.stringify(oldRule) !== JSON.stringify(newRule) ) {
+ removeRuleIds.push(oldRule.id);
+ addRules.push(newRule);
+ dynamicRuleMap.set(oldRule.id, newRule);
+ }
+ }
+
+ for ( const newRule of newRuleMap.values() ) {
+ if ( dynamicRuleMap.has(newRule.id) ) { continue; }
+ addRules.push(newRule);
+ dynamicRuleMap.set(newRule.id, newRule);
+ }
+
+ if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; }
+
+ if ( removeRuleIds.length !== 0 ) {
+ ubolLog(`Remove ${removeRuleIds.length} DNR modifyHeaders rules`);
+ }
+ if ( addRules.length !== 0 ) {
+ ubolLog(`Add ${addRules.length} DNR modifyHeaders rules`);
+ }
+
+ return dnr.updateDynamicRules({ addRules, removeRuleIds }).catch(reason => {
+ console.error(`updateModifyHeadersRules() / ${reason}`);
+ });
+}
+
+/******************************************************************************/
+
+// TODO: group all omnipotence-related rules into one realm.
+
+async function updateDynamicRules() {
+ return Promise.all([
+ updateRegexRules(),
+ updateRemoveparamRules(),
+ updateRedirectRules(),
+ updateModifyHeadersRules(),
+ ]);
+}
+
+/******************************************************************************/
+
+async function defaultRulesetsFromLanguage() {
+ const out = [ 'default' ];
+
+ const dropCountry = lang => {
+ const pos = lang.indexOf('-');
+ if ( pos === -1 ) { return lang; }
+ return lang.slice(0, pos);
+ };
+
+ const langSet = new Set();
+
+ for ( const lang of navigator.languages.map(dropCountry) ) {
+ langSet.add(lang);
+ }
+ langSet.add(dropCountry(i18n.getUILanguage()));
+
+ const reTargetLang = new RegExp(
+ `\\b(${Array.from(langSet).join('|')})\\b`
+ );
+
+ const rulesetDetails = await getRulesetDetails();
+ for ( const [ id, details ] of rulesetDetails ) {
+ if ( typeof details.lang !== 'string' ) { continue; }
+ if ( reTargetLang.test(details.lang) === false ) { continue; }
+ out.push(id);
+ }
+ return out;
+}
+
+/******************************************************************************/
+
+async function enableRulesets(ids) {
+ const afterIds = new Set(ids);
+ const beforeIds = new Set(await dnr.getEnabledRulesets());
+ const enableRulesetSet = new Set();
+ const disableRulesetSet = new Set();
+ for ( const id of afterIds ) {
+ if ( beforeIds.has(id) ) { continue; }
+ enableRulesetSet.add(id);
+ }
+ for ( const id of beforeIds ) {
+ if ( afterIds.has(id) ) { continue; }
+ disableRulesetSet.add(id);
+ }
+
+ if ( enableRulesetSet.size === 0 && disableRulesetSet.size === 0 ) {
+ return;
+ }
+
+ // Be sure the rulesets to enable/disable do exist in the current version,
+ // otherwise the API throws.
+ const rulesetDetails = await getRulesetDetails();
+ for ( const id of enableRulesetSet ) {
+ if ( rulesetDetails.has(id) ) { continue; }
+ enableRulesetSet.delete(id);
+ }
+ for ( const id of disableRulesetSet ) {
+ if ( rulesetDetails.has(id) ) { continue; }
+ disableRulesetSet.delete(id);
+ }
+ const enableRulesetIds = Array.from(enableRulesetSet);
+ const disableRulesetIds = Array.from(disableRulesetSet);
+
+ if ( enableRulesetIds.length !== 0 ) {
+ ubolLog(`Enable rulesets: ${enableRulesetIds}`);
+ }
+ if ( disableRulesetIds.length !== 0 ) {
+ ubolLog(`Disable ruleset: ${disableRulesetIds}`);
+ }
+ await dnr.updateEnabledRulesets({ enableRulesetIds, disableRulesetIds });
+
+ return updateDynamicRules();
+}
+
+/******************************************************************************/
+
+async function getEnabledRulesetsDetails() {
+ const [
+ ids,
+ rulesetDetails,
+ ] = await Promise.all([
+ dnr.getEnabledRulesets(),
+ getRulesetDetails(),
+ ]);
+ const out = [];
+ for ( const id of ids ) {
+ const ruleset = rulesetDetails.get(id);
+ if ( ruleset === undefined ) { continue; }
+ out.push(ruleset);
+ }
+ return out;
+}
+
+/******************************************************************************/
+
+export {
+ TRUSTED_DIRECTIVE_BASE_RULE_ID,
+ getRulesetDetails,
+ getDynamicRules,
+ enableRulesets,
+ defaultRulesetsFromLanguage,
+ getEnabledRulesetsDetails,
+ updateDynamicRules,
+};
diff --git a/platform/mv3/extension/js/scripting-manager.js b/platform/mv3/extension/js/scripting-manager.js
new file mode 100644
index 0000000..e6eebf2
--- /dev/null
+++ b/platform/mv3/extension/js/scripting-manager.js
@@ -0,0 +1,563 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2022-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+import { browser } from './ext.js';
+import { fetchJSON } from './fetch.js';
+import { getFilteringModeDetails } from './mode-manager.js';
+import { getEnabledRulesetsDetails } from './ruleset-manager.js';
+
+import * as ut from './utils.js';
+
+/******************************************************************************/
+
+const isGecko = browser.runtime.getURL('').startsWith('moz-extension://');
+
+const resourceDetailPromises = new Map();
+
+function getScriptletDetails() {
+ let promise = resourceDetailPromises.get('scriptlet');
+ if ( promise !== undefined ) { return promise; }
+ promise = fetchJSON('/rulesets/scriptlet-details').then(
+ entries => new Map(entries)
+ );
+ resourceDetailPromises.set('scriptlet', promise);
+ return promise;
+}
+
+function getGenericDetails() {
+ let promise = resourceDetailPromises.get('generic');
+ if ( promise !== undefined ) { return promise; }
+ promise = fetchJSON('/rulesets/generic-details').then(
+ entries => new Map(entries)
+ );
+ resourceDetailPromises.set('generic', promise);
+ return promise;
+}
+
+/******************************************************************************/
+
+// Important: We need to sort the arrays for fast comparison
+const arrayEq = (a = [], b = [], sort = true) => {
+ const alen = a.length;
+ if ( alen !== b.length ) { return false; }
+ if ( sort ) { a.sort(); b.sort(); }
+ for ( let i = 0; i < alen; i++ ) {
+ if ( a[i] !== b[i] ) { return false; }
+ }
+ return true;
+};
+
+/******************************************************************************/
+
+// The extensions API does not always return exactly what we fed it, so we
+// need to normalize some entries to be sure we properly detect changes when
+// comparing registered entries vs. entries to register.
+
+const normalizeRegisteredContentScripts = registered => {
+ for ( const entry of registered ) {
+ const { css = [], js = [] } = entry;
+ for ( let i = 0; i < css.length; i++ ) {
+ const path = css[i];
+ if ( path.startsWith('/') ) { continue; }
+ css[i] = `/${path}`;
+ }
+ for ( let i = 0; i < js.length; i++ ) {
+ const path = js[i];
+ if ( path.startsWith('/') ) { continue; }
+ js[i] = `/${path}`;
+ }
+ }
+ return registered;
+};
+
+/******************************************************************************/
+
+function registerHighGeneric(context, genericDetails) {
+ const { before, filteringModeDetails, rulesetsDetails } = context;
+
+ const excludeHostnames = [];
+ const css = [];
+ for ( const details of rulesetsDetails ) {
+ const hostnames = genericDetails.get(details.id);
+ if ( hostnames !== undefined ) {
+ excludeHostnames.push(...hostnames);
+ }
+ const count = details.css?.generichigh || 0;
+ if ( count === 0 ) { continue; }
+ css.push(`/rulesets/scripting/generichigh/${details.id}.css`);
+ }
+
+ if ( css.length === 0 ) { return; }
+
+ const { none, basic, optimal, complete } = filteringModeDetails;
+ const matches = [];
+ const excludeMatches = [];
+ if ( complete.has('all-urls') ) {
+ excludeMatches.push(...ut.matchesFromHostnames(none));
+ excludeMatches.push(...ut.matchesFromHostnames(basic));
+ excludeMatches.push(...ut.matchesFromHostnames(optimal));
+ excludeMatches.push(...ut.matchesFromHostnames(excludeHostnames));
+ matches.push('<all_urls>');
+ } else {
+ matches.push(
+ ...ut.matchesFromHostnames(
+ ut.subtractHostnameIters(
+ Array.from(complete),
+ excludeHostnames
+ )
+ )
+ );
+ }
+
+ if ( matches.length === 0 ) { return; }
+
+ const registered = before.get('css-generichigh');
+ before.delete('css-generichigh'); // Important!
+
+ // https://github.com/w3c/webextensions/issues/414#issuecomment-1623992885
+ // Once supported, add:
+ // cssOrigin: 'USER',
+ const directive = {
+ id: 'css-generichigh',
+ css,
+ matches,
+ excludeMatches,
+ runAt: 'document_end',
+ };
+
+ // register
+ if ( registered === undefined ) {
+ context.toAdd.push(directive);
+ return;
+ }
+
+ // update
+ if (
+ arrayEq(registered.css, css, false) === false ||
+ arrayEq(registered.matches, matches) === false ||
+ arrayEq(registered.excludeMatches, excludeMatches) === false
+ ) {
+ context.toRemove.push('css-generichigh');
+ context.toAdd.push(directive);
+ }
+}
+
+/******************************************************************************/
+
+function registerGeneric(context, genericDetails) {
+ const { before, filteringModeDetails, rulesetsDetails } = context;
+
+ const excludeHostnames = [];
+ const js = [];
+ for ( const details of rulesetsDetails ) {
+ const hostnames = genericDetails.get(details.id);
+ if ( hostnames !== undefined ) {
+ excludeHostnames.push(...hostnames);
+ }
+ const count = details.css?.generic || 0;
+ if ( count === 0 ) { continue; }
+ js.push(`/rulesets/scripting/generic/${details.id}.js`);
+ }
+
+ if ( js.length === 0 ) { return; }
+
+ js.push('/js/scripting/css-generic.js');
+
+ const { none, basic, optimal, complete } = filteringModeDetails;
+ const matches = [];
+ const excludeMatches = [];
+ if ( complete.has('all-urls') ) {
+ excludeMatches.push(...ut.matchesFromHostnames(none));
+ excludeMatches.push(...ut.matchesFromHostnames(basic));
+ excludeMatches.push(...ut.matchesFromHostnames(optimal));
+ excludeMatches.push(...ut.matchesFromHostnames(excludeHostnames));
+ matches.push('<all_urls>');
+ } else {
+ matches.push(
+ ...ut.matchesFromHostnames(
+ ut.subtractHostnameIters(
+ Array.from(complete),
+ excludeHostnames
+ )
+ )
+ );
+ }
+
+ if ( matches.length === 0 ) { return; }
+
+ const registered = before.get('css-generic');
+ before.delete('css-generic'); // Important!
+
+ const directive = {
+ id: 'css-generic',
+ js,
+ matches,
+ excludeMatches,
+ runAt: 'document_idle',
+ };
+
+ // register
+ if ( registered === undefined ) {
+ context.toAdd.push(directive);
+ return;
+ }
+
+ // update
+ if (
+ arrayEq(registered.js, js, false) === false ||
+ arrayEq(registered.matches, matches) === false ||
+ arrayEq(registered.excludeMatches, excludeMatches) === false
+ ) {
+ context.toRemove.push('css-generic');
+ context.toAdd.push(directive);
+ }
+}
+
+/******************************************************************************/
+
+function registerProcedural(context) {
+ const { before, filteringModeDetails, rulesetsDetails } = context;
+
+ const js = [];
+ for ( const rulesetDetails of rulesetsDetails ) {
+ const count = rulesetDetails.css?.procedural || 0;
+ if ( count === 0 ) { continue; }
+ js.push(`/rulesets/scripting/procedural/${rulesetDetails.id}.js`);
+ }
+ if ( js.length === 0 ) { return; }
+
+ const { none, basic, optimal, complete } = filteringModeDetails;
+ const matches = [
+ ...ut.matchesFromHostnames(optimal),
+ ...ut.matchesFromHostnames(complete),
+ ];
+ if ( matches.length === 0 ) { return; }
+
+ js.push('/js/scripting/css-procedural.js');
+
+ const excludeMatches = [];
+ if ( none.has('all-urls') === false ) {
+ excludeMatches.push(...ut.matchesFromHostnames(none));
+ }
+ if ( basic.has('all-urls') === false ) {
+ excludeMatches.push(...ut.matchesFromHostnames(basic));
+ }
+
+ const registered = before.get('css-procedural');
+ before.delete('css-procedural'); // Important!
+
+ const directive = {
+ id: 'css-procedural',
+ js,
+ allFrames: true,
+ matches,
+ excludeMatches,
+ runAt: 'document_end',
+ };
+
+ // register
+ if ( registered === undefined ) {
+ context.toAdd.push(directive);
+ return;
+ }
+
+ // update
+ if (
+ arrayEq(registered.js, js, false) === false ||
+ arrayEq(registered.matches, matches) === false ||
+ arrayEq(registered.excludeMatches, excludeMatches) === false
+ ) {
+ context.toRemove.push('css-procedural');
+ context.toAdd.push(directive);
+ }
+}
+
+/******************************************************************************/
+
+function registerDeclarative(context) {
+ const { before, filteringModeDetails, rulesetsDetails } = context;
+
+ const js = [];
+ for ( const rulesetDetails of rulesetsDetails ) {
+ const count = rulesetDetails.css?.declarative || 0;
+ if ( count === 0 ) { continue; }
+ js.push(`/rulesets/scripting/declarative/${rulesetDetails.id}.js`);
+ }
+ if ( js.length === 0 ) { return; }
+
+ const { none, basic, optimal, complete } = filteringModeDetails;
+ const matches = [
+ ...ut.matchesFromHostnames(optimal),
+ ...ut.matchesFromHostnames(complete),
+ ];
+ if ( matches.length === 0 ) { return; }
+
+ js.push('/js/scripting/css-declarative.js');
+
+ const excludeMatches = [];
+ if ( none.has('all-urls') === false ) {
+ excludeMatches.push(...ut.matchesFromHostnames(none));
+ }
+ if ( basic.has('all-urls') === false ) {
+ excludeMatches.push(...ut.matchesFromHostnames(basic));
+ }
+
+ const registered = before.get('css-declarative');
+ before.delete('css-declarative'); // Important!
+
+ const directive = {
+ id: 'css-declarative',
+ js,
+ allFrames: true,
+ matches,
+ excludeMatches,
+ runAt: 'document_start',
+ };
+
+ // register
+ if ( registered === undefined ) {
+ context.toAdd.push(directive);
+ return;
+ }
+
+ // update
+ if (
+ arrayEq(registered.js, js, false) === false ||
+ arrayEq(registered.matches, matches) === false ||
+ arrayEq(registered.excludeMatches, excludeMatches) === false
+ ) {
+ context.toRemove.push('css-declarative');
+ context.toAdd.push(directive);
+ }
+}
+
+/******************************************************************************/
+
+function registerSpecific(context) {
+ const { before, filteringModeDetails, rulesetsDetails } = context;
+
+ const js = [];
+ for ( const rulesetDetails of rulesetsDetails ) {
+ const count = rulesetDetails.css?.specific || 0;
+ if ( count === 0 ) { continue; }
+ js.push(`/rulesets/scripting/specific/${rulesetDetails.id}.js`);
+ }
+ if ( js.length === 0 ) { return; }
+
+ const { none, basic, optimal, complete } = filteringModeDetails;
+ const matches = [
+ ...ut.matchesFromHostnames(optimal),
+ ...ut.matchesFromHostnames(complete),
+ ];
+ if ( matches.length === 0 ) { return; }
+
+ js.push('/js/scripting/css-specific.js');
+
+ const excludeMatches = [];
+ if ( none.has('all-urls') === false ) {
+ excludeMatches.push(...ut.matchesFromHostnames(none));
+ }
+ if ( basic.has('all-urls') === false ) {
+ excludeMatches.push(...ut.matchesFromHostnames(basic));
+ }
+
+ const registered = before.get('css-specific');
+ before.delete('css-specific'); // Important!
+
+ const directive = {
+ id: 'css-specific',
+ js,
+ allFrames: true,
+ matches,
+ excludeMatches,
+ runAt: 'document_start',
+ };
+
+ // register
+ if ( registered === undefined ) {
+ context.toAdd.push(directive);
+ return;
+ }
+
+ // update
+ if (
+ arrayEq(registered.js, js, false) === false ||
+ arrayEq(registered.matches, matches) === false ||
+ arrayEq(registered.excludeMatches, excludeMatches) === false
+ ) {
+ context.toRemove.push('css-specific');
+ context.toAdd.push(directive);
+ }
+}
+
+/******************************************************************************/
+
+function registerScriptlet(context, scriptletDetails) {
+ const { before, filteringModeDetails, rulesetsDetails } = context;
+
+ const hasBroadHostPermission =
+ filteringModeDetails.optimal.has('all-urls') ||
+ filteringModeDetails.complete.has('all-urls');
+
+ const permissionRevokedMatches = [
+ ...ut.matchesFromHostnames(filteringModeDetails.none),
+ ...ut.matchesFromHostnames(filteringModeDetails.basic),
+ ];
+ const permissionGrantedHostnames = [
+ ...filteringModeDetails.optimal,
+ ...filteringModeDetails.complete,
+ ];
+
+ for ( const rulesetId of rulesetsDetails.map(v => v.id) ) {
+ const scriptletList = scriptletDetails.get(rulesetId);
+ if ( scriptletList === undefined ) { continue; }
+
+ for ( const [ token, scriptletHostnames ] of scriptletList ) {
+ const id = `${rulesetId}.${token}`;
+ const registered = before.get(id);
+
+ const matches = [];
+ const excludeMatches = [];
+ let targetHostnames = [];
+ if ( hasBroadHostPermission ) {
+ excludeMatches.push(...permissionRevokedMatches);
+ if ( scriptletHostnames.length > 100 ) {
+ targetHostnames = [ '*' ];
+ } else {
+ targetHostnames = scriptletHostnames;
+ }
+ } else if ( permissionGrantedHostnames.length !== 0 ) {
+ if ( scriptletHostnames.includes('*') ) {
+ targetHostnames = permissionGrantedHostnames;
+ } else {
+ targetHostnames = ut.intersectHostnameIters(
+ permissionGrantedHostnames,
+ scriptletHostnames
+ );
+ }
+ }
+ if ( targetHostnames.length === 0 ) { continue; }
+ matches.push(...ut.matchesFromHostnames(targetHostnames));
+
+ before.delete(id); // Important!
+
+ const directive = {
+ id,
+ js: [ `/rulesets/scripting/scriptlet/${id}.js` ],
+ allFrames: true,
+ matches,
+ excludeMatches,
+ runAt: 'document_start',
+ };
+
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1736575
+ // `MAIN` world not yet supported in Firefox
+ if ( isGecko === false ) {
+ directive.world = 'MAIN';
+ }
+
+ // register
+ if ( registered === undefined ) {
+ context.toAdd.push(directive);
+ continue;
+ }
+
+ // update
+ if (
+ arrayEq(registered.matches, matches) === false ||
+ arrayEq(registered.excludeMatches, excludeMatches) === false
+ ) {
+ context.toRemove.push(id);
+ context.toAdd.push(directive);
+ }
+ }
+ }
+}
+
+/******************************************************************************/
+
+async function registerInjectables(origins) {
+ void origins;
+
+ if ( browser.scripting === undefined ) { return false; }
+
+ const [
+ filteringModeDetails,
+ rulesetsDetails,
+ scriptletDetails,
+ genericDetails,
+ registered,
+ ] = await Promise.all([
+ getFilteringModeDetails(),
+ getEnabledRulesetsDetails(),
+ getScriptletDetails(),
+ getGenericDetails(),
+ browser.scripting.getRegisteredContentScripts(),
+ ]);
+ const before = new Map(
+ normalizeRegisteredContentScripts(registered).map(
+ entry => [ entry.id, entry ]
+ )
+ );
+ const toAdd = [], toRemove = [];
+ const context = {
+ filteringModeDetails,
+ rulesetsDetails,
+ before,
+ toAdd,
+ toRemove,
+ };
+
+ registerDeclarative(context);
+ registerProcedural(context);
+ registerScriptlet(context, scriptletDetails);
+ registerSpecific(context);
+ registerGeneric(context, genericDetails);
+ registerHighGeneric(context, genericDetails);
+
+ toRemove.push(...Array.from(before.keys()));
+
+ if ( toRemove.length !== 0 ) {
+ ut.ubolLog(`Unregistered ${toRemove} content (css/js)`);
+ await browser.scripting.unregisterContentScripts({ ids: toRemove })
+ .catch(reason => { console.info(reason); });
+ }
+
+ if ( toAdd.length !== 0 ) {
+ ut.ubolLog(`Registered ${toAdd.map(v => v.id)} content (css/js)`);
+ await browser.scripting.registerContentScripts(toAdd)
+ .catch(reason => { console.info(reason); });
+ }
+
+ return true;
+}
+
+/******************************************************************************/
+
+export {
+ registerInjectables
+};
diff --git a/platform/mv3/extension/js/scripting/css-declarative.js b/platform/mv3/extension/js/scripting/css-declarative.js
new file mode 100644
index 0000000..d5c5555
--- /dev/null
+++ b/platform/mv3/extension/js/scripting/css-declarative.js
@@ -0,0 +1,157 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+// Important!
+// Isolate from global scope
+(function uBOL_cssDeclarative() {
+
+/******************************************************************************/
+
+const declarativeImports = self.declarativeImports || [];
+self.declarativeImports = undefined;
+delete self.declarativeImports;
+
+/******************************************************************************/
+
+const hnParts = [];
+try { hnParts.push(...document.location.hostname.split('.')); }
+catch(ex) { }
+const hnpartslen = hnParts.length;
+if ( hnpartslen === 0 ) { return; }
+
+const selectors = [];
+
+for ( const { argsList, exceptionsMap, hostnamesMap, entitiesMap } of declarativeImports ) {
+ const todoIndices = new Set();
+ const tonotdoIndices = [];
+ // Exceptions
+ if ( exceptionsMap.size !== 0 ) {
+ for ( let i = 0; i < hnpartslen; i++ ) {
+ const hn = hnParts.slice(i).join('.');
+ const excepted = exceptionsMap.get(hn);
+ if ( excepted ) { tonotdoIndices.push(...excepted); }
+ }
+ exceptionsMap.clear();
+ }
+ // Hostname-based
+ if ( hostnamesMap.size !== 0 ) {
+ const collectArgIndices = hn => {
+ let argsIndices = hostnamesMap.get(hn);
+ if ( argsIndices === undefined ) { return; }
+ if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
+ for ( const argsIndex of argsIndices ) {
+ if ( tonotdoIndices.includes(argsIndex) ) { continue; }
+ todoIndices.add(argsIndex);
+ }
+ };
+ for ( let i = 0; i < hnpartslen; i++ ) {
+ const hn = hnParts.slice(i).join('.');
+ collectArgIndices(hn);
+ }
+ collectArgIndices('*');
+ hostnamesMap.clear();
+ }
+ // Entity-based
+ if ( entitiesMap.size !== 0 ) {
+ const n = hnpartslen - 1;
+ for ( let i = 0; i < n; i++ ) {
+ for ( let j = n; j > i; j-- ) {
+ const en = hnParts.slice(i,j).join('.');
+ let argsIndices = entitiesMap.get(en);
+ if ( argsIndices === undefined ) { continue; }
+ if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
+ for ( const argsIndex of argsIndices ) {
+ if ( tonotdoIndices.includes(argsIndex) ) { continue; }
+ todoIndices.add(argsIndex);
+ }
+ }
+ }
+ entitiesMap.clear();
+ }
+ for ( const i of todoIndices ) {
+ selectors.push(...argsList[i].map(json => JSON.parse(json)));
+ }
+ argsList.length = 0;
+}
+declarativeImports.length = 0;
+
+if ( selectors.length === 0 ) { return; }
+
+/******************************************************************************/
+
+const cssRuleFromProcedural = details => {
+ const { tasks, action } = details;
+ let mq, selector;
+ if ( Array.isArray(tasks) ) {
+ if ( tasks[0][0] !== 'matches-media' ) { return; }
+ mq = tasks[0][1];
+ if ( tasks.length > 2 ) { return; }
+ if ( tasks.length === 2 ) {
+ if ( tasks[1][0] !== 'spath' ) { return; }
+ selector = tasks[1][1];
+ }
+ }
+ let style;
+ if ( Array.isArray(action) ) {
+ if ( action[0] !== 'style' ) { return; }
+ selector = selector || details.selector;
+ style = action[1];
+ }
+ if ( mq === undefined && style === undefined && selector === undefined ) { return; }
+ if ( mq === undefined ) {
+ return `${selector}\n{${style}}`;
+ }
+ if ( style === undefined ) {
+ return `@media ${mq} {\n${selector}\n{display:none!important;}\n}`;
+ }
+ return `@media ${mq} {\n${selector}\n{${style}}\n}`;
+};
+
+const sheetText = [];
+for ( const selector of selectors ) {
+ const ruleText = cssRuleFromProcedural(selector);
+ if ( ruleText === undefined ) { continue; }
+ sheetText.push(ruleText);
+}
+
+if ( sheetText.length === 0 ) { return; }
+
+(function uBOL_injectCSS(css, count = 10) {
+ chrome.runtime.sendMessage({ what: 'insertCSS', css }).catch(( ) => {
+ count -= 1;
+ if ( count === 0 ) { return; }
+ uBOL_injectCSS(css, count - 1);
+ });
+})(sheetText.join('\n'));
+
+/******************************************************************************/
+
+})();
+
+/******************************************************************************/
+
+void 0;
diff --git a/platform/mv3/extension/js/scripting/css-generic.js b/platform/mv3/extension/js/scripting/css-generic.js
new file mode 100644
index 0000000..49856ae
--- /dev/null
+++ b/platform/mv3/extension/js/scripting/css-generic.js
@@ -0,0 +1,239 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+// Important!
+// Isolate from global scope
+(function uBOL_cssGeneric() {
+
+const genericSelectorMap = self.genericSelectorMap || new Map();
+delete self.genericSelectorMap;
+
+if ( genericSelectorMap.size === 0 ) { return; }
+
+/******************************************************************************/
+
+const maxSurveyTimeSlice = 4;
+const maxSurveyNodeSlice = 64;
+const styleSheetSelectors = [];
+const stopAllRatio = 0.95; // To be investigated
+
+let surveyCount = 0;
+let surveyMissCount = 0;
+let styleSheetTimer;
+let processTimer;
+let domChangeTimer;
+let lastDomChange = Date.now();
+
+/******************************************************************************/
+
+// http://www.cse.yorku.ca/~oz/hash.html#djb2
+// Must mirror dnrRulesetFromRawLists's version
+
+const hashFromStr = (type, s) => {
+ const len = s.length;
+ const step = len + 7 >>> 3;
+ let hash = (type << 5) + type ^ len;
+ for ( let i = 0; i < len; i += step ) {
+ hash = (hash << 5) + hash ^ s.charCodeAt(i);
+ }
+ return hash & 0xFFFFFF;
+};
+
+/******************************************************************************/
+
+// Extract all classes/ids: these will be passed to the cosmetic
+// filtering engine, and in return we will obtain only the relevant
+// CSS selectors.
+
+// https://github.com/gorhill/uBlock/issues/672
+// http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens
+// http://jsperf.com/enumerate-classes/6
+
+const uBOL_idFromNode = (node, out) => {
+ const raw = node.id;
+ if ( typeof raw !== 'string' || raw.length === 0 ) { return; }
+ out.push(hashFromStr(0x23 /* '#' */, raw.trim()));
+};
+
+// https://github.com/uBlockOrigin/uBlock-issues/discussions/2076
+// Performance: avoid using Element.classList
+const uBOL_classesFromNode = (node, out) => {
+ const s = node.getAttribute('class');
+ if ( typeof s !== 'string' ) { return; }
+ const len = s.length;
+ for ( let beg = 0, end = 0; beg < len; beg += 1 ) {
+ end = s.indexOf(' ', beg);
+ if ( end === beg ) { continue; }
+ if ( end === -1 ) { end = len; }
+ out.push(hashFromStr(0x2E /* '.' */, s.slice(beg, end)));
+ beg = end;
+ }
+};
+
+/******************************************************************************/
+
+const pendingNodes = {
+ addedNodes: [],
+ nodeSet: new Set(),
+ add(node) {
+ this.addedNodes.push(node);
+ },
+ next(out) {
+ for ( const added of this.addedNodes ) {
+ if ( this.nodeSet.has(added) ) { continue; }
+ if ( added.nodeType === 1 ) {
+ this.nodeSet.add(added);
+ }
+ if ( added.firstElementChild === null ) { continue; }
+ for ( const descendant of added.querySelectorAll('[id],[class]') ) {
+ this.nodeSet.add(descendant);
+ }
+ }
+ this.addedNodes.length = 0;
+ for ( const node of this.nodeSet ) {
+ this.nodeSet.delete(node);
+ out.push(node);
+ if ( out.length === maxSurveyNodeSlice ) { break; }
+ }
+ },
+ hasNodes() {
+ return this.addedNodes.length !== 0 || this.nodeSet.size !== 0;
+ },
+};
+
+/******************************************************************************/
+
+const uBOL_processNodes = ( ) => {
+ const t0 = Date.now();
+ const hashes = [];
+ const nodes = [];
+ const deadline = t0 + maxSurveyTimeSlice;
+ for (;;) {
+ pendingNodes.next(nodes);
+ if ( nodes.length === 0 ) { break; }
+ for ( const node of nodes ) {
+ uBOL_idFromNode(node, hashes);
+ uBOL_classesFromNode(node, hashes);
+ }
+ nodes.length = 0;
+ if ( performance.now() >= deadline ) { break; }
+ }
+ for ( const hash of hashes ) {
+ const selectorList = genericSelectorMap.get(hash);
+ if ( selectorList === undefined ) { continue; }
+ styleSheetSelectors.push(selectorList);
+ genericSelectorMap.delete(hash);
+ }
+ surveyCount += 1;
+ if ( styleSheetSelectors.length === 0 ) {
+ surveyMissCount += 1;
+ if (
+ surveyCount >= 100 &&
+ (surveyMissCount / surveyCount) >= stopAllRatio
+ ) {
+ stopAll(`too many misses in surveyor (${surveyMissCount}/${surveyCount})`);
+ }
+ return;
+ }
+ if ( styleSheetTimer !== undefined ) { return; }
+ styleSheetTimer = self.requestAnimationFrame(( ) => {
+ styleSheetTimer = undefined;
+ uBOL_injectCSS(`${styleSheetSelectors.join(',')}{display:none!important;}`);
+ styleSheetSelectors.length = 0;
+ });
+};
+
+/******************************************************************************/
+
+const uBOL_processChanges = mutations => {
+ for ( let i = 0; i < mutations.length; i++ ) {
+ const mutation = mutations[i];
+ for ( const added of mutation.addedNodes ) {
+ if ( added.nodeType !== 1 ) { continue; }
+ pendingNodes.add(added);
+ }
+ }
+ if ( pendingNodes.hasNodes() === false ) { return; }
+ lastDomChange = Date.now();
+ if ( processTimer !== undefined ) { return; }
+ processTimer = self.setTimeout(( ) => {
+ processTimer = undefined;
+ uBOL_processNodes();
+ }, 64);
+};
+
+/******************************************************************************/
+
+const uBOL_injectCSS = (css, count = 10) => {
+ chrome.runtime.sendMessage({ what: 'insertCSS', css }).catch(( ) => {
+ count -= 1;
+ if ( count === 0 ) { return; }
+ uBOL_injectCSS(css, count - 1);
+ });
+};
+
+/******************************************************************************/
+
+pendingNodes.add(document);
+uBOL_processNodes();
+
+let domMutationObserver = new MutationObserver(uBOL_processChanges);
+domMutationObserver.observe(document, {
+ childList: true,
+ subtree: true,
+});
+
+const needDomChangeObserver = ( ) => {
+ domChangeTimer = undefined;
+ if ( domMutationObserver === undefined ) { return; }
+ if ( (Date.now() - lastDomChange) > 20000 ) {
+ return stopAll('no more DOM changes');
+ }
+ domChangeTimer = self.setTimeout(needDomChangeObserver, 20000);
+};
+
+needDomChangeObserver();
+
+/******************************************************************************/
+
+const stopAll = reason => {
+ if ( domChangeTimer !== undefined ) {
+ self.clearTimeout(domChangeTimer);
+ domChangeTimer = undefined;
+ }
+ domMutationObserver.disconnect();
+ domMutationObserver.takeRecords();
+ domMutationObserver = undefined;
+ genericSelectorMap.clear();
+ console.info(`uBOL: Generic cosmetic filtering stopped because ${reason}`);
+};
+
+/******************************************************************************/
+
+})();
+
+/******************************************************************************/
diff --git a/platform/mv3/extension/js/scripting/css-procedural.js b/platform/mv3/extension/js/scripting/css-procedural.js
new file mode 100644
index 0000000..818e697
--- /dev/null
+++ b/platform/mv3/extension/js/scripting/css-procedural.js
@@ -0,0 +1,762 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+// Important!
+// Isolate from global scope
+(function uBOL_cssProcedural() {
+
+/******************************************************************************/
+
+const proceduralImports = self.proceduralImports || [];
+self.proceduralImports = undefined;
+delete self.proceduralImports;
+
+/******************************************************************************/
+
+const hnParts = [];
+try { hnParts.push(...document.location.hostname.split('.')); }
+catch(ex) { }
+const hnpartslen = hnParts.length;
+if ( hnpartslen === 0 ) { return; }
+
+const selectors = [];
+
+for ( const { argsList, exceptionsMap, hostnamesMap, entitiesMap } of proceduralImports ) {
+ const todoIndices = new Set();
+ const tonotdoIndices = [];
+ // Exceptions
+ if ( exceptionsMap.size !== 0 ) {
+ for ( let i = 0; i < hnpartslen; i++ ) {
+ const hn = hnParts.slice(i).join('.');
+ const excepted = exceptionsMap.get(hn);
+ if ( excepted ) { tonotdoIndices.push(...excepted); }
+ }
+ exceptionsMap.clear();
+ }
+ // Hostname-based
+ if ( hostnamesMap.size !== 0 ) {
+ const collectArgIndices = hn => {
+ let argsIndices = hostnamesMap.get(hn);
+ if ( argsIndices === undefined ) { return; }
+ if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
+ for ( const argsIndex of argsIndices ) {
+ if ( tonotdoIndices.includes(argsIndex) ) { continue; }
+ todoIndices.add(argsIndex);
+ }
+ };
+ for ( let i = 0; i < hnpartslen; i++ ) {
+ const hn = hnParts.slice(i).join('.');
+ collectArgIndices(hn);
+ }
+ collectArgIndices('*');
+ hostnamesMap.clear();
+ }
+ // Entity-based
+ if ( entitiesMap.size !== 0 ) {
+ const n = hnpartslen - 1;
+ for ( let i = 0; i < n; i++ ) {
+ for ( let j = n; j > i; j-- ) {
+ const en = hnParts.slice(i,j).join('.');
+ let argsIndices = entitiesMap.get(en);
+ if ( argsIndices === undefined ) { continue; }
+ if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
+ for ( const argsIndex of argsIndices ) {
+ if ( tonotdoIndices.includes(argsIndex) ) { continue; }
+ todoIndices.add(argsIndex);
+ }
+ }
+ }
+ entitiesMap.clear();
+ }
+ for ( const i of todoIndices ) {
+ selectors.push(...argsList[i].map(json => JSON.parse(json)));
+ }
+ argsList.length = 0;
+}
+proceduralImports.length = 0;
+
+if ( selectors.length === 0 ) { return; }
+
+/******************************************************************************/
+
+const uBOL_injectCSS = (css, count = 10) => {
+ chrome.runtime.sendMessage({ what: 'insertCSS', css }).catch(( ) => {
+ count -= 1;
+ if ( count === 0 ) { return; }
+ uBOL_injectCSS(css, count - 1);
+ });
+};
+
+const nonVisualElements = {
+ script: true,
+ style: true,
+};
+
+const regexFromString = (s, exact = false) => {
+ if ( s === '' ) { return /^/; }
+ const match = /^\/(.+)\/([i]?)$/.exec(s);
+ if ( match !== null ) {
+ return new RegExp(match[1], match[2] || undefined);
+ }
+ const reStr = s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ return new RegExp(exact ? `^${reStr}$` : reStr, 'i');
+};
+
+/******************************************************************************/
+
+// 'P' stands for 'Procedural'
+
+class PSelectorTask {
+ begin() {
+ }
+ end() {
+ }
+}
+
+/******************************************************************************/
+
+class PSelectorVoidTask extends PSelectorTask {
+ constructor(task) {
+ super();
+ console.info(`uBO: :${task[0]}() operator does not exist`);
+ }
+ transpose() {
+ }
+}
+
+/******************************************************************************/
+
+class PSelectorHasTextTask extends PSelectorTask {
+ constructor(task) {
+ super();
+ this.needle = regexFromString(task[1]);
+ }
+ transpose(node, output) {
+ if ( this.needle.test(node.textContent) ) {
+ output.push(node);
+ }
+ }
+}
+
+/******************************************************************************/
+
+class PSelectorIfTask extends PSelectorTask {
+ constructor(task) {
+ super();
+ this.pselector = new PSelector(task[1]);
+ }
+ transpose(node, output) {
+ if ( this.pselector.test(node) === this.target ) {
+ output.push(node);
+ }
+ }
+}
+PSelectorIfTask.prototype.target = true;
+
+class PSelectorIfNotTask extends PSelectorIfTask {
+}
+PSelectorIfNotTask.prototype.target = false;
+
+/******************************************************************************/
+
+class PSelectorMatchesAttrTask extends PSelectorTask {
+ constructor(task) {
+ super();
+ this.reAttr = regexFromString(task[1].attr, true);
+ this.reValue = regexFromString(task[1].value, true);
+ }
+ transpose(node, output) {
+ const attrs = node.getAttributeNames();
+ for ( const attr of attrs ) {
+ if ( this.reAttr.test(attr) === false ) { continue; }
+ if ( this.reValue.test(node.getAttribute(attr)) === false ) { continue; }
+ output.push(node);
+ }
+ }
+}
+
+/******************************************************************************/
+
+class PSelectorMatchesCSSTask extends PSelectorTask {
+ constructor(task) {
+ super();
+ this.name = task[1].name;
+ this.pseudo = task[1].pseudo ? `::${task[1].pseudo}` : null;
+ let arg0 = task[1].value, arg1;
+ if ( Array.isArray(arg0) ) {
+ arg1 = arg0[1]; arg0 = arg0[0];
+ }
+ this.value = new RegExp(arg0, arg1);
+ }
+ transpose(node, output) {
+ const style = window.getComputedStyle(node, this.pseudo);
+ if ( style !== null && this.value.test(style[this.name]) ) {
+ output.push(node);
+ }
+ }
+}
+class PSelectorMatchesCSSAfterTask extends PSelectorMatchesCSSTask {
+ constructor(task) {
+ super(task);
+ this.pseudo = '::after';
+ }
+}
+
+class PSelectorMatchesCSSBeforeTask extends PSelectorMatchesCSSTask {
+ constructor(task) {
+ super(task);
+ this.pseudo = '::before';
+ }
+}
+
+/******************************************************************************/
+
+class PSelectorMatchesMediaTask extends PSelectorTask {
+ constructor(task) {
+ super();
+ this.mql = window.matchMedia(task[1]);
+ if ( this.mql.media === 'not all' ) { return; }
+ this.mql.addEventListener('change', ( ) => {
+ if ( proceduralFilterer instanceof Object === false ) { return; }
+ proceduralFilterer.onDOMChanged([ null ]);
+ });
+ }
+ transpose(node, output) {
+ if ( this.mql.matches === false ) { return; }
+ output.push(node);
+ }
+}
+
+/******************************************************************************/
+
+class PSelectorMatchesPathTask extends PSelectorTask {
+ constructor(task) {
+ super();
+ this.needle = regexFromString(
+ task[1].replace(/\P{ASCII}/gu, s => encodeURIComponent(s))
+ );
+ }
+ transpose(node, output) {
+ if ( this.needle.test(self.location.pathname + self.location.search) ) {
+ output.push(node);
+ }
+ }
+}
+
+/******************************************************************************/
+
+class PSelectorMinTextLengthTask extends PSelectorTask {
+ constructor(task) {
+ super();
+ this.min = task[1];
+ }
+ transpose(node, output) {
+ if ( node.textContent.length >= this.min ) {
+ output.push(node);
+ }
+ }
+}
+
+/******************************************************************************/
+
+class PSelectorOthersTask extends PSelectorTask {
+ constructor() {
+ super();
+ this.targets = new Set();
+ }
+ begin() {
+ this.targets.clear();
+ }
+ end(output) {
+ const toKeep = new Set(this.targets);
+ const toDiscard = new Set();
+ const body = document.body;
+ let discard = null;
+ for ( let keep of this.targets ) {
+ while ( keep !== null && keep !== body ) {
+ toKeep.add(keep);
+ toDiscard.delete(keep);
+ discard = keep.previousElementSibling;
+ while ( discard !== null ) {
+ if (
+ nonVisualElements[discard.localName] !== true &&
+ toKeep.has(discard) === false
+ ) {
+ toDiscard.add(discard);
+ }
+ discard = discard.previousElementSibling;
+ }
+ discard = keep.nextElementSibling;
+ while ( discard !== null ) {
+ if (
+ nonVisualElements[discard.localName] !== true &&
+ toKeep.has(discard) === false
+ ) {
+ toDiscard.add(discard);
+ }
+ discard = discard.nextElementSibling;
+ }
+ keep = keep.parentElement;
+ }
+ }
+ for ( discard of toDiscard ) {
+ output.push(discard);
+ }
+ this.targets.clear();
+ }
+ transpose(candidate) {
+ for ( const target of this.targets ) {
+ if ( target.contains(candidate) ) { return; }
+ if ( candidate.contains(target) ) {
+ this.targets.delete(target);
+ }
+ }
+ this.targets.add(candidate);
+ }
+}
+
+/******************************************************************************/
+
+// https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277
+// Prepend `:scope ` if needed.
+class PSelectorSpathTask extends PSelectorTask {
+ constructor(task) {
+ super();
+ this.spath = task[1];
+ this.nth = /^(?:\s*[+~]|:)/.test(this.spath);
+ if ( this.nth ) { return; }
+ if ( /^\s*>/.test(this.spath) ) {
+ this.spath = `:scope ${this.spath.trim()}`;
+ }
+ }
+ transpose(node, output) {
+ const nodes = this.nth
+ ? PSelectorSpathTask.qsa(node, this.spath)
+ : node.querySelectorAll(this.spath);
+ for ( const node of nodes ) {
+ output.push(node);
+ }
+ }
+ // Helper method for other operators.
+ static qsa(node, selector) {
+ const parent = node.parentElement;
+ if ( parent === null ) { return []; }
+ let pos = 1;
+ for (;;) {
+ node = node.previousElementSibling;
+ if ( node === null ) { break; }
+ pos += 1;
+ }
+ return parent.querySelectorAll(
+ `:scope > :nth-child(${pos})${selector}`
+ );
+ }
+}
+
+/******************************************************************************/
+
+class PSelectorUpwardTask extends PSelectorTask {
+ constructor(task) {
+ super();
+ const arg = task[1];
+ if ( typeof arg === 'number' ) {
+ this.i = arg;
+ } else {
+ this.s = arg;
+ }
+ }
+ transpose(node, output) {
+ if ( this.s !== '' ) {
+ const parent = node.parentElement;
+ if ( parent === null ) { return; }
+ node = parent.closest(this.s);
+ if ( node === null ) { return; }
+ } else {
+ let nth = this.i;
+ for (;;) {
+ node = node.parentElement;
+ if ( node === null ) { return; }
+ nth -= 1;
+ if ( nth === 0 ) { break; }
+ }
+ }
+ output.push(node);
+ }
+}
+PSelectorUpwardTask.prototype.i = 0;
+PSelectorUpwardTask.prototype.s = '';
+
+/******************************************************************************/
+
+class PSelectorWatchAttrs extends PSelectorTask {
+ constructor(task) {
+ super();
+ this.observer = null;
+ this.observed = new WeakSet();
+ this.observerOptions = {
+ attributes: true,
+ subtree: true,
+ };
+ const attrs = task[1];
+ if ( Array.isArray(attrs) && attrs.length !== 0 ) {
+ this.observerOptions.attributeFilter = task[1];
+ }
+ }
+ // TODO: Is it worth trying to re-apply only the current selector?
+ handler() {
+ if ( proceduralFilterer instanceof Object ) {
+ proceduralFilterer.onDOMChanged([ null ]);
+ }
+ }
+ transpose(node, output) {
+ output.push(node);
+ if ( this.observed.has(node) ) { return; }
+ if ( this.observer === null ) {
+ this.observer = new MutationObserver(this.handler);
+ }
+ this.observer.observe(node, this.observerOptions);
+ this.observed.add(node);
+ }
+}
+
+/******************************************************************************/
+
+class PSelectorXpathTask extends PSelectorTask {
+ constructor(task) {
+ super();
+ this.xpe = document.createExpression(task[1], null);
+ this.xpr = null;
+ }
+ transpose(node, output) {
+ this.xpr = this.xpe.evaluate(
+ node,
+ XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
+ this.xpr
+ );
+ let j = this.xpr.snapshotLength;
+ while ( j-- ) {
+ const node = this.xpr.snapshotItem(j);
+ if ( node.nodeType === 1 ) {
+ output.push(node);
+ }
+ }
+ }
+}
+
+/******************************************************************************/
+
+class PSelector {
+ constructor(o) {
+ this.raw = o.raw;
+ this.selector = o.selector;
+ this.tasks = [];
+ const tasks = [];
+ if ( Array.isArray(o.tasks) === false ) { return; }
+ for ( const task of o.tasks ) {
+ const ctor = this.operatorToTaskMap.get(task[0]) || PSelectorVoidTask;
+ tasks.push(new ctor(task));
+ }
+ this.tasks = tasks;
+ }
+ prime(input) {
+ const root = input || document;
+ if ( this.selector === '' ) { return [ root ]; }
+ if ( input !== document ) {
+ const c0 = this.selector.charCodeAt(0);
+ if ( c0 === 0x2B /* + */ || c0 === 0x7E /* ~ */ ) {
+ return Array.from(PSelectorSpathTask.qsa(input, this.selector));
+ } else if ( c0 === 0x3E /* > */ ) {
+ return Array.from(input.querySelectorAll(`:scope ${this.selector}`));
+ }
+ }
+ return Array.from(root.querySelectorAll(this.selector));
+ }
+ exec(input) {
+ let nodes = this.prime(input);
+ for ( const task of this.tasks ) {
+ if ( nodes.length === 0 ) { break; }
+ const transposed = [];
+ task.begin();
+ for ( const node of nodes ) {
+ task.transpose(node, transposed);
+ }
+ task.end(transposed);
+ nodes = transposed;
+ }
+ return nodes;
+ }
+ test(input) {
+ const nodes = this.prime(input);
+ for ( const node of nodes ) {
+ let output = [ node ];
+ for ( const task of this.tasks ) {
+ const transposed = [];
+ task.begin();
+ for ( const node of output ) {
+ task.transpose(node, transposed);
+ }
+ task.end(transposed);
+ output = transposed;
+ if ( output.length === 0 ) { break; }
+ }
+ if ( output.length !== 0 ) { return true; }
+ }
+ return false;
+ }
+}
+PSelector.prototype.operatorToTaskMap = new Map([
+ [ 'has', PSelectorIfTask ],
+ [ 'has-text', PSelectorHasTextTask ],
+ [ 'if', PSelectorIfTask ],
+ [ 'if-not', PSelectorIfNotTask ],
+ [ 'matches-attr', PSelectorMatchesAttrTask ],
+ [ 'matches-css', PSelectorMatchesCSSTask ],
+ [ 'matches-css-after', PSelectorMatchesCSSAfterTask ],
+ [ 'matches-css-before', PSelectorMatchesCSSBeforeTask ],
+ [ 'matches-media', PSelectorMatchesMediaTask ],
+ [ 'matches-path', PSelectorMatchesPathTask ],
+ [ 'min-text-length', PSelectorMinTextLengthTask ],
+ [ 'not', PSelectorIfNotTask ],
+ [ 'others', PSelectorOthersTask ],
+ [ 'spath', PSelectorSpathTask ],
+ [ 'upward', PSelectorUpwardTask ],
+ [ 'watch-attr', PSelectorWatchAttrs ],
+ [ 'xpath', PSelectorXpathTask ],
+]);
+
+/******************************************************************************/
+
+class PSelectorRoot extends PSelector {
+ constructor(o) {
+ super(o);
+ this.budget = 200; // I arbitrary picked a 1/5 second
+ this.raw = o.raw;
+ this.cost = 0;
+ this.lastAllowanceTime = 0;
+ this.action = o.action;
+ }
+ prime(input) {
+ try {
+ return super.prime(input);
+ } catch (ex) {
+ }
+ return [];
+ }
+}
+
+/******************************************************************************/
+
+class ProceduralFilterer {
+ constructor(selectors) {
+ this.selectors = [];
+ this.masterToken = this.randomToken();
+ this.styleTokenMap = new Map();
+ this.styledNodes = new Set();
+ this.timer = undefined;
+ this.hideStyle = 'display:none!important;';
+ this.addSelectors(selectors);
+ // Important: commit now (do not go through onDOMChanged) to be sure
+ // first pass is going to happen asap.
+ this.uBOL_commitNow();
+ }
+
+ addSelectors() {
+ for ( const selector of selectors ) {
+ const pselector = new PSelectorRoot(selector);
+ this.primeProceduralSelector(pselector);
+ this.selectors.push(pselector);
+ }
+ this.onDOMChanged();
+ }
+
+ // This allows to perform potentially expensive initialization steps
+ // before the filters are ready to be applied.
+ primeProceduralSelector(pselector) {
+ if ( pselector.action === undefined ) {
+ this.styleTokenFromStyle(this.hideStyle);
+ } else if ( pselector.action[0] === 'style' ) {
+ this.styleTokenFromStyle(pselector.action[1]);
+ }
+ return pselector;
+ }
+
+ uBOL_commitNow() {
+ // https://github.com/uBlockOrigin/uBlock-issues/issues/341
+ // Be ready to unhide nodes which no longer matches any of
+ // the procedural selectors.
+ const toUnstyle = this.styledNodes;
+ this.styledNodes = new Set();
+
+ let t0 = Date.now();
+
+ for ( const pselector of this.selectors.values() ) {
+ const allowance = Math.floor((t0 - pselector.lastAllowanceTime) / 2000);
+ if ( allowance >= 1 ) {
+ pselector.budget += allowance * 50;
+ if ( pselector.budget > 200 ) { pselector.budget = 200; }
+ pselector.lastAllowanceTime = t0;
+ }
+ if ( pselector.budget <= 0 ) { continue; }
+ const nodes = pselector.exec();
+ const t1 = Date.now();
+ pselector.budget += t0 - t1;
+ if ( pselector.budget < -500 ) {
+ console.info('uBOL: disabling %s', pselector.raw);
+ pselector.budget = -0x7FFFFFFF;
+ }
+ t0 = t1;
+ if ( nodes.length === 0 ) { continue; }
+ this.processNodes(nodes, pselector.action);
+ }
+
+ this.unprocessNodes(toUnstyle);
+ }
+
+ styleTokenFromStyle(style) {
+ if ( style === undefined ) { return; }
+ let styleToken = this.styleTokenMap.get(style);
+ if ( styleToken !== undefined ) { return styleToken; }
+ styleToken = this.randomToken();
+ this.styleTokenMap.set(style, styleToken);
+ uBOL_injectCSS(`[${this.masterToken}][${styleToken}]\n{${style}}\n`);
+ return styleToken;
+ }
+
+ processNodes(nodes, action) {
+ const op = action && action[0] || '';
+ const arg = op !== '' ? action[1] : '';
+ switch ( op ) {
+ case '':
+ /* fall through */
+ case 'style': {
+ const styleToken = this.styleTokenFromStyle(
+ arg === '' ? this.hideStyle : arg
+ );
+ for ( const node of nodes ) {
+ node.setAttribute(this.masterToken, '');
+ node.setAttribute(styleToken, '');
+ this.styledNodes.add(node);
+ }
+ break;
+ }
+ case 'remove': {
+ for ( const node of nodes ) {
+ node.remove();
+ node.textContent = '';
+ }
+ break;
+ }
+ case 'remove-attr': {
+ const reAttr = regexFromString(arg, true);
+ for ( const node of nodes ) {
+ for ( const name of node.getAttributeNames() ) {
+ if ( reAttr.test(name) === false ) { continue; }
+ node.removeAttribute(name);
+ }
+ }
+ break;
+ }
+ case 'remove-class': {
+ const reClass = regexFromString(arg, true);
+ for ( const node of nodes ) {
+ const cl = node.classList;
+ for ( const name of cl.values() ) {
+ if ( reClass.test(name) === false ) { continue; }
+ cl.remove(name);
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ // TODO: Current assumption is one style per hit element. Could be an
+ // issue if an element has multiple styling and one styling is
+ // brought back. Possibly too rare to care about this for now.
+ unprocessNodes(nodes) {
+ for ( const node of nodes ) {
+ if ( this.styledNodes.has(node) ) { continue; }
+ node.removeAttribute(this.masterToken);
+ }
+ }
+
+ randomToken() {
+ const n = Math.random();
+ return String.fromCharCode(n * 25 + 97) +
+ Math.floor(
+ (0.25 + n * 0.75) * Number.MAX_SAFE_INTEGER
+ ).toString(36).slice(-8);
+ }
+
+ onDOMChanged() {
+ if ( this.timer !== undefined ) { return; }
+ this.timer = self.requestAnimationFrame(( ) => {
+ this.timer = undefined;
+ this.uBOL_commitNow();
+ });
+ }
+}
+
+/******************************************************************************/
+
+const proceduralFilterer = new ProceduralFilterer(selectors);
+
+const observer = new MutationObserver(mutations => {
+ let domChanged = false;
+ for ( let i = 0; i < mutations.length && !domChanged; i++ ) {
+ const mutation = mutations[i];
+ for ( const added of mutation.addedNodes ) {
+ if ( added.nodeType !== 1 ) { continue; }
+ domChanged = true;
+ break;
+ }
+ if ( domChanged === false ) {
+ for ( const removed of mutation.removedNodes ) {
+ if ( removed.nodeType !== 1 ) { continue; }
+ domChanged = true;
+ break;
+ }
+ }
+ }
+ if ( domChanged === false ) { return; }
+ proceduralFilterer.onDOMChanged();
+});
+
+observer.observe(document, {
+ childList: true,
+ subtree: true,
+});
+
+/******************************************************************************/
+
+})();
+
+/******************************************************************************/
+
+void 0;
diff --git a/platform/mv3/extension/js/scripting/css-specific.js b/platform/mv3/extension/js/scripting/css-specific.js
new file mode 100644
index 0000000..faf997c
--- /dev/null
+++ b/platform/mv3/extension/js/scripting/css-specific.js
@@ -0,0 +1,120 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2019-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+// Important!
+// Isolate from global scope
+(function uBOL_cssSpecific() {
+
+/******************************************************************************/
+
+const specificImports = self.specificImports || [];
+self.specificImports = undefined;
+delete self.specificImports;
+
+/******************************************************************************/
+
+const hnParts = [];
+try { hnParts.push(...document.location.hostname.split('.')); }
+catch(ex) { }
+const hnpartslen = hnParts.length;
+if ( hnpartslen === 0 ) { return; }
+
+const selectors = [];
+
+for ( const { argsList, exceptionsMap, hostnamesMap, entitiesMap } of specificImports ) {
+ const todoIndices = new Set();
+ const tonotdoIndices = [];
+ // Exceptions
+ if ( exceptionsMap.size !== 0 ) {
+ for ( let i = 0; i < hnpartslen; i++ ) {
+ const hn = hnParts.slice(i).join('.');
+ const excepted = exceptionsMap.get(hn);
+ if ( excepted ) { tonotdoIndices.push(...excepted); }
+ }
+ exceptionsMap.clear();
+ }
+ // Hostname-based
+ if ( hostnamesMap.size !== 0 ) {
+ const collectArgIndices = hn => {
+ let argsIndices = hostnamesMap.get(hn);
+ if ( argsIndices === undefined ) { return; }
+ if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
+ for ( const argsIndex of argsIndices ) {
+ if ( tonotdoIndices.includes(argsIndex) ) { continue; }
+ todoIndices.add(argsIndex);
+ }
+ };
+ for ( let i = 0; i < hnpartslen; i++ ) {
+ const hn = hnParts.slice(i).join('.');
+ collectArgIndices(hn);
+ }
+ collectArgIndices('*');
+ hostnamesMap.clear();
+ }
+ // Entity-based
+ if ( entitiesMap.size !== 0 ) {
+ const n = hnpartslen - 1;
+ for ( let i = 0; i < n; i++ ) {
+ for ( let j = n; j > i; j-- ) {
+ const en = hnParts.slice(i,j).join('.');
+ let argsIndices = entitiesMap.get(en);
+ if ( argsIndices === undefined ) { continue; }
+ if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
+ for ( const argsIndex of argsIndices ) {
+ if ( tonotdoIndices.includes(argsIndex) ) { continue; }
+ todoIndices.add(argsIndex);
+ }
+ }
+ }
+ entitiesMap.clear();
+ }
+ for ( const i of todoIndices ) {
+ selectors.push(argsList[i]);
+ }
+ argsList.length = 0;
+}
+specificImports.length = 0;
+
+if ( selectors.length === 0 ) { return; }
+
+/******************************************************************************/
+
+(function uBOL_injectCSS(css, count = 10) {
+ chrome.runtime.sendMessage({ what: 'insertCSS', css }).catch(( ) => {
+ count -= 1;
+ if ( count === 0 ) { return; }
+ uBOL_injectCSS(css, count - 1);
+ });
+})(`${selectors.join(',')}{display:none!important;}`);
+
+/******************************************************************************/
+
+})();
+
+/******************************************************************************/
+
+void 0;
diff --git a/platform/mv3/extension/js/settings.js b/platform/mv3/extension/js/settings.js
new file mode 100644
index 0000000..1a95ac0
--- /dev/null
+++ b/platform/mv3/extension/js/settings.js
@@ -0,0 +1,488 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+import { browser, sendMessage, localRead, localWrite } from './ext.js';
+import { i18n$, i18n } from './i18n.js';
+import { dom, qs$, qsa$ } from './dom.js';
+import punycode from './punycode.js';
+
+/******************************************************************************/
+
+const rulesetMap = new Map();
+let cachedRulesetData = {};
+let hideUnusedSet = new Set([ 'regions' ]);
+
+/******************************************************************************/
+
+function renderNumber(value) {
+ return value.toLocaleString();
+}
+
+function hashFromIterable(iter) {
+ return Array.from(iter).sort().join('\n');
+}
+
+/******************************************************************************/
+
+function rulesetStats(rulesetId) {
+ const hasOmnipotence = cachedRulesetData.defaultFilteringMode > 1;
+ const rulesetDetails = rulesetMap.get(rulesetId);
+ if ( rulesetDetails === undefined ) { return; }
+ const { rules, filters } = rulesetDetails;
+ let ruleCount = rules.plain + rules.regex;
+ if ( hasOmnipotence ) {
+ ruleCount += rules.removeparam + rules.redirect + rules.modifyHeaders;
+ }
+ const filterCount = filters.accepted;
+ return { ruleCount, filterCount };
+}
+
+/******************************************************************************/
+
+function renderFilterLists() {
+ const { enabledRulesets, rulesetDetails } = cachedRulesetData;
+ const listGroupTemplate = qs$('#templates .groupEntry');
+ const listEntryTemplate = qs$('#templates .listEntry');
+ const listStatsTemplate = i18n$('perRulesetStats');
+ const groupNames = new Map([ [ 'user', '' ] ]);
+
+ const liFromListEntry = function(ruleset, li, hideUnused) {
+ if ( !li ) {
+ li = dom.clone(listEntryTemplate);
+ }
+ const on = enabledRulesets.includes(ruleset.id);
+ dom.cl.toggle(li, 'checked', on);
+ dom.cl.toggle(li, 'unused', hideUnused && !on);
+ qs$(li, 'input[type="checkbox"]').checked = on;
+ if ( dom.attr(li, 'data-listkey') !== ruleset.id ) {
+ dom.attr(li, 'data-listkey', ruleset.id);
+ qs$(li, '.listname').append(i18n.patchUnicodeFlags(ruleset.name));
+ dom.cl.remove(li, 'toRemove');
+ if ( ruleset.homeURL ) {
+ dom.cl.add(li, 'support');
+ dom.attr(qs$(li, 'a.support'), 'href', ruleset.homeURL);
+ } else {
+ dom.cl.remove(li, 'support');
+ }
+ if ( ruleset.instructionURL ) {
+ dom.cl.add(li, 'mustread');
+ dom.attr(qs$(li, 'a.mustread'), 'href', ruleset.instructionURL);
+ } else {
+ dom.cl.remove(li, 'mustread');
+ }
+ dom.cl.toggle(li, 'isDefault', ruleset.id === 'default');
+ }
+ const stats = rulesetStats(ruleset.id);
+ li.title = listStatsTemplate
+ .replace('{{ruleCount}}', renderNumber(stats.ruleCount))
+ .replace('{{filterCount}}', renderNumber(stats.filterCount));
+ dom.attr(
+ qs$(li, '.input.checkbox'),
+ 'disabled',
+ stats.ruleCount === 0 ? '' : null
+ );
+ dom.cl.remove(li, 'discard');
+ return li;
+ };
+
+ const listEntryCountFromGroup = function(groupRulesets) {
+ if ( Array.isArray(groupRulesets) === false ) { return ''; }
+ let count = 0,
+ total = 0;
+ for ( const ruleset of groupRulesets ) {
+ if ( enabledRulesets.includes(ruleset.id) ) {
+ count += 1;
+ }
+ total += 1;
+ }
+ return total !== 0 ?
+ `(${count.toLocaleString()}/${total.toLocaleString()})` :
+ '';
+ };
+
+ const liFromListGroup = function(groupKey, groupRulesets) {
+ let liGroup = qs$(`#lists > .groupEntry[data-groupkey="${groupKey}"]`);
+ if ( liGroup === null ) {
+ liGroup = dom.clone(listGroupTemplate);
+ let groupName = groupNames.get(groupKey);
+ if ( groupName === undefined ) {
+ groupName = i18n$('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1));
+ groupNames.set(groupKey, groupName);
+ }
+ if ( groupName !== '' ) {
+ dom.text(qs$(liGroup, '.geName'), groupName);
+ }
+ }
+ if ( qs$(liGroup, '.geName:empty') === null ) {
+ dom.text(
+ qs$(liGroup, '.geCount'),
+ listEntryCountFromGroup(groupRulesets)
+ );
+ }
+ const hideUnused = mustHideUnusedLists(groupKey);
+ dom.cl.toggle(liGroup, 'hideUnused', hideUnused);
+ const ulGroup = qs$(liGroup, '.listEntries');
+ if ( !groupRulesets ) { return liGroup; }
+ groupRulesets.sort(function(a, b) {
+ return (a.name || '').localeCompare(b.name || '');
+ });
+ for ( let i = 0; i < groupRulesets.length; i++ ) {
+ const liEntry = liFromListEntry(
+ groupRulesets[i],
+ ulGroup.children[i],
+ hideUnused
+ );
+ if ( liEntry.parentElement === null ) {
+ ulGroup.appendChild(liEntry);
+ }
+ }
+ return liGroup;
+ };
+
+ // Visually split the filter lists in groups
+ const ulLists = qs$('#lists');
+ const groups = new Map([
+ [
+ 'default',
+ rulesetDetails.filter(ruleset =>
+ ruleset.id === 'default'
+ ),
+ ],
+ [
+ 'annoyances',
+ rulesetDetails.filter(ruleset =>
+ ruleset.group === 'annoyances'
+ ),
+ ],
+ [
+ 'misc',
+ rulesetDetails.filter(ruleset =>
+ ruleset.id !== 'default' &&
+ ruleset.group === undefined &&
+ typeof ruleset.lang !== 'string'
+ ),
+ ],
+ [
+ 'regions',
+ rulesetDetails.filter(ruleset =>
+ typeof ruleset.lang === 'string'
+ ),
+ ],
+ ]);
+
+ dom.cl.toggle(dom.body, 'hideUnused', mustHideUnusedLists('*'));
+
+ for ( const [ groupKey, groupRulesets ] of groups ) {
+ const liGroup = liFromListGroup(groupKey, groupRulesets);
+ dom.attr(liGroup, 'data-groupkey', groupKey);
+ if ( liGroup.parentElement === null ) {
+ ulLists.appendChild(liGroup);
+ }
+ }
+}
+
+/******************************************************************************/
+
+function renderWidgets() {
+ if ( cachedRulesetData.firstRun ) {
+ dom.cl.add(dom.body, 'firstRun');
+ }
+
+ renderDefaultMode();
+ renderTrustedSites();
+
+ qs$('#autoReload input[type="checkbox"').checked = cachedRulesetData.autoReload;
+
+ // Compute total counts
+ let rulesetCount = 0;
+ let filterCount = 0;
+ let ruleCount = 0;
+ for ( const liEntry of qsa$('#lists .listEntry[data-listkey]') ) {
+ if ( qs$(liEntry, 'input[type="checkbox"]:checked') === null ) { continue; }
+ rulesetCount += 1;
+ const stats = rulesetStats(liEntry.dataset.listkey);
+ if ( stats === undefined ) { continue; }
+ ruleCount += stats.ruleCount;
+ filterCount += stats.filterCount;
+ }
+ dom.text('#listsOfBlockedHostsPrompt', i18n$('perRulesetStats')
+ .replace('{{ruleCount}}', ruleCount.toLocaleString())
+ .replace('{{filterCount}}', filterCount.toLocaleString())
+ );
+
+ dom.cl.toggle(dom.body, 'noMoreRuleset',
+ rulesetCount === cachedRulesetData.maxNumberOfEnabledRulesets
+ );
+}
+
+/******************************************************************************/
+
+function renderDefaultMode() {
+ const defaultLevel = cachedRulesetData.defaultFilteringMode;
+ if ( defaultLevel !== 0 ) {
+ qs$(`.filteringModeCard input[type="radio"][value="${defaultLevel}"]`).checked = true;
+ } else {
+ dom.prop('.filteringModeCard input[type="radio"]', 'checked', false);
+ }
+}
+
+/******************************************************************************/
+
+async function onFilteringModeChange(ev) {
+ const input = ev.target;
+ const newLevel = parseInt(input.value, 10);
+
+ switch ( newLevel ) {
+ case 1: { // Revoke broad permissions
+ await browser.permissions.remove({
+ origins: [ '<all_urls>' ]
+ });
+ cachedRulesetData.defaultFilteringMode = 1;
+ break;
+ }
+ case 2:
+ case 3: { // Request broad permissions
+ const granted = await browser.permissions.request({
+ origins: [ '<all_urls>' ]
+ });
+ if ( granted ) {
+ const actualLevel = await sendMessage({
+ what: 'setDefaultFilteringMode',
+ level: newLevel,
+ });
+ cachedRulesetData.defaultFilteringMode = actualLevel;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ renderFilterLists();
+ renderWidgets();
+}
+
+dom.on(
+ '#defaultFilteringMode',
+ 'change',
+ '.filteringModeCard input[type="radio"]',
+ ev => { onFilteringModeChange(ev); }
+);
+
+/******************************************************************************/
+
+dom.on('#autoReload input[type="checkbox"', 'change', ev => {
+ sendMessage({
+ what: 'setAutoReload',
+ state: ev.target.checked,
+ });
+});
+
+/******************************************************************************/
+
+function renderTrustedSites() {
+ const textarea = qs$('#trustedSites');
+ const hostnames = cachedRulesetData.trustedSites;
+ textarea.value = hostnames.map(hn => punycode.toUnicode(hn)).join('\n');
+ if ( textarea.value !== '' ) {
+ textarea.value += '\n';
+ }
+}
+
+function changeTrustedSites() {
+ const hostnames = getStagedTrustedSites();
+ const hash = hashFromIterable(cachedRulesetData.trustedSites);
+ if ( hashFromIterable(hostnames) === hash ) { return; }
+ sendMessage({
+ what: 'setTrustedSites',
+ hostnames,
+ });
+}
+
+function getStagedTrustedSites() {
+ const textarea = qs$('#trustedSites');
+ return textarea.value.split(/\s/).map(hn => {
+ try {
+ return punycode.toASCII(
+ (new URL(`https://${hn}/`)).hostname
+ );
+ } catch(_) {
+ }
+ return '';
+ }).filter(hn => hn !== '');
+}
+
+dom.on('#trustedSites', 'blur', changeTrustedSites);
+
+self.addEventListener('beforeunload', changeTrustedSites);
+
+/******************************************************************************/
+
+async function applyEnabledRulesets() {
+ const enabledRulesets = [];
+ for ( const liEntry of qsa$('#lists .listEntry[data-listkey]') ) {
+ const checked = qs$(liEntry, 'input[type="checkbox"]:checked') !== null;
+ dom.cl.toggle(liEntry, 'checked', checked);
+ if ( checked === false ) { continue; }
+ enabledRulesets.push(liEntry.dataset.listkey);
+ }
+
+ await sendMessage({
+ what: 'applyRulesets',
+ enabledRulesets,
+ });
+
+ renderWidgets();
+}
+
+dom.on('#lists', 'change', '.listEntry input[type="checkbox"]', ( ) => {
+ applyEnabledRulesets();
+});
+
+/******************************************************************************/
+
+// Collapsing of unused lists.
+
+function mustHideUnusedLists(which) {
+ const hideAll = hideUnusedSet.has('*');
+ if ( which === '*' ) { return hideAll; }
+ return hideUnusedSet.has(which) !== hideAll;
+}
+
+function toggleHideUnusedLists(which) {
+ const doesHideAll = hideUnusedSet.has('*');
+ let groupSelector;
+ let mustHide;
+ if ( which === '*' ) {
+ mustHide = doesHideAll === false;
+ groupSelector = '';
+ hideUnusedSet.clear();
+ if ( mustHide ) {
+ hideUnusedSet.add(which);
+ }
+ dom.cl.toggle(dom.body, 'hideUnused', mustHide);
+ dom.cl.toggle('.groupEntry[data-groupkey]', 'hideUnused', mustHide);
+ } else {
+ const doesHide = hideUnusedSet.has(which);
+ if ( doesHide ) {
+ hideUnusedSet.delete(which);
+ } else {
+ hideUnusedSet.add(which);
+ }
+ mustHide = doesHide === doesHideAll;
+ groupSelector = `.groupEntry[data-groupkey="${which}"]`;
+ dom.cl.toggle(groupSelector, 'hideUnused', mustHide);
+ }
+
+ for ( const elem of qsa$(`#lists ${groupSelector} .listEntry[data-listkey] input[type="checkbox"]:not(:checked)`) ) {
+ dom.cl.toggle(
+ elem.closest('.listEntry[data-listkey]'),
+ 'unused',
+ mustHide
+ );
+ }
+
+ localWrite('hideUnusedFilterLists', Array.from(hideUnusedSet));
+}
+
+dom.on('#lists', 'click', '.groupEntry[data-groupkey] > .geDetails', ev => {
+ toggleHideUnusedLists(
+ dom.attr(ev.target.closest('[data-groupkey]'), 'data-groupkey')
+ );
+});
+
+// Initialize from saved state.
+localRead('hideUnusedFilterLists').then(value => {
+ if ( Array.isArray(value) === false ) { return; }
+ hideUnusedSet = new Set(value);
+});
+
+/******************************************************************************/
+
+const bc = new self.BroadcastChannel('uBOL');
+
+bc.onmessage = ev => {
+ const message = ev.data;
+ if ( message instanceof Object === false ) { return; }
+ const local = cachedRulesetData;
+ let render = false;
+
+ // Keep added sites which have not yet been committed
+ if ( message.trustedSites !== undefined ) {
+ if ( hashFromIterable(message.trustedSites) !== hashFromIterable(local.trustedSites) ) {
+ const current = new Set(local.trustedSites);
+ const staged = new Set(getStagedTrustedSites());
+ for ( const hn of staged ) {
+ if ( current.has(hn) === false ) { continue; }
+ staged.delete(hn);
+ }
+ const combined = Array.from(new Set([ ...message.trustedSites, ...staged ]));
+ local.trustedSites = combined;
+ render = true;
+ }
+ }
+
+ if ( message.defaultFilteringMode !== undefined ) {
+ if ( message.defaultFilteringMode !== local.defaultFilteringMode ) {
+ local.defaultFilteringMode = message.defaultFilteringMode;
+ render = true;
+ }
+ }
+
+ if ( message.autoReload !== undefined ) {
+ if ( message.autoReload !== local.autoReload ) {
+ local.autoReload = message.autoReload;
+ render = true;
+ }
+ }
+
+ if ( message.enabledRulesets !== undefined ) {
+ if ( hashFromIterable(message.enabledRulesets) !== hashFromIterable(local.enabledRulesets) ) {
+ local.enabledRulesets = message.enabledRulesets;
+ render = true;
+ }
+ }
+
+ if ( render === false ) { return; }
+ renderFilterLists();
+ renderWidgets();
+};
+
+/******************************************************************************/
+
+sendMessage({
+ what: 'getOptionsPageData',
+}).then(data => {
+ if ( !data ) { return; }
+ cachedRulesetData = data;
+ rulesetMap.clear();
+ cachedRulesetData.rulesetDetails.forEach(rule => rulesetMap.set(rule.id, rule));
+ try {
+ renderFilterLists();
+ renderWidgets();
+ } catch(ex) {
+ }
+}).catch(reason => {
+ console.trace(reason);
+});
+
+/******************************************************************************/
diff --git a/platform/mv3/extension/js/theme.js b/platform/mv3/extension/js/theme.js
new file mode 100644
index 0000000..a61384e
--- /dev/null
+++ b/platform/mv3/extension/js/theme.js
@@ -0,0 +1,35 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+import { dom } from './dom.js';
+
+/******************************************************************************/
+
+const mql = self.matchMedia('(prefers-color-scheme: dark)');
+const theme = mql instanceof Object && mql.matches === true
+ ? 'dark'
+ : 'light';
+dom.cl.toggle(dom.html, 'dark', theme === 'dark');
+dom.cl.toggle(dom.html, 'light', theme !== 'dark');
diff --git a/platform/mv3/extension/js/utils.js b/platform/mv3/extension/js/utils.js
new file mode 100644
index 0000000..b1a463a
--- /dev/null
+++ b/platform/mv3/extension/js/utils.js
@@ -0,0 +1,151 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2022-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+import { browser } from './ext.js';
+
+/******************************************************************************/
+
+function parsedURLromOrigin(origin) {
+ try {
+ return new URL(origin);
+ } catch(ex) {
+ }
+}
+
+/******************************************************************************/
+
+const toBroaderHostname = hn => {
+ if ( hn === '*' ) { return ''; }
+ const pos = hn.indexOf('.');
+ return pos !== -1 ? hn.slice(pos+1) : '*';
+};
+
+/******************************************************************************/
+
+// Is hna descendant hostname of hnb?
+
+const isDescendantHostname = (hna, hnb) => {
+ if ( hnb === 'all-urls' ) { return true; }
+ if ( hna.endsWith(hnb) === false ) { return false; }
+ if ( hna === hnb ) { return false; }
+ return hna.charCodeAt(hna.length - hnb.length - 1) === 0x2E /* '.' */;
+};
+
+const isDescendantHostnameOfIter = (hna, iterb) => {
+ const setb = iterb instanceof Set ? iterb : new Set(iterb);
+ if ( setb.has('all-urls') || setb.has('*') ) { return true; }
+ let hn = hna;
+ while ( hn ) {
+ const pos = hn.indexOf('.');
+ if ( pos === -1 ) { break; }
+ hn = hn.slice(pos + 1);
+ if ( setb.has(hn) ) { return true; }
+ }
+ return false;
+};
+
+const intersectHostnameIters = (itera, iterb) => {
+ const setb = iterb instanceof Set ? iterb : new Set(iterb);
+ if ( setb.has('all-urls') || setb.has('*') ) { return Array.from(itera); }
+ const out = [];
+ for ( const hna of itera ) {
+ if ( setb.has(hna) || isDescendantHostnameOfIter(hna, setb) ) {
+ out.push(hna);
+ }
+ }
+ return out;
+};
+
+const subtractHostnameIters = (itera, iterb) => {
+ const setb = iterb instanceof Set ? iterb : new Set(iterb);
+ if ( setb.has('all-urls') || setb.has('*') ) { return []; }
+ const out = [];
+ for ( const hna of itera ) {
+ if ( setb.has(hna) ) { continue; }
+ if ( isDescendantHostnameOfIter(hna, setb) ) { continue; }
+ out.push(hna);
+ }
+ return out;
+};
+
+/******************************************************************************/
+
+const matchesFromHostnames = hostnames => {
+ const out = [];
+ for ( const hn of hostnames ) {
+ if ( hn === '*' || hn === 'all-urls' ) {
+ out.length = 0;
+ out.push('<all_urls>');
+ break;
+ }
+ out.push(`*://*.${hn}/*`);
+ }
+ return out;
+};
+
+const hostnamesFromMatches = origins => {
+ const out = [];
+ for ( const origin of origins ) {
+ if ( origin === '<all_urls>' ) {
+ out.push('all-urls');
+ continue;
+ }
+ const match = /^\*:\/\/(?:\*\.)?([^\/]+)\/\*/.exec(origin);
+ if ( match === null ) { continue; }
+ out.push(match[1]);
+ }
+ return out;
+};
+
+/******************************************************************************/
+
+export const broadcastMessage = message => {
+ const bc = new self.BroadcastChannel('uBOL');
+ bc.postMessage(message);
+};
+
+/******************************************************************************/
+
+const ubolLog = (...args) => {
+ // Do not pollute dev console in stable release.
+ if ( browser.runtime.id === 'ddkjiahejlhfcafbddmgiahcphecmpfh' ) { return; }
+ console.info('[uBOL]', ...args);
+};
+
+/******************************************************************************/
+
+export {
+ parsedURLromOrigin,
+ toBroaderHostname,
+ isDescendantHostname,
+ isDescendantHostnameOfIter,
+ intersectHostnameIters,
+ subtractHostnameIters,
+ matchesFromHostnames,
+ hostnamesFromMatches,
+ ubolLog,
+};
diff --git a/platform/mv3/extension/managed_storage.json b/platform/mv3/extension/managed_storage.json
new file mode 100644
index 0000000..8571f59
--- /dev/null
+++ b/platform/mv3/extension/managed_storage.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "http://json-schema.org/draft-03/schema#",
+ "type": "object",
+ "properties": {
+ "noFiltering": {
+ "title": "List of domains for which no filtering should occur",
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "disableFirstRunPage": {
+ "title": "Disable first run page",
+ "type": "boolean"
+ }
+ }
+}
diff --git a/platform/mv3/extension/popup.html b/platform/mv3/extension/popup.html
new file mode 100644
index 0000000..7492469
--- /dev/null
+++ b/platform/mv3/extension/popup.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html id="uBO-popup-panel" class="desktop">
+
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="stylesheet" href="css/default.css">
+<link rel="stylesheet" href="css/common.css">
+<link rel="stylesheet" href="css/fa-icons.css">
+<link rel="stylesheet" href="css/filtering-mode.css">
+<link rel="stylesheet" href="css/popup.css">
+<title data-i18n="extName"></title>
+</head>
+
+<body class="loading" data-section="">
+<div id="main">
+ <div id="hostname"><span></span>&shy;<span></span></div>
+ <!-- -------- -->
+ <div class="filteringModeSlider">
+ <div class="filteringModeButton"><div></div></div>
+ <span data-level="0"></span>
+ <span data-level="1"></span>
+ <span data-level="2"></span>
+ <span data-level="3"></span>
+ </div>
+ <!-- -------- -->
+ <div id="filteringModeText"><label data-i18n="popupFilteringModeLabel">_</label><br><span>_</span><span></span></div>
+ <div class="toolRibbon pageTools">
+ <span></span>
+ <span></span>
+ <span></span>
+ <span></span>
+ <span class="fa-icon tool enabled" tabindex="0" data-i18n-title="popupTipDashboard">cogs<span class="caption" data-i18n="popupTipDashboard"></span></span>
+ </div>
+ <!-- -------- -->
+ <div id="rulesetStats" data-section="a">
+ </div>
+ <hr data-section="a">
+ <!-- -------- -->
+ <div id="moreOrLess">
+ <span id="moreButton">
+ <span data-i18n="popupMoreButton">_</span>&emsp;<span class="fa-icon">angle-up</span>
+ </span>
+ <span id="lessButton">
+ <span class="fa-icon">angle-up</span>&emsp;<span data-i18n="popupLessButton">_</span>
+ </span>
+ </div>
+</div>
+
+<div id="templates">
+ <div class="rulesetDetails"><h1></h1><p data-section="b"></p></div>
+</div>
+
+<script src="js/theme.js" type="module"></script>
+<script src="js/fa-icons.js" type="module"></script>
+<script src="js/i18n.js" type="module"></script>
+<script src="js/popup.js" type="module"></script>
+
+</body>
+
+</html>
diff --git a/platform/mv3/firefox/background.html b/platform/mv3/firefox/background.html
new file mode 100644
index 0000000..58e9c5e
--- /dev/null
+++ b/platform/mv3/firefox/background.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>uBlock Origin Background Page</title>
+</head>
+<body>
+<script src="js/background.js" type="module"></script>
+</body>
+</html>
diff --git a/platform/mv3/firefox/manifest.json b/platform/mv3/firefox/manifest.json
new file mode 100644
index 0000000..723ef0f
--- /dev/null
+++ b/platform/mv3/firefox/manifest.json
@@ -0,0 +1,54 @@
+{
+ "action": {
+ "default_area": "navbar",
+ "default_icon": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png",
+ "64": "img/icon_64.png"
+ },
+ "default_popup": "popup.html"
+ },
+ "author": "Raymond Hill",
+ "background": {
+ "scripts": [ "/js/background.js" ],
+ "type": "module"
+ },
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "uBOLite@raymondhill.net",
+ "strict_min_version": "114.0"
+ },
+ "gecko_android": {
+ "strict_min_version": "114.0"
+ }
+ },
+ "declarative_net_request": {
+ "rule_resources": [
+ ]
+ },
+ "default_locale": "en",
+ "description": "__MSG_extShortDesc__",
+ "icons": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png",
+ "64": "img/icon_64.png",
+ "128": "img/icon_128.png"
+ },
+ "manifest_version": 3,
+ "name": "__MSG_extName__",
+ "options_ui": {
+ "page": "dashboard.html"
+ },
+ "optional_permissions": [
+ "<all_urls>"
+ ],
+ "permissions": [
+ "activeTab",
+ "declarativeNetRequest",
+ "scripting",
+ "storage"
+ ],
+ "short_name": "uBO Lite",
+ "version": "1.0",
+ "web_accessible_resources": []
+}
diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js
new file mode 100644
index 0000000..6b608bf
--- /dev/null
+++ b/platform/mv3/make-rulesets.js
@@ -0,0 +1,1344 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2022-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+/******************************************************************************/
+
+import fs from 'fs/promises';
+import https from 'https';
+import path from 'path';
+import process from 'process';
+import { createHash, randomBytes } from 'crypto';
+import redirectResourcesMap from './js/redirect-resources.js';
+import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.js';
+import * as sfp from './js/static-filtering-parser.js';
+import * as makeScriptlet from './make-scriptlets.js';
+import { safeReplace } from './safe-replace.js';
+
+/******************************************************************************/
+
+const commandLineArgs = (( ) => {
+ const args = new Map();
+ let name, value;
+ for ( const arg of process.argv.slice(2) ) {
+ const pos = arg.indexOf('=');
+ if ( pos === -1 ) {
+ name = arg;
+ value = '';
+ } else {
+ name = arg.slice(0, pos);
+ value = arg.slice(pos+1);
+ }
+ args.set(name, value);
+ }
+ return args;
+})();
+
+const platform = commandLineArgs.get('platform') || 'chromium';
+const outputDir = commandLineArgs.get('output') || '.';
+const cacheDir = `${outputDir}/../mv3-data`;
+const rulesetDir = `${outputDir}/rulesets`;
+const scriptletDir = `${rulesetDir}/scripting`;
+const env = [
+ platform,
+ 'mv3',
+ 'ublock',
+ 'ubol',
+ 'user_stylesheet',
+];
+
+if ( platform !== 'firefox' ) {
+ env.push('native_css_has');
+}
+
+/******************************************************************************/
+
+const jsonSetMapReplacer = (k, v) => {
+ if ( v instanceof Set || v instanceof Map ) {
+ if ( v.size === 0 ) { return; }
+ return Array.from(v);
+ }
+ return v;
+};
+
+const uidint32 = (s) => {
+ const h = createHash('sha256').update(s).digest('hex').slice(0,8);
+ return parseInt(h,16) & 0x7FFFFFFF;
+};
+
+const hnSort = (a, b) =>
+ a.split('.').reverse().join('.').localeCompare(
+ b.split('.').reverse().join('.')
+ );
+
+/******************************************************************************/
+
+const stdOutput = [];
+
+const log = (text, silent = false) => {
+ stdOutput.push(text);
+ if ( silent === false ) {
+ console.log(text);
+ }
+};
+
+/******************************************************************************/
+
+const urlToFileName = url => {
+ return url
+ .replace(/^https?:\/\//, '')
+ .replace(/\//g, '_')
+ ;
+};
+
+const fetchList = (url, cacheDir) => {
+ return new Promise((resolve, reject) => {
+ const fname = urlToFileName(url);
+ fs.readFile(`${cacheDir}/${fname}`, { encoding: 'utf8' }).then(content => {
+ log(`\tFetched local ${url}`);
+ resolve({ url, content });
+ }).catch(( ) => {
+ log(`\tFetching remote ${url}`);
+ https.get(url, response => {
+ const data = [];
+ response.on('data', chunk => {
+ data.push(chunk.toString());
+ });
+ response.on('end', ( ) => {
+ const content = data.join('');
+ try {
+ writeFile(`${cacheDir}/${fname}`, content);
+ } catch (ex) {
+ }
+ resolve({ url, content });
+ });
+ }).on('error', error => {
+ reject(error);
+ });
+ });
+ });
+};
+
+/******************************************************************************/
+
+const writeFile = async (fname, data) => {
+ const dir = path.dirname(fname);
+ await fs.mkdir(dir, { recursive: true });
+ const promise = fs.writeFile(fname, data);
+ writeOps.push(promise);
+ return promise;
+};
+
+const copyFile = async (from, to) => {
+ const dir = path.dirname(to);
+ await fs.mkdir(dir, { recursive: true });
+ const promise = fs.copyFile(from, to);
+ writeOps.push(promise);
+ return promise;
+};
+
+const writeOps = [];
+
+/******************************************************************************/
+
+const ruleResources = [];
+const rulesetDetails = [];
+const scriptletStats = new Map();
+const genericDetails = new Map();
+const requiredRedirectResources = new Set();
+
+/******************************************************************************/
+
+async function fetchAsset(assetDetails) {
+ // Remember fetched URLs
+ const fetchedURLs = new Set();
+
+ // Fetch list and expand `!#include` directives
+ let parts = assetDetails.urls.map(url => ({ url }));
+ while ( parts.every(v => typeof v === 'string') === false ) {
+ const newParts = [];
+ for ( const part of parts ) {
+ if ( typeof part === 'string' ) {
+ newParts.push(part);
+ continue;
+ }
+ if ( fetchedURLs.has(part.url) ) {
+ newParts.push('');
+ continue;
+ }
+ fetchedURLs.add(part.url);
+ if ( part.url.startsWith('https://ublockorigin.github.io/uAssets/filters/') ) {
+ newParts.push(`!#trusted on ${assetDetails.secret}`);
+ }
+ newParts.push(
+ fetchList(part.url, cacheDir).then(details => {
+ const { url } = details;
+ const content = details.content.trim();
+ if ( typeof content === 'string' && content !== '' ) {
+ if (
+ content.startsWith('<') === false ||
+ content.endsWith('>') === false
+ ) {
+ return { url, content };
+ }
+ }
+ log(`No valid content for ${details.name}`);
+ return { url, content: '' };
+ })
+ );
+ newParts.push(`!#trusted off ${assetDetails.secret}`);
+ }
+ parts = await Promise.all(newParts);
+ parts = sfp.utils.preparser.expandIncludes(parts, env);
+ }
+ const text = parts.join('\n');
+
+ if ( text === '' ) {
+ log('No filterset found');
+ }
+ return text;
+}
+
+/******************************************************************************/
+
+const isUnsupported = rule =>
+ rule._error !== undefined;
+
+const isRegex = rule =>
+ rule.condition !== undefined &&
+ rule.condition.regexFilter !== undefined;
+
+const isRedirect = rule =>
+ rule.action !== undefined &&
+ rule.action.type === 'redirect' &&
+ rule.action.redirect.extensionPath !== undefined;
+
+const isModifyHeaders = rule =>
+ rule.action !== undefined &&
+ rule.action.type === 'modifyHeaders';
+
+const isRemoveparam = rule =>
+ rule.action !== undefined &&
+ rule.action.type === 'redirect' &&
+ rule.action.redirect.transform !== undefined;
+
+const isGood = rule =>
+ isUnsupported(rule) === false &&
+ isRedirect(rule) === false &&
+ isModifyHeaders(rule) === false &&
+ isRemoveparam(rule) === false;
+
+/******************************************************************************/
+
+// Two distinct hostnames:
+// www.example.com
+// example.com
+// Can be reduced to a single one:
+// example.com
+// Since if example.com matches, then www.example.com (or any other subdomain
+// of example.com) will always match.
+
+function pruneHostnameArray(hostnames) {
+ const rootMap = new Map();
+ for ( const hostname of hostnames ) {
+ const labels = hostname.split('.');
+ let currentMap = rootMap;
+ let i = labels.length;
+ while ( i-- ) {
+ const label = labels[i];
+ let nextMap = currentMap.get(label);
+ if ( nextMap === null ) { break; }
+ if ( nextMap === undefined ) {
+ if ( i === 0 ) {
+ currentMap.set(label, (nextMap = null));
+ } else {
+ currentMap.set(label, (nextMap = new Map()));
+ }
+ } else if ( i === 0 ) {
+ currentMap.set(label, null);
+ }
+ currentMap = nextMap;
+ }
+ }
+ const assemble = (currentMap, currentHostname, out) => {
+ for ( const [ label, nextMap ] of currentMap ) {
+ const nextHostname = currentHostname === ''
+ ? label
+ : `${label}.${currentHostname}`;
+ if ( nextMap === null ) {
+ out.push(nextHostname);
+ } else {
+ assemble(nextMap, nextHostname, out);
+ }
+ }
+ return out;
+ };
+ return assemble(rootMap, '', []);
+}
+
+/*******************************************************************************
+ *
+ * For large rulesets, one rule per line for compromise between size and
+ * readability. This also means that the number of lines in resulting file
+ * representative of the number of rules in the ruleset.
+ *
+ * */
+
+function toJSONRuleset(ruleset) {
+ const replacer = (k, v) => {
+ if ( k.startsWith('_') ) { return; }
+ if ( Array.isArray(v) ) {
+ return v.sort();
+ }
+ if ( v instanceof Object ) {
+ const sorted = {};
+ for ( const kk of Object.keys(v).sort() ) {
+ sorted[kk] = v[kk];
+ }
+ return sorted;
+ }
+ return v;
+ };
+ const indent = ruleset.length > 10 ? undefined : 1;
+ const out = [];
+ for ( const rule of ruleset ) {
+ out.push(JSON.stringify(rule, replacer, indent));
+ }
+ return `[\n${out.join(',\n')}\n]\n`;
+}
+
+/******************************************************************************/
+
+async function processNetworkFilters(assetDetails, network) {
+ const { ruleset: rules } = network;
+ log(`Input filter count: ${network.filterCount}`);
+ log(`\tAccepted filter count: ${network.acceptedFilterCount}`);
+ log(`\tRejected filter count: ${network.rejectedFilterCount}`);
+ log(`Output rule count: ${rules.length}`);
+
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest/RuleCondition#browser_compatibility
+ // isUrlFilterCaseSensitive is true by default in Chromium. It will be
+ // false by default in Chromium 118+.
+ if ( platform !== 'firefox' ) {
+ for ( const rule of rules ) {
+ const { condition } = rule;
+ if ( condition === undefined ) { continue; }
+ if ( condition.urlFilter === undefined ) {
+ if ( condition.regexFilter === undefined ) { continue; }
+ }
+ if ( condition.isUrlFilterCaseSensitive === undefined ) {
+ condition.isUrlFilterCaseSensitive = false;
+ } else if ( condition.isUrlFilterCaseSensitive === true ) {
+ condition.isUrlFilterCaseSensitive = undefined;
+ }
+ }
+ }
+
+ // Minimize requestDomains arrays
+ for ( const rule of rules ) {
+ const condition = rule.condition;
+ if ( condition === undefined ) { continue; }
+ const requestDomains = condition.requestDomains;
+ if ( requestDomains === undefined ) { continue; }
+ const beforeCount = requestDomains.length;
+ condition.requestDomains = pruneHostnameArray(requestDomains);
+ const afterCount = condition.requestDomains.length;
+ if ( afterCount !== beforeCount ) {
+ log(`\tPruning requestDomains: from ${beforeCount} to ${afterCount}`);
+ }
+ }
+
+ const plainGood = rules.filter(rule => isGood(rule) && isRegex(rule) === false);
+ log(`\tPlain good: ${plainGood.length}`);
+ log(plainGood
+ .filter(rule => Array.isArray(rule._warning))
+ .map(rule => rule._warning.map(v => `\t\t${v}`))
+ .join('\n'),
+ true
+ );
+
+ const regexes = rules.filter(rule => isGood(rule) && isRegex(rule));
+ log(`\tMaybe good (regexes): ${regexes.length}`);
+
+ const redirects = rules.filter(rule =>
+ isUnsupported(rule) === false &&
+ isRedirect(rule)
+ );
+ redirects.forEach(rule => {
+ requiredRedirectResources.add(
+ rule.action.redirect.extensionPath.replace(/^\/+/, '')
+ );
+ });
+ log(`\tredirect=: ${redirects.length}`);
+
+ const removeparamsGood = rules.filter(rule =>
+ isUnsupported(rule) === false && isRemoveparam(rule)
+ );
+ const removeparamsBad = rules.filter(rule =>
+ isUnsupported(rule) && isRemoveparam(rule)
+ );
+ log(`\tremoveparams= (accepted/discarded): ${removeparamsGood.length}/${removeparamsBad.length}`);
+
+ const modifyHeaders = rules.filter(rule =>
+ isUnsupported(rule) === false &&
+ isModifyHeaders(rule)
+ );
+ log(`\tmodifyHeaders=: ${modifyHeaders.length}`);
+
+ const bad = rules.filter(rule =>
+ isUnsupported(rule)
+ );
+ log(`\tUnsupported: ${bad.length}`);
+ log(bad.map(rule => rule._error.map(v => `\t\t${v}`)).join('\n'), true);
+
+ writeFile(
+ `${rulesetDir}/main/${assetDetails.id}.json`,
+ toJSONRuleset(plainGood)
+ );
+
+ if ( regexes.length !== 0 ) {
+ writeFile(
+ `${rulesetDir}/regex/${assetDetails.id}.json`,
+ toJSONRuleset(regexes)
+ );
+ }
+
+ if ( removeparamsGood.length !== 0 ) {
+ writeFile(
+ `${rulesetDir}/removeparam/${assetDetails.id}.json`,
+ toJSONRuleset(removeparamsGood)
+ );
+ }
+
+ if ( redirects.length !== 0 ) {
+ writeFile(
+ `${rulesetDir}/redirect/${assetDetails.id}.json`,
+ toJSONRuleset(redirects)
+ );
+ }
+
+ if ( modifyHeaders.length !== 0 ) {
+ writeFile(
+ `${rulesetDir}/modify-headers/${assetDetails.id}.json`,
+ toJSONRuleset(modifyHeaders)
+ );
+ }
+
+ return {
+ total: rules.length,
+ plain: plainGood.length,
+ discarded: removeparamsBad.length,
+ rejected: bad.length,
+ regex: regexes.length,
+ removeparam: removeparamsGood.length,
+ redirect: redirects.length,
+ modifyHeaders: modifyHeaders.length,
+ };
+}
+
+/******************************************************************************/
+
+// TODO: unify css/scriptlet processing code since now css styles are
+// injected using scriptlet injection.
+
+// Load all available scriptlets into a key-val map, where the key is the
+// scriptlet token, and val is the whole content of the file.
+
+let scriptletsMapPromise;
+
+function loadAllSourceScriptlets() {
+ if ( scriptletsMapPromise !== undefined ) {
+ return scriptletsMapPromise;
+ }
+
+ scriptletsMapPromise = fs.readdir('./scriptlets').then(files => {
+ const readTemplateFile = file =>
+ fs.readFile(`./scriptlets/${file}`, { encoding: 'utf8' })
+ .then(text => ({ file, text }));
+ const readPromises = [];
+ for ( const file of files ) {
+ readPromises.push(readTemplateFile(file));
+ }
+ return Promise.all(readPromises).then(results => {
+ const originalScriptletMap = new Map();
+ for ( const details of results ) {
+ originalScriptletMap.set(
+ details.file.replace('.template.js', '')
+ .replace('.template.css', ''),
+ details.text
+ );
+ }
+ return originalScriptletMap;
+ });
+ });
+
+ return scriptletsMapPromise;
+}
+
+/******************************************************************************/
+
+async function processGenericCosmeticFilters(assetDetails, bucketsMap, exceptionSet) {
+ if ( bucketsMap === undefined ) { return 0; }
+ if ( exceptionSet ) {
+ for ( const [ hash, selectors ] of bucketsMap ) {
+ let i = selectors.length;
+ while ( i-- ) {
+ const selector = selectors[i];
+ if ( exceptionSet.has(selector) === false ) { continue; }
+ selectors.splice(i, 1);
+ //log(`\tRemoving excepted generic filter ##${selector}`);
+ }
+ if ( selectors.length === 0 ) {
+ bucketsMap.delete(hash);
+ }
+ }
+ }
+ if ( bucketsMap.size === 0 ) { return 0; }
+ const bucketsList = Array.from(bucketsMap);
+ const count = bucketsList.reduce((a, v) => a += v[1].length, 0);
+ if ( count === 0 ) { return 0; }
+ const selectorLists = bucketsList.map(v => [ v[0], v[1].join(',') ]);
+ const originalScriptletMap = await loadAllSourceScriptlets();
+
+ let patchedScriptlet = originalScriptletMap.get('css-generic').replace(
+ '$rulesetId$',
+ assetDetails.id
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$genericSelectorMap\$/,
+ `${JSON.stringify(selectorLists, scriptletJsonReplacer)}`
+ );
+
+ writeFile(
+ `${scriptletDir}/generic/${assetDetails.id}.js`,
+ patchedScriptlet
+ );
+
+ log(`CSS-generic: ${count} plain CSS selectors`);
+
+ return count;
+}
+
+/******************************************************************************/
+
+async function processGenericHighCosmeticFilters(assetDetails, selectorSet, exceptionSet) {
+ if ( selectorSet === undefined ) { return 0; }
+ if ( exceptionSet ) {
+ for ( const selector of selectorSet ) {
+ if ( exceptionSet.has(selector) === false ) { continue; }
+ selectorSet.delete(selector);
+ //log(`\tRemoving excepted generic filter ##${selector}`);
+ }
+ }
+ if ( selectorSet.size === 0 ) { return 0; }
+ const selectorLists = Array.from(selectorSet).sort().join(',\n');
+ const originalScriptletMap = await loadAllSourceScriptlets();
+
+ let patchedScriptlet = originalScriptletMap.get('css-generichigh').replace(
+ '$rulesetId$',
+ assetDetails.id
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\$selectorList\$/,
+ selectorLists
+ );
+
+ writeFile(
+ `${scriptletDir}/generichigh/${assetDetails.id}.css`,
+ patchedScriptlet
+ );
+
+ log(`CSS-generic-high: ${selectorSet.size} plain CSS selectors`);
+
+ return selectorSet.size;
+}
+
+/******************************************************************************/
+
+// This merges selectors which are used by the same hostnames
+
+function groupSelectorsByHostnames(mapin) {
+ if ( mapin === undefined ) { return []; }
+ const merged = new Map();
+ for ( const [ selector, details ] of mapin ) {
+ if ( details.rejected ) { continue; }
+ const json = JSON.stringify(details);
+ let entries = merged.get(json);
+ if ( entries === undefined ) {
+ entries = new Set();
+ merged.set(json, entries);
+ }
+ entries.add(selector);
+ }
+ const out = [];
+ for ( const [ json, entries ] of merged ) {
+ const details = JSON.parse(json);
+ details.selectors = Array.from(entries).sort();
+ out.push(details);
+ }
+ return out;
+}
+
+// This merges hostnames which have the same set of selectors.
+//
+// Also, we sort the hostnames to increase likelihood that selector with
+// same hostnames will end up in same generated scriptlet.
+
+function groupHostnamesBySelectors(arrayin) {
+ const contentMap = new Map();
+ for ( const entry of arrayin ) {
+ const id = uidint32(JSON.stringify(entry.selectors));
+ let details = contentMap.get(id);
+ if ( details === undefined ) {
+ details = { a: entry.selectors };
+ contentMap.set(id, details);
+ }
+ if ( entry.matches !== undefined ) {
+ if ( details.y === undefined ) {
+ details.y = new Set();
+ }
+ for ( const hn of entry.matches ) {
+ details.y.add(hn);
+ }
+ }
+ if ( entry.excludeMatches !== undefined ) {
+ if ( details.n === undefined ) {
+ details.n = new Set();
+ }
+ for ( const hn of entry.excludeMatches ) {
+ details.n.add(hn);
+ }
+ }
+ }
+ const out = Array.from(contentMap).map(a => [
+ a[0], {
+ a: a[1].a,
+ y: a[1].y ? Array.from(a[1].y).sort(hnSort) : '*',
+ n: a[1].n ? Array.from(a[1].n) : undefined,
+ }
+ ]).sort((a, b) => {
+ const ha = Array.isArray(a[1].y) ? a[1].y[0] : '*';
+ const hb = Array.isArray(b[1].y) ? b[1].y[0] : '*';
+ return hnSort(ha, hb);
+ });
+ return out;
+}
+
+const scriptletHostnameToIdMap = (hostnames, id, map) => {
+ for ( const hn of hostnames ) {
+ const existing = map.get(hn);
+ if ( existing === undefined ) {
+ map.set(hn, id);
+ } else if ( Array.isArray(existing) ) {
+ existing.push(id);
+ } else {
+ map.set(hn, [ existing, id ]);
+ }
+ }
+};
+
+const scriptletJsonReplacer = (k, v) => {
+ if ( k === 'n' ) {
+ if ( v === undefined || v.size === 0 ) { return; }
+ return Array.from(v);
+ }
+ if ( v instanceof Set || v instanceof Map ) {
+ if ( v.size === 0 ) { return; }
+ return Array.from(v);
+ }
+ return v;
+};
+
+/******************************************************************************/
+
+function argsMap2List(argsMap, hostnamesMap) {
+ const argsList = [];
+ const indexMap = new Map();
+ for ( const [ id, details ] of argsMap ) {
+ indexMap.set(id, argsList.length);
+ argsList.push(details);
+ }
+ for ( const [ hn, ids ] of hostnamesMap ) {
+ if ( typeof ids === 'number' ) {
+ hostnamesMap.set(hn, indexMap.get(ids));
+ continue;
+ }
+ for ( let i = 0; i < ids.length; i++ ) {
+ ids[i] = indexMap.get(ids[i]);
+ }
+ }
+ return argsList;
+}
+
+/******************************************************************************/
+
+async function processCosmeticFilters(assetDetails, mapin) {
+ if ( mapin === undefined ) { return 0; }
+ if ( mapin.size === 0 ) { return 0; }
+
+ const domainBasedEntries = groupHostnamesBySelectors(
+ groupSelectorsByHostnames(mapin)
+ );
+ // We do not want more than n CSS files per subscription, so we will
+ // group multiple unrelated selectors in the same file, and distinct
+ // css declarations will be injected programmatically according to the
+ // hostname of the current document.
+ //
+ // The cosmetic filters will be injected programmatically as content
+ // script and the decisions to activate the cosmetic filters will be
+ // done at injection time according to the document's hostname.
+ const generatedFiles = [];
+
+ const argsMap = domainBasedEntries.map(entry => [
+ entry[0],
+ {
+ a: entry[1].a ? entry[1].a.join(',\n') : undefined,
+ n: entry[1].n
+ }
+ ]);
+ const hostnamesMap = new Map();
+ for ( const [ id, details ] of domainBasedEntries ) {
+ if ( details.y === undefined ) { continue; }
+ scriptletHostnameToIdMap(details.y, id, hostnamesMap);
+ }
+ const argsList = argsMap2List(argsMap, hostnamesMap);
+ const entitiesMap = new Map();
+ for ( const [ hn, details ] of hostnamesMap ) {
+ if ( hn.endsWith('.*') === false ) { continue; }
+ hostnamesMap.delete(hn);
+ entitiesMap.set(hn.slice(0, -2), details);
+ }
+
+ // Extract exceptions from argsList, simplify argsList entries
+ const exceptionsMap = new Map();
+ for ( let i = 0; i < argsList.length; i++ ) {
+ const details = argsList[i];
+ if ( details.n ) {
+ for ( const hn of details.n ) {
+ if ( exceptionsMap.has(hn) === false ) {
+ exceptionsMap.set(hn, []);
+ }
+ exceptionsMap.get(hn).push(i);
+ }
+ }
+ argsList[i] = details.a;
+ }
+
+ const originalScriptletMap = await loadAllSourceScriptlets();
+ let patchedScriptlet = originalScriptletMap.get('css-specific').replace(
+ '$rulesetId$',
+ assetDetails.id
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$argsList\$/,
+ `${JSON.stringify(argsList, scriptletJsonReplacer)}`
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$hostnamesMap\$/,
+ `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}`
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$entitiesMap\$/,
+ `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}`
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$exceptionsMap\$/,
+ `${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}`
+ );
+ writeFile(`${scriptletDir}/specific/${assetDetails.id}.js`, patchedScriptlet);
+ generatedFiles.push(`${assetDetails.id}`);
+
+ if ( generatedFiles.length !== 0 ) {
+ log(`CSS-specific: ${mapin.size} distinct filters`);
+ log(`\tCombined into ${hostnamesMap.size} distinct hostnames`);
+ log(`\tCombined into ${entitiesMap.size} distinct entities`);
+ }
+
+ return hostnamesMap.size + entitiesMap.size;
+}
+
+/******************************************************************************/
+
+async function processDeclarativeCosmeticFilters(assetDetails, mapin) {
+ if ( mapin === undefined ) { return 0; }
+ if ( mapin.size === 0 ) { return 0; }
+
+ // Distinguish declarative-compiled-as-procedural from actual procedural.
+ const declaratives = new Map();
+ mapin.forEach((details, jsonSelector) => {
+ const selector = JSON.parse(jsonSelector);
+ if ( selector.cssable !== true ) { return; }
+ selector.cssable = undefined;
+ declaratives.set(JSON.stringify(selector), details);
+ });
+ if ( declaratives.size === 0 ) { return 0; }
+
+ const contentArray = groupHostnamesBySelectors(
+ groupSelectorsByHostnames(declaratives)
+ );
+
+ const argsMap = contentArray.map(entry => [
+ entry[0],
+ {
+ a: entry[1].a,
+ n: entry[1].n,
+ }
+ ]);
+ const hostnamesMap = new Map();
+ for ( const [ id, details ] of contentArray ) {
+ if ( details.y === undefined ) { continue; }
+ scriptletHostnameToIdMap(details.y, id, hostnamesMap);
+ }
+ const argsList = argsMap2List(argsMap, hostnamesMap);
+ const entitiesMap = new Map();
+ for ( const [ hn, details ] of hostnamesMap ) {
+ if ( hn.endsWith('.*') === false ) { continue; }
+ hostnamesMap.delete(hn);
+ entitiesMap.set(hn.slice(0, -2), details);
+ }
+
+ // Extract exceptions from argsList, simplify argsList entries
+ const exceptionsMap = new Map();
+ for ( let i = 0; i < argsList.length; i++ ) {
+ const details = argsList[i];
+ if ( details.n ) {
+ for ( const hn of details.n ) {
+ if ( exceptionsMap.has(hn) === false ) {
+ exceptionsMap.set(hn, []);
+ }
+ exceptionsMap.get(hn).push(i);
+ }
+ }
+ argsList[i] = details.a;
+ }
+
+ const originalScriptletMap = await loadAllSourceScriptlets();
+ let patchedScriptlet = originalScriptletMap.get('css-declarative').replace(
+ '$rulesetId$',
+ assetDetails.id
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$argsList\$/,
+ `${JSON.stringify(argsList, scriptletJsonReplacer)}`
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$hostnamesMap\$/,
+ `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}`
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$entitiesMap\$/,
+ `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}`
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$exceptionsMap\$/,
+ `${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}`
+ );
+ writeFile(`${scriptletDir}/declarative/${assetDetails.id}.js`, patchedScriptlet);
+
+ if ( contentArray.length !== 0 ) {
+ log(`CSS-declarative: ${declaratives.size} distinct filters`);
+ log(`\tCombined into ${hostnamesMap.size} distinct hostnames`);
+ log(`\tCombined into ${entitiesMap.size} distinct entities`);
+ }
+
+ return hostnamesMap.size + entitiesMap.size;
+}
+
+/******************************************************************************/
+
+async function processProceduralCosmeticFilters(assetDetails, mapin) {
+ if ( mapin === undefined ) { return 0; }
+ if ( mapin.size === 0 ) { return 0; }
+
+ // Distinguish declarative-compiled-as-procedural from actual procedural.
+ const procedurals = new Map();
+ mapin.forEach((details, jsonSelector) => {
+ const selector = JSON.parse(jsonSelector);
+ if ( selector.cssable ) { return; }
+ procedurals.set(jsonSelector, details);
+ });
+ if ( procedurals.size === 0 ) { return 0; }
+
+ const contentArray = groupHostnamesBySelectors(
+ groupSelectorsByHostnames(procedurals)
+ );
+
+ const argsMap = contentArray.map(entry => [
+ entry[0],
+ {
+ a: entry[1].a,
+ n: entry[1].n,
+ }
+ ]);
+ const hostnamesMap = new Map();
+ for ( const [ id, details ] of contentArray ) {
+ if ( details.y === undefined ) { continue; }
+ scriptletHostnameToIdMap(details.y, id, hostnamesMap);
+ }
+ const argsList = argsMap2List(argsMap, hostnamesMap);
+ const entitiesMap = new Map();
+ for ( const [ hn, details ] of hostnamesMap ) {
+ if ( hn.endsWith('.*') === false ) { continue; }
+ hostnamesMap.delete(hn);
+ entitiesMap.set(hn.slice(0, -2), details);
+ }
+
+ // Extract exceptions from argsList, simplify argsList entries
+ const exceptionsMap = new Map();
+ for ( let i = 0; i < argsList.length; i++ ) {
+ const details = argsList[i];
+ if ( details.n ) {
+ for ( const hn of details.n ) {
+ if ( exceptionsMap.has(hn) === false ) {
+ exceptionsMap.set(hn, []);
+ }
+ exceptionsMap.get(hn).push(i);
+ }
+ }
+ argsList[i] = details.a;
+ }
+
+ const originalScriptletMap = await loadAllSourceScriptlets();
+ let patchedScriptlet = originalScriptletMap.get('css-procedural').replace(
+ '$rulesetId$',
+ assetDetails.id
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$argsList\$/,
+ `${JSON.stringify(argsList, scriptletJsonReplacer)}`
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$hostnamesMap\$/,
+ `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}`
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$entitiesMap\$/,
+ `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}`
+ );
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /\bself\.\$exceptionsMap\$/,
+ `${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}`
+ );
+ writeFile(`${scriptletDir}/procedural/${assetDetails.id}.js`, patchedScriptlet);
+
+ if ( contentArray.length !== 0 ) {
+ log(`Procedural-related distinct filters: ${procedurals.size} distinct combined selectors`);
+ log(`\tCombined into ${hostnamesMap.size} distinct hostnames`);
+ log(`\tCombined into ${entitiesMap.size} distinct entities`);
+ }
+
+ return hostnamesMap.size + entitiesMap.size;
+}
+
+/******************************************************************************/
+
+async function processScriptletFilters(assetDetails, mapin) {
+ if ( mapin === undefined ) { return 0; }
+ if ( mapin.size === 0 ) { return 0; }
+
+ makeScriptlet.init();
+
+ for ( const details of mapin.values() ) {
+ makeScriptlet.compile(details);
+ }
+ const stats = await makeScriptlet.commit(
+ assetDetails.id,
+ `${scriptletDir}/scriptlet`,
+ writeFile
+ );
+ if ( stats.length !== 0 ) {
+ scriptletStats.set(assetDetails.id, stats);
+ }
+ makeScriptlet.reset();
+ return stats.length;
+}
+
+/******************************************************************************/
+
+async function rulesetFromURLs(assetDetails) {
+ log('============================');
+ log(`Listset for '${assetDetails.id}':`);
+
+ if ( assetDetails.text === undefined ) {
+ const text = await fetchAsset(assetDetails);
+ if ( text === '' ) { return; }
+ assetDetails.text = text;
+ }
+
+ const extensionPaths = [];
+ for ( const [ fname, details ] of redirectResourcesMap ) {
+ const path = `/web_accessible_resources/${fname}`;
+ extensionPaths.push([ fname, path ]);
+ if ( details.alias === undefined ) { continue; }
+ if ( typeof details.alias === 'string' ) {
+ extensionPaths.push([ details.alias, path ]);
+ continue;
+ }
+ if ( Array.isArray(details.alias) === false ) { continue; }
+ for ( const alias of details.alias ) {
+ extensionPaths.push([ alias, path ]);
+ }
+ }
+
+ const results = await dnrRulesetFromRawLists(
+ [ { name: assetDetails.id, text: assetDetails.text } ],
+ { env, extensionPaths, secret: assetDetails.secret }
+ );
+
+ const netStats = await processNetworkFilters(
+ assetDetails,
+ results.network
+ );
+
+ // Split cosmetic filters into two groups: declarative and procedural
+ const declarativeCosmetic = new Map();
+ const proceduralCosmetic = new Map();
+ const rejectedCosmetic = [];
+ if ( results.specificCosmetic ) {
+ for ( const [ selector, details ] of results.specificCosmetic ) {
+ if ( details.rejected ) {
+ rejectedCosmetic.push(selector);
+ continue;
+ }
+ if ( selector.startsWith('{') === false ) {
+ declarativeCosmetic.set(selector, details);
+ continue;
+ }
+ const parsed = JSON.parse(selector);
+ parsed.raw = undefined;
+ proceduralCosmetic.set(JSON.stringify(parsed), details);
+ }
+ }
+ if ( rejectedCosmetic.length !== 0 ) {
+ log(`Rejected cosmetic filters: ${rejectedCosmetic.length}`);
+ log(rejectedCosmetic.map(line => `\t${line}`).join('\n'), true);
+ }
+
+ if (
+ Array.isArray(results.network.generichideExclusions) &&
+ results.network.generichideExclusions.length !== 0
+ ) {
+ genericDetails.set(
+ assetDetails.id,
+ results.network.generichideExclusions.filter(hn => hn.endsWith('.*') === false).sort()
+ );
+ }
+
+ const genericCosmeticStats = await processGenericCosmeticFilters(
+ assetDetails,
+ results.genericCosmetic,
+ results.genericCosmeticExceptions
+ );
+ const genericHighCosmeticStats = await processGenericHighCosmeticFilters(
+ assetDetails,
+ results.genericHighCosmetic,
+ results.genericCosmeticExceptions
+ );
+ const specificCosmeticStats = await processCosmeticFilters(
+ assetDetails,
+ declarativeCosmetic
+ );
+ const declarativeStats = await processDeclarativeCosmeticFilters(
+ assetDetails,
+ proceduralCosmetic
+ );
+ const proceduralStats = await processProceduralCosmeticFilters(
+ assetDetails,
+ proceduralCosmetic
+ );
+ const scriptletStats = await processScriptletFilters(
+ assetDetails,
+ results.scriptlet
+ );
+
+ rulesetDetails.push({
+ id: assetDetails.id,
+ name: assetDetails.name,
+ group: assetDetails.group,
+ enabled: assetDetails.enabled,
+ lang: assetDetails.lang,
+ homeURL: assetDetails.homeURL,
+ filters: {
+ total: results.network.filterCount,
+ accepted: results.network.acceptedFilterCount,
+ rejected: results.network.rejectedFilterCount,
+ },
+ rules: {
+ total: netStats.total,
+ plain: netStats.plain,
+ regex: netStats.regex,
+ removeparam: netStats.removeparam,
+ redirect: netStats.redirect,
+ modifyHeaders: netStats.modifyHeaders,
+ discarded: netStats.discarded,
+ rejected: netStats.rejected,
+ },
+ css: {
+ generic: genericCosmeticStats,
+ generichigh: genericHighCosmeticStats,
+ specific: specificCosmeticStats,
+ declarative: declarativeStats,
+ procedural: proceduralStats,
+ },
+ scriptlets: scriptletStats,
+ });
+
+ ruleResources.push({
+ id: assetDetails.id,
+ enabled: assetDetails.enabled,
+ path: `/rulesets/main/${assetDetails.id}.json`
+ });
+}
+
+/******************************************************************************/
+
+async function main() {
+
+ let version = '';
+ {
+ const now = new Date();
+ const yearPart = now.getUTCFullYear();
+ const monthPart = now.getUTCMonth() + 1;
+ const dayPart = now.getUTCDate();
+ const hourPart = Math.floor(now.getUTCHours());
+ const minutePart = Math.floor(now.getUTCMinutes());
+ version = `${yearPart}.${monthPart}.${dayPart}.${hourPart * 60 + minutePart}`;
+ }
+ log(`Version: ${version}`);
+
+ // Get assets.json content
+ const assets = await fs.readFile(
+ `./assets.json`,
+ { encoding: 'utf8' }
+ ).then(text =>
+ JSON.parse(text)
+ );
+
+ // This will be used to sign our inserted `!#trusted on` directives
+ const secret = createHash('sha256').update(randomBytes(16)).digest('hex').slice(0,16);
+ log(`Secret: ${secret}`);
+
+ // Assemble all default lists as the default ruleset
+ const contentURLs = [
+ 'https://ublockorigin.github.io/uAssets/filters/filters.min.txt',
+ 'https://ublockorigin.github.io/uAssets/filters/badware.txt',
+ 'https://ublockorigin.github.io/uAssets/filters/privacy.min.txt',
+ 'https://ublockorigin.github.io/uAssets/filters/unbreak.min.txt',
+ 'https://ublockorigin.github.io/uAssets/filters/quick-fixes.txt',
+ 'https://ublockorigin.github.io/uAssets/filters/ubol-filters.txt',
+ 'https://ublockorigin.github.io/uAssets/thirdparties/easylist.txt',
+ 'https://ublockorigin.github.io/uAssets/thirdparties/easyprivacy.txt',
+ 'https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=1&mimetype=plaintext',
+ ];
+ await rulesetFromURLs({
+ id: 'default',
+ name: 'Ads, trackers, miners, and more' ,
+ enabled: true,
+ secret,
+ urls: contentURLs,
+ homeURL: 'https://github.com/uBlockOrigin/uAssets',
+ });
+
+ // Regional rulesets
+ const excludedLists = [
+ 'ara-0',
+ 'EST-0',
+ ];
+ // Merge lists which have same target languages
+ const langToListsMap = new Map();
+ for ( const [ id, asset ] of Object.entries(assets) ) {
+ if ( asset.content !== 'filters' ) { continue; }
+ if ( asset.off !== true ) { continue; }
+ if ( typeof asset.lang !== 'string' ) { continue; }
+ if ( excludedLists.includes(id) ) { continue; }
+ let ids = langToListsMap.get(asset.lang);
+ if ( ids === undefined ) {
+ langToListsMap.set(asset.lang, ids = []);
+ }
+ ids.push(id);
+ }
+ for ( const ids of langToListsMap.values() ) {
+ const urls = [];
+ for ( const id of ids ) {
+ const asset = assets[id];
+ const contentURL = Array.isArray(asset.contentURL)
+ ? asset.contentURL[0]
+ : asset.contentURL;
+ urls.push(contentURL);
+ }
+ const id = ids[0];
+ const asset = assets[id];
+ await rulesetFromURLs({
+ id: id.toLowerCase(),
+ lang: asset.lang,
+ name: asset.title,
+ enabled: false,
+ urls,
+ homeURL: asset.supportURL,
+ });
+ }
+
+ // Handpicked rulesets from assets.json
+ const handpicked = [
+ 'block-lan',
+ 'dpollock-0',
+ 'adguard-spyware-url',
+ ];
+ for ( const id of handpicked ) {
+ const asset = assets[id];
+ if ( asset.content !== 'filters' ) { continue; }
+
+ const contentURL = Array.isArray(asset.contentURL)
+ ? asset.contentURL[0]
+ : asset.contentURL;
+ await rulesetFromURLs({
+ id: id.toLowerCase(),
+ name: asset.title,
+ enabled: false,
+ urls: [ contentURL ],
+ homeURL: asset.supportURL,
+ });
+ }
+
+ // Handpicked annoyance rulesets from assets.json
+ await rulesetFromURLs({
+ id: 'annoyances-cookies',
+ name: 'EasyList/uBO – Cookie Notices',
+ group: 'annoyances',
+ enabled: false,
+ secret,
+ urls: [
+ 'https://ublockorigin.github.io/uAssets/thirdparties/easylist-cookies.txt',
+ 'https://ublockorigin.github.io/uAssets/filters/annoyances-cookies.txt',
+ ],
+ homeURL: 'https://github.com/easylist/easylist#fanboy-lists',
+ });
+ await rulesetFromURLs({
+ id: 'annoyances-overlays',
+ name: 'AdGuard/uBO – Overlays',
+ group: 'annoyances',
+ enabled: false,
+ secret,
+ urls: [
+ 'https://filters.adtidy.org/extension/ublock/filters/19.txt',
+ 'https://ublockorigin.github.io/uAssets/filters/annoyances-others.txt',
+ ],
+ homeURL: 'https://github.com/AdguardTeam/AdguardFilters#adguard-filters',
+ });
+ await rulesetFromURLs({
+ id: 'annoyances-social',
+ name: 'AdGuard – Social Media',
+ group: 'annoyances',
+ enabled: false,
+ urls: [
+ 'https://filters.adtidy.org/extension/ublock/filters/4.txt',
+ ],
+ homeURL: 'https://github.com/AdguardTeam/AdguardFilters#adguard-filters',
+ });
+ await rulesetFromURLs({
+ id: 'annoyances-widgets',
+ name: 'AdGuard – Widgets',
+ group: 'annoyances',
+ enabled: false,
+ urls: [
+ 'https://filters.adtidy.org/extension/ublock/filters/22.txt',
+ ],
+ homeURL: 'https://github.com/AdguardTeam/AdguardFilters#adguard-filters',
+ });
+ await rulesetFromURLs({
+ id: 'annoyances-others',
+ name: 'AdGuard – Other Annoyances',
+ group: 'annoyances',
+ enabled: false,
+ urls: [
+ 'https://filters.adtidy.org/extension/ublock/filters/21.txt',
+ ],
+ homeURL: 'https://github.com/AdguardTeam/AdguardFilters#adguard-filters',
+ });
+
+ // Handpicked rulesets from abroad
+ await rulesetFromURLs({
+ id: 'stevenblack-hosts',
+ name: 'Steven Black\'s hosts file',
+ enabled: false,
+ urls: [ 'https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts' ],
+ homeURL: 'https://github.com/StevenBlack/hosts#readme',
+ });
+
+ writeFile(
+ `${rulesetDir}/ruleset-details.json`,
+ `${JSON.stringify(rulesetDetails, null, 1)}\n`
+ );
+
+ writeFile(
+ `${rulesetDir}/scriptlet-details.json`,
+ `${JSON.stringify(scriptletStats, jsonSetMapReplacer, 1)}\n`
+ );
+
+ writeFile(
+ `${rulesetDir}/generic-details.json`,
+ `${JSON.stringify(genericDetails, jsonSetMapReplacer, 1)}\n`
+ );
+
+ // Copy required redirect resources
+ for ( const path of requiredRedirectResources ) {
+ copyFile(`./${path}`, `${outputDir}/${path}`);
+ }
+
+ await Promise.all(writeOps);
+
+ // Patch manifest
+ // Get manifest content
+ const manifest = await fs.readFile(
+ `${outputDir}/manifest.json`,
+ { encoding: 'utf8' }
+ ).then(text =>
+ JSON.parse(text)
+ );
+ // Patch declarative_net_request key
+ manifest.declarative_net_request = { rule_resources: ruleResources };
+ // Patch web_accessible_resources key
+ const web_accessible_resources = {
+ resources: Array.from(requiredRedirectResources).map(path => `/${path}`),
+ matches: [ '<all_urls>' ],
+ };
+ if ( platform === 'chromium' ) {
+ web_accessible_resources.use_dynamic_url = true;
+ }
+ manifest.web_accessible_resources = [ web_accessible_resources ];
+
+ // Patch manifest version property
+ manifest.version = version;
+ // Commit changes
+ await fs.writeFile(
+ `${outputDir}/manifest.json`,
+ JSON.stringify(manifest, null, 2) + '\n'
+ );
+
+ // Log results
+ const logContent = stdOutput.join('\n') + '\n';
+ await fs.writeFile(`${cacheDir}/log.txt`, logContent);
+}
+
+main();
+
+/******************************************************************************/
diff --git a/platform/mv3/make-scriptlets.js b/platform/mv3/make-scriptlets.js
new file mode 100644
index 0000000..d276a56
--- /dev/null
+++ b/platform/mv3/make-scriptlets.js
@@ -0,0 +1,193 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2017-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+/******************************************************************************/
+
+import fs from 'fs/promises';
+import { builtinScriptlets } from './scriptlets.js';
+import { safeReplace } from './safe-replace.js';
+
+/******************************************************************************/
+
+const resourceDetails = new Map();
+const resourceAliases = new Map();
+const scriptletFiles = new Map();
+
+/******************************************************************************/
+
+function createScriptletCoreCode(scriptletToken) {
+ const details = resourceDetails.get(scriptletToken);
+ const components = new Map([ [ scriptletToken, details.code ] ]);
+ const dependencies = details.dependencies && details.dependencies.slice() || [];
+ while ( dependencies.length !== 0 ) {
+ const token = dependencies.shift();
+ if ( components.has(token) ) { continue; }
+ const details = resourceDetails.get(token);
+ if ( details === undefined ) { continue; }
+ components.set(token, details.code);
+ if ( Array.isArray(details.dependencies) === false ) { continue; }
+ dependencies.push(...details.dependencies);
+ }
+ return Array.from(components.values()).join('\n\n');
+}
+
+/******************************************************************************/
+
+export function init() {
+ for ( const scriptlet of builtinScriptlets ) {
+ const { name, aliases, fn } = scriptlet;
+ const entry = {
+ name: fn.name,
+ code: fn.toString(),
+ world: scriptlet.world || 'MAIN',
+ dependencies: scriptlet.dependencies,
+ requiresTrust: scriptlet.requiresTrust === true,
+ };
+ resourceDetails.set(name, entry);
+ if ( Array.isArray(aliases) === false ) { continue; }
+ for ( const alias of aliases ) {
+ resourceAliases.set(alias, name);
+ }
+ }
+}
+
+/******************************************************************************/
+
+export function reset() {
+ scriptletFiles.clear();
+}
+
+/******************************************************************************/
+
+export function compile(details) {
+ if ( details.args[0].endsWith('.js') === false ) {
+ details.args[0] += '.js';
+ }
+ if ( resourceAliases.has(details.args[0]) ) {
+ details.args[0] = resourceAliases.get(details.args[0]);
+ }
+ const scriptletToken = details.args[0];
+ const resourceEntry = resourceDetails.get(scriptletToken);
+ if ( resourceEntry === undefined ) { return; }
+ if ( resourceEntry.requiresTrust && details.trustedSource !== true ) {
+ console.log(`Rejecting ${scriptletToken}: source is not trusted`);
+ return;
+ }
+ if ( scriptletFiles.has(scriptletToken) === false ) {
+ scriptletFiles.set(scriptletToken, {
+ name: resourceEntry.name,
+ code: createScriptletCoreCode(scriptletToken),
+ world: resourceEntry.world,
+ args: new Map(),
+ hostnames: new Map(),
+ entities: new Map(),
+ exceptions: new Map(),
+ matches: new Set(),
+ });
+ }
+ const scriptletDetails = scriptletFiles.get(scriptletToken);
+ const argsToken = JSON.stringify(details.args.slice(1));
+ if ( scriptletDetails.args.has(argsToken) === false ) {
+ scriptletDetails.args.set(argsToken, scriptletDetails.args.size);
+ }
+ const iArgs = scriptletDetails.args.get(argsToken);
+ if ( details.matches ) {
+ for ( const hn of details.matches ) {
+ if ( hn.endsWith('.*') ) {
+ scriptletDetails.matches.clear();
+ scriptletDetails.matches.add('*');
+ const entity = hn.slice(0, -2);
+ if ( scriptletDetails.entities.has(entity) === false ) {
+ scriptletDetails.entities.set(entity, new Set());
+ }
+ scriptletDetails.entities.get(entity).add(iArgs);
+ } else {
+ if ( scriptletDetails.matches.has('*') === false ) {
+ scriptletDetails.matches.add(hn);
+ }
+ if ( scriptletDetails.hostnames.has(hn) === false ) {
+ scriptletDetails.hostnames.set(hn, new Set());
+ }
+ scriptletDetails.hostnames.get(hn).add(iArgs);
+ }
+ }
+ } else {
+ scriptletDetails.matches.add('*');
+ }
+ if ( details.excludeMatches ) {
+ for ( const hn of details.excludeMatches ) {
+ if ( scriptletDetails.exceptions.has(hn) === false ) {
+ scriptletDetails.exceptions.set(hn, []);
+ }
+ scriptletDetails.exceptions.get(hn).push(iArgs);
+ }
+ }
+}
+
+/******************************************************************************/
+
+export async function commit(rulesetId, path, writeFn) {
+ const scriptletTemplate = await fs.readFile(
+ './scriptlets/scriptlet.template.js',
+ { encoding: 'utf8' }
+ );
+ const patchHnMap = hnmap => {
+ const out = Array.from(hnmap);
+ out.forEach(a => {
+ const values = Array.from(a[1]);
+ a[1] = values.length === 1 ? values[0] : values;
+ });
+ return out;
+ };
+ const scriptletStats = [];
+ for ( const [ name, details ] of scriptletFiles ) {
+ let content = safeReplace(scriptletTemplate,
+ 'function $scriptletName$(){}',
+ details.code
+ );
+ content = safeReplace(content, /\$rulesetId\$/, rulesetId, 0);
+ content = safeReplace(content, /\$scriptletName\$/, details.name, 0);
+ content = safeReplace(content, '$world$', details.world);
+ content = safeReplace(content,
+ 'self.$argsList$',
+ JSON.stringify(Array.from(details.args.keys()).map(a => JSON.parse(a)))
+ );
+ content = safeReplace(content,
+ 'self.$hostnamesMap$',
+ JSON.stringify(patchHnMap(details.hostnames))
+ );
+ content = safeReplace(content,
+ 'self.$entitiesMap$',
+ JSON.stringify(patchHnMap(details.entities))
+ );
+ content = safeReplace(content,
+ 'self.$exceptionsMap$',
+ JSON.stringify(Array.from(details.exceptions))
+ );
+ writeFn(`${path}/${rulesetId}.${name}`, content);
+ scriptletStats.push([ name.slice(0, -3), Array.from(details.matches).sort() ]);
+ }
+ return scriptletStats;
+}
+
+/******************************************************************************/
diff --git a/platform/mv3/package.json b/platform/mv3/package.json
new file mode 100644
index 0000000..c10527a
--- /dev/null
+++ b/platform/mv3/package.json
@@ -0,0 +1,6 @@
+{
+ "engines": {
+ "node": ">=17.5.0"
+ },
+ "type": "module"
+}
diff --git a/platform/mv3/safe-replace.js b/platform/mv3/safe-replace.js
new file mode 100644
index 0000000..b56e5e5
--- /dev/null
+++ b/platform/mv3/safe-replace.js
@@ -0,0 +1,41 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2017-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+/******************************************************************************/
+
+export function safeReplace(text, pattern, replacement, count = 1) {
+ const rePattern = typeof pattern === 'string'
+ ? new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
+ : pattern;
+ let out = text;
+ for (;;) {
+ const match = rePattern.exec(out);
+ if ( match === null ) { break; }
+ out = out.slice(0, match.index) +
+ replacement +
+ out.slice(match.index + match[0].length);
+ count -= 1;
+ if ( count === 0 ) { break; }
+ }
+ return out;
+}
diff --git a/platform/mv3/scriptlets/css-declarative.template.js b/platform/mv3/scriptlets/css-declarative.template.js
new file mode 100644
index 0000000..c1f480f
--- /dev/null
+++ b/platform/mv3/scriptlets/css-declarative.template.js
@@ -0,0 +1,51 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+// ruleset: $rulesetId$
+
+/******************************************************************************/
+
+// Important!
+// Isolate from global scope
+(function uBOL_cssDeclarativeImport() {
+
+/******************************************************************************/
+
+const argsList = self.$argsList$;
+
+const hostnamesMap = new Map(self.$hostnamesMap$);
+
+const entitiesMap = new Map(self.$entitiesMap$);
+
+const exceptionsMap = new Map(self.$exceptionsMap$);
+
+self.declarativeImports = self.declarativeImports || [];
+self.declarativeImports.push({ argsList, hostnamesMap, entitiesMap, exceptionsMap });
+
+/******************************************************************************/
+
+})();
+
+/******************************************************************************/
diff --git a/platform/mv3/scriptlets/css-generic.template.js b/platform/mv3/scriptlets/css-generic.template.js
new file mode 100644
index 0000000..a1f1d6c
--- /dev/null
+++ b/platform/mv3/scriptlets/css-generic.template.js
@@ -0,0 +1,61 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+/******************************************************************************/
+
+// Important!
+// Isolate from global scope
+(function uBOL_cssGenericImport() {
+
+/******************************************************************************/
+
+// $rulesetId$
+
+const toImport = self.$genericSelectorMap$;
+
+const genericSelectorMap = self.genericSelectorMap || new Map();
+
+if ( genericSelectorMap.size === 0 ) {
+ self.genericSelectorMap = new Map(toImport);
+ return;
+}
+
+for ( const toImportEntry of toImport ) {
+ const existing = genericSelectorMap.get(toImportEntry[0]);
+ genericSelectorMap.set(
+ toImportEntry[0],
+ existing === undefined
+ ? toImportEntry[1]
+ : `${existing},${toImportEntry[1]}`
+ );
+}
+
+self.genericSelectorMap = genericSelectorMap;
+
+/******************************************************************************/
+
+})();
+
+/******************************************************************************/
diff --git a/platform/mv3/scriptlets/css-generichigh.template.css b/platform/mv3/scriptlets/css-generichigh.template.css
new file mode 100644
index 0000000..370bc4c
--- /dev/null
+++ b/platform/mv3/scriptlets/css-generichigh.template.css
@@ -0,0 +1,26 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* $rulesetId$ */
+
+$selectorList$ {
+ display: none !important;
+}
diff --git a/platform/mv3/scriptlets/css-procedural.template.js b/platform/mv3/scriptlets/css-procedural.template.js
new file mode 100644
index 0000000..61c95e6
--- /dev/null
+++ b/platform/mv3/scriptlets/css-procedural.template.js
@@ -0,0 +1,51 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+// ruleset: $rulesetId$
+
+/******************************************************************************/
+
+// Important!
+// Isolate from global scope
+(function uBOL_cssProceduralImport() {
+
+/******************************************************************************/
+
+const argsList = self.$argsList$;
+
+const hostnamesMap = new Map(self.$hostnamesMap$);
+
+const entitiesMap = new Map(self.$entitiesMap$);
+
+const exceptionsMap = new Map(self.$exceptionsMap$);
+
+self.proceduralImports = self.proceduralImports || [];
+self.proceduralImports.push({ argsList, hostnamesMap, entitiesMap, exceptionsMap });
+
+/******************************************************************************/
+
+})();
+
+/******************************************************************************/
diff --git a/platform/mv3/scriptlets/css-specific.template.js b/platform/mv3/scriptlets/css-specific.template.js
new file mode 100644
index 0000000..6858931
--- /dev/null
+++ b/platform/mv3/scriptlets/css-specific.template.js
@@ -0,0 +1,51 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2019-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* jshint esversion:11 */
+
+'use strict';
+
+// ruleset: $rulesetId$
+
+/******************************************************************************/
+
+// Important!
+// Isolate from global scope
+(function uBOL_cssSpecificImports() {
+
+/******************************************************************************/
+
+const argsList = self.$argsList$;
+
+const hostnamesMap = new Map(self.$hostnamesMap$);
+
+const entitiesMap = new Map(self.$entitiesMap$);
+
+const exceptionsMap = new Map(self.$exceptionsMap$);
+
+self.specificImports = self.specificImports || [];
+self.specificImports.push({ argsList, hostnamesMap, entitiesMap, exceptionsMap });
+
+/******************************************************************************/
+
+})();
+
+/******************************************************************************/
diff --git a/platform/mv3/scriptlets/scriptlet.template.js b/platform/mv3/scriptlets/scriptlet.template.js
new file mode 100644
index 0000000..b2c4ada
--- /dev/null
+++ b/platform/mv3/scriptlets/scriptlet.template.js
@@ -0,0 +1,175 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+
+*/
+
+/* jshint esversion:11 */
+/* global cloneInto */
+
+'use strict';
+
+// ruleset: $rulesetId$
+
+/******************************************************************************/
+
+// Important!
+// Isolate from global scope
+
+// Start of local scope
+(( ) => {
+
+/******************************************************************************/
+
+// Start of code to inject
+const uBOL_$scriptletName$ = function() {
+
+const scriptletGlobals = new Map(); // jshint ignore: line
+
+const argsList = self.$argsList$;
+
+const hostnamesMap = new Map(self.$hostnamesMap$);
+
+const entitiesMap = new Map(self.$entitiesMap$);
+
+const exceptionsMap = new Map(self.$exceptionsMap$);
+
+/******************************************************************************/
+
+function $scriptletName$(){}
+
+/******************************************************************************/
+
+const hnParts = [];
+try { hnParts.push(...document.location.hostname.split('.')); }
+catch(ex) { }
+const hnpartslen = hnParts.length;
+if ( hnpartslen === 0 ) { return; }
+
+const todoIndices = new Set();
+const tonotdoIndices = [];
+
+// Exceptions
+if ( exceptionsMap.size !== 0 ) {
+ for ( let i = 0; i < hnpartslen; i++ ) {
+ const hn = hnParts.slice(i).join('.');
+ const excepted = exceptionsMap.get(hn);
+ if ( excepted ) { tonotdoIndices.push(...excepted); }
+ }
+ exceptionsMap.clear();
+}
+
+// Hostname-based
+if ( hostnamesMap.size !== 0 ) {
+ const collectArgIndices = hn => {
+ let argsIndices = hostnamesMap.get(hn);
+ if ( argsIndices === undefined ) { return; }
+ if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
+ for ( const argsIndex of argsIndices ) {
+ if ( tonotdoIndices.includes(argsIndex) ) { continue; }
+ todoIndices.add(argsIndex);
+ }
+ };
+ for ( let i = 0; i < hnpartslen; i++ ) {
+ const hn = hnParts.slice(i).join('.');
+ collectArgIndices(hn);
+ }
+ collectArgIndices('*');
+ hostnamesMap.clear();
+}
+
+// Entity-based
+if ( entitiesMap.size !== 0 ) {
+ const n = hnpartslen - 1;
+ for ( let i = 0; i < n; i++ ) {
+ for ( let j = n; j > i; j-- ) {
+ const en = hnParts.slice(i,j).join('.');
+ let argsIndices = entitiesMap.get(en);
+ if ( argsIndices === undefined ) { continue; }
+ if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
+ for ( const argsIndex of argsIndices ) {
+ if ( tonotdoIndices.includes(argsIndex) ) { continue; }
+ todoIndices.add(argsIndex);
+ }
+ }
+ }
+ entitiesMap.clear();
+}
+
+// Apply scriplets
+for ( const i of todoIndices ) {
+ try { $scriptletName$(...argsList[i]); }
+ catch(ex) {}
+}
+argsList.length = 0;
+
+/******************************************************************************/
+
+};
+// End of code to inject
+
+/******************************************************************************/
+
+// Inject code
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1736575
+// 'MAIN' world not yet supported in Firefox, so we inject the code into
+// 'MAIN' ourself when environment in Firefox.
+
+const targetWorld = '$world$';
+
+// Not Firefox
+if ( typeof wrappedJSObject !== 'object' || targetWorld === 'ISOLATED' ) {
+ return uBOL_$scriptletName$();
+}
+
+// Firefox
+{
+ const page = self.wrappedJSObject;
+ let script, url;
+ try {
+ page.uBOL_$scriptletName$ = cloneInto([
+ [ '(', uBOL_$scriptletName$.toString(), ')();' ],
+ { type: 'text/javascript; charset=utf-8' },
+ ], self);
+ const blob = new page.Blob(...page.uBOL_$scriptletName$);
+ url = page.URL.createObjectURL(blob);
+ const doc = page.document;
+ script = doc.createElement('script');
+ script.async = false;
+ script.src = url;
+ (doc.head || doc.documentElement || doc).append(script);
+ } catch (ex) {
+ console.error(ex);
+ }
+ if ( url ) {
+ if ( script ) { script.remove(); }
+ page.URL.revokeObjectURL(url);
+ }
+ delete page.uBOL_$scriptletName$;
+}
+
+/******************************************************************************/
+
+// End of local scope
+})();
+
+/******************************************************************************/
+
+void 0;
diff --git a/platform/mv3/ubo-version b/platform/mv3/ubo-version
new file mode 100644
index 0000000..4437cef
--- /dev/null
+++ b/platform/mv3/ubo-version
@@ -0,0 +1 @@
+https://github.com/gorhill/uBlock/tree/4b83101ab9270a5403d66af4ebe08d251ac372ca
diff --git a/platform/nodejs/README.md b/platform/nodejs/README.md
new file mode 100644
index 0000000..0b3e3d8
--- /dev/null
+++ b/platform/nodejs/README.md
@@ -0,0 +1,158 @@
+# uBlock Origin Core
+
+The core filtering engines used in the uBlock Origin ("uBO") extension, and has
+no external dependencies.
+
+## Installation
+
+Install: `npm install @gorhill/ubo-core`
+
+This is a very early version and the API is subject to change at any time.
+
+This package uses [native JavaScript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules).
+
+
+## Description
+
+The package contains uBO's static network filtering engine ("SNFE"), which
+purpose is to parse and enforce filter lists. The matching algorithm is highly
+efficient, and _especially_ optimized to match against large sets of pure
+hostnames.
+
+The SNFE can be fed filter lists from a variety of sources, such as [EasyList/EasyPrivacy](https://easylist.to/),
+[uBlock filters](https://github.com/uBlockOrigin/uAssets/tree/master/filters),
+and also lists of domain names or hosts file format (i.e. block lists from [The Block List Project](https://github.com/blocklistproject/Lists#the-block-list-project),
+[Steven Black's HOSTS](https://github.com/StevenBlack/hosts#readme), etc).
+
+
+## Usage
+
+At the moment, there can be only one instance of the static network filtering
+engine ("SNFE"), which proxy API must be imported as follow:
+
+```js
+import { StaticNetFilteringEngine } from '@gorhill/ubo-core';
+```
+
+If you must import as a NodeJS module:
+
+```js
+const { StaticNetFilteringEngine } = await import('@gorhill/ubo-core');
+```
+
+
+Create an instance of SNFE:
+
+```js
+const snfe = StaticNetFilteringEngine.create();
+```
+
+Feed the SNFE with filter lists -- `useLists()` accepts an array of
+objects (or promises to object) which expose the raw text of a list
+through the `raw` property, and optionally the name of the list through the
+`name` property (how you fetch the lists is up to you):
+
+```js
+await snfe.useLists([
+ fetch('easylist').then(raw => ({ name: 'easylist', raw })),
+ fetch('easyprivacy').then(raw => ({ name: 'easyprivacy', raw })),
+]);
+```
+
+Now we are ready to match network requests:
+
+```js
+// Not blocked
+if ( snfe.matchRequest({
+ originURL: 'https://www.bloomberg.com/',
+ url: 'https://www.bloomberg.com/tophat/assets/v2.6.1/that.css',
+ type: 'stylesheet'
+}) !== 0 ) {
+ console.log(snfe.toLogData());
+}
+
+// Blocked
+if ( snfe.matchRequest({
+ originURL: 'https://www.bloomberg.com/',
+ url: 'https://securepubads.g.doubleclick.net/tag/js/gpt.js',
+ type: 'script'
+}) !== 0 ) {
+ console.log(snfe.toLogData());
+}
+
+// Unblocked
+if ( snfe.matchRequest({
+ originURL: 'https://www.bloomberg.com/',
+ url: 'https://sourcepointcmp.bloomberg.com/ccpa.js',
+ type: 'script'
+}) !== 0 ) {
+ console.log(snfe.toLogData());
+}
+```
+
+It is possible to pre-parse filter lists and save the intermediate results for
+later use -- useful to speed up the loading of filter lists. This will be
+documented eventually, but if you feel adventurous, you can look at the code
+and use this capability now if you figure out the details.
+
+---
+
+## Extras
+
+You can directly use specific APIs exposed by this package, here are some of
+them, which are used internally by uBO's SNFE.
+
+### HNTrieContainer
+
+A well optimised [compressed trie](https://en.wikipedia.org/wiki/Trie#Compressing_tries)
+container specialized to specifically store and lookup hostnames.
+
+The matching algorithm is designed for hostnames, i.e. the hostname labels
+making up a hostname are matched from right to left, such that `www.example.org`
+with be a match if `example.org` is stored into the trie, while
+`anotherexample.org` won't be a match.
+
+`HNTrieContainer` is designed to store a large number of hostnames with CPU and
+memory efficiency as a main concern -- and is a key component of uBO.
+
+To create and use a standalone `HNTrieContainer` object:
+
+```js
+import HNTrieContainer from '@gorhill/ubo-core/js/hntrie.js';
+
+const trieContainer = new HNTrieContainer();
+
+const aTrie = trieContainer.createOne();
+trieContainer.add(aTrie, 'example.org');
+trieContainer.add(aTrie, 'example.com');
+
+const anotherTrie = trieContainer.createOne();
+trieContainer.add(anotherTrie, 'foo.invalid');
+trieContainer.add(anotherTrie, 'bar.invalid');
+
+// matches() return the position at which the match starts, or -1 when
+// there is no match.
+
+// Matches: return 4
+console.log("trieContainer.matches(aTrie, 'www.example.org')", trieContainer.matches(aTrie, 'www.example.org'));
+
+// Does not match: return -1
+console.log("trieContainer.matches(aTrie, 'www.foo.invalid')", trieContainer.matches(aTrie, 'www.foo.invalid'));
+
+// Does not match: return -1
+console.log("trieContainer.matches(anotherTrie, 'www.example.org')", trieContainer.matches(anotherTrie, 'www.example.org'));
+
+// Matches: return 0
+console.log("trieContainer.matches(anotherTrie, 'foo.invalid')", trieContainer.matches(anotherTrie, 'foo.invalid'));
+```
+
+The `reset()` method must be used to remove all the tries from a trie container,
+you can't remove a single trie from the container.
+
+```js
+trieContainer.reset();
+```
+
+When you reset a trie container, you can't use the reference to prior instances
+of trie, i.e. `aTrie` and `anotherTrie` are no longer valid and shouldn't be
+used following a reset.
diff --git a/platform/nodejs/build.js b/platform/nodejs/build.js
new file mode 100644
index 0000000..dbc0843
--- /dev/null
+++ b/platform/nodejs/build.js
@@ -0,0 +1,34 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+/******************************************************************************/
+
+import fs from 'fs';
+
+import { pslInit } from './index.js';
+
+/******************************************************************************/
+
+fs.mkdirSync('./build', { recursive: true });
+fs.writeFileSync('./build/publicsuffixlist.json',
+ JSON.stringify(pslInit().toSelfie()));
diff --git a/platform/nodejs/index.js b/platform/nodejs/index.js
new file mode 100644
index 0000000..1d39a7d
--- /dev/null
+++ b/platform/nodejs/index.js
@@ -0,0 +1,281 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* globals WebAssembly */
+
+'use strict';
+
+/******************************************************************************/
+
+import { createRequire } from 'module';
+
+import { readFileSync } from 'fs';
+import { dirname, resolve } from 'path';
+import { domainToASCII, fileURLToPath } from 'url';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+import publicSuffixList from './lib/publicsuffixlist/publicsuffixlist.js';
+
+import snfe from './js/static-net-filtering.js';
+import { FilteringContext } from './js/filtering-context.js';
+import { LineIterator } from './js/text-utils.js';
+import * as sfp from './js/static-filtering-parser.js';
+
+import {
+ CompiledListReader,
+ CompiledListWriter,
+} from './js/static-filtering-io.js';
+
+/******************************************************************************/
+
+function loadJSON(path) {
+ return JSON.parse(readFileSync(resolve(__dirname, path), 'utf8'));
+}
+
+/******************************************************************************/
+
+async function enableWASM() {
+ const wasmModuleFetcher = function(path) {
+ const require = createRequire(import.meta.url); // jshint ignore:line
+ const wasm = new Uint8Array(require(`${path}.wasm.json`));
+ return WebAssembly.compile(wasm);
+ };
+ try {
+ const results = await Promise.all([
+ publicSuffixList.enableWASM(wasmModuleFetcher, './lib/publicsuffixlist/wasm/'),
+ snfe.enableWASM(wasmModuleFetcher, './js/wasm/'),
+ ]);
+ return results.every(a => a === true);
+ } catch(reason) {
+ console.log(reason);
+ }
+ return false;
+}
+
+/******************************************************************************/
+
+function pslInit(raw) {
+ if ( typeof raw === 'string' && raw.trim() !== '' ) {
+ publicSuffixList.parse(raw, domainToASCII);
+ return publicSuffixList;
+ }
+
+ // Use serialized version if available
+ let serialized = null;
+ try {
+ // Use loadJSON() because require() would keep the string in memory.
+ serialized = loadJSON('build/publicsuffixlist.json');
+ } catch (error) {
+ if ( process.env.npm_lifecycle_event !== 'build' ) {
+ // This should never happen except during package building.
+ console.error(error);
+ }
+ }
+ if ( serialized !== null ) {
+ publicSuffixList.fromSelfie(serialized);
+ return publicSuffixList;
+ }
+
+ raw = readFileSync(
+ resolve(__dirname, './assets/thirdparties/publicsuffix.org/list/effective_tld_names.dat'),
+ 'utf8'
+ );
+ if ( typeof raw !== 'string' || raw.trim() === '' ) {
+ console.error('Unable to populate public suffix list');
+ return;
+ }
+ publicSuffixList.parse(raw, domainToASCII);
+ return publicSuffixList;
+}
+
+/******************************************************************************/
+
+function compileList({ name, raw }, compiler, writer, options = {}) {
+ const lineIter = new LineIterator(raw);
+ const events = Array.isArray(options.events) ? options.events : undefined;
+
+ if ( name ) {
+ writer.properties.set('name', name);
+ }
+
+ const parser = new sfp.AstFilterParser({
+ maxTokenLength: snfe.MAX_TOKEN_LENGTH,
+ });
+
+ while ( lineIter.eot() === false ) {
+ let line = lineIter.next();
+ while ( line.endsWith(' \\') ) {
+ if ( lineIter.peek(4) !== ' ' ) { break; }
+ line = line.slice(0, -2).trim() + lineIter.next().trim();
+ }
+ parser.parse(line);
+ if ( parser.isFilter() === false ) { continue; }
+ if ( parser.isNetworkFilter() === false ) { continue; }
+ if ( compiler.compile(parser, writer) ) { continue; }
+ if ( compiler.error !== undefined && events !== undefined ) {
+ options.events.push({
+ type: 'error',
+ text: compiler.error
+ });
+ }
+ }
+
+ return writer.toString();
+}
+
+/******************************************************************************/
+
+async function useLists(lists, options = {}) {
+ if ( useLists.promise !== null ) {
+ throw new Error('Pending useLists() operation');
+ }
+
+ // Remove all filters
+ snfe.reset();
+
+ if ( Array.isArray(lists) === false || lists.length === 0 ) {
+ return;
+ }
+
+ let compiler = null;
+
+ const consumeList = list => {
+ let { compiled } = list;
+ if ( typeof compiled !== 'string' || compiled === '' ) {
+ const writer = new CompiledListWriter();
+ if ( compiler === null ) {
+ compiler = snfe.createCompiler();
+ }
+ compiled = compileList(list, compiler, writer, options);
+ }
+ snfe.fromCompiled(new CompiledListReader(compiled));
+ };
+
+ // Populate filtering engine with resolved filter lists
+ const promises = [];
+ for ( const list of lists ) {
+ const promise = list instanceof Promise ? list : Promise.resolve(list);
+ promises.push(promise.then(list => consumeList(list)));
+ }
+
+ useLists.promise = Promise.all(promises);
+ await useLists.promise;
+ useLists.promise = null; // eslint-disable-line require-atomic-updates
+
+ // Commit changes
+ snfe.freeze();
+ snfe.optimize();
+}
+
+useLists.promise = null;
+
+/******************************************************************************/
+
+const fctx = new FilteringContext();
+let snfeProxyInstance = null;
+
+class StaticNetFilteringEngine {
+ constructor() {
+ if ( snfeProxyInstance !== null ) {
+ throw new Error('Only a single instance is supported.');
+ }
+ snfeProxyInstance = this;
+ }
+
+ useLists(lists) {
+ return useLists(lists);
+ }
+
+ matchRequest(details) {
+ return snfe.matchRequest(fctx.fromDetails(details));
+ }
+
+ matchAndFetchModifiers(details, modifier) {
+ return snfe.matchAndFetchModifiers(fctx.fromDetails(details), modifier);
+ }
+
+ hasQuery(details) {
+ return snfe.hasQuery(details);
+ }
+
+ filterQuery(details) {
+ const directives = snfe.filterQuery(fctx.fromDetails(details));
+ if ( directives === undefined ) { return; }
+ return { redirectURL: fctx.redirectURL, directives };
+ }
+
+ isBlockImportant() {
+ return snfe.isBlockImportant();
+ }
+
+ toLogData() {
+ return snfe.toLogData();
+ }
+
+ createCompiler(parser) {
+ return snfe.createCompiler(parser);
+ }
+
+ compileList(...args) {
+ return compileList(...args);
+ }
+
+ serialize() {
+ return snfe.serialize();
+ }
+
+ deserialize(serialized) {
+ return snfe.unserialize(serialized);
+ }
+
+ static async create({ noPSL = false } = {}) {
+ const instance = new StaticNetFilteringEngine();
+
+ if ( noPSL !== true && !pslInit() ) {
+ throw new Error('Failed to initialize public suffix list.');
+ }
+
+ return instance;
+ }
+
+ static async release() {
+ if ( snfeProxyInstance === null ) { return; }
+ snfeProxyInstance = null;
+ await useLists([]);
+ }
+}
+
+/******************************************************************************/
+
+// rollup.js needs module.exports to be set back to the local exports object.
+// This is because some of the code (e.g. publicsuffixlist.js) sets
+// module.exports. Once all included files are written like ES modules, using
+// export statements, this should no longer be necessary.
+if ( typeof module !== 'undefined' && typeof exports !== 'undefined' ) {
+ module.exports = exports;
+}
+
+export {
+ enableWASM,
+ pslInit,
+ StaticNetFilteringEngine,
+};
diff --git a/platform/npm/.eslintrc.json b/platform/npm/.eslintrc.json
new file mode 100644
index 0000000..5f7c6b5
--- /dev/null
+++ b/platform/npm/.eslintrc.json
@@ -0,0 +1,38 @@
+{
+ "root": true,
+ "env": {
+ "es2021": true,
+ "node": true
+ },
+ "extends": "eslint:recommended",
+ "parserOptions": {
+ "ecmaVersion": 12,
+ "sourceType": "module"
+ },
+ "rules": {
+ "eqeqeq": [ "warn", "always" ],
+ "indent": [
+ "warn",
+ 4,
+ {
+ "ArrayExpression": "first",
+ "CallExpression": { "arguments": "first" },
+ "MemberExpression": "off",
+ "ObjectExpression": "off",
+ "ignoreComments": true,
+ "ignoredNodes": [
+ "AssignmentExpression:has(Literal)"
+ ]
+ }
+ ],
+ "getter-return": "off",
+ "no-control-regex": "off",
+ "no-empty": [ "error", { "allowEmptyCatch": true } ],
+ "no-promise-executor-return": [ "error" ],
+ "no-template-curly-in-string": [ "error" ],
+ "no-unreachable-loop": [ "error" ],
+ "no-useless-backreference": [ "error" ],
+ "no-useless-escape": "off",
+ "require-atomic-updates": [ "warn" ]
+ }
+}
diff --git a/platform/npm/.npmignore b/platform/npm/.npmignore
new file mode 100644
index 0000000..5c606d4
--- /dev/null
+++ b/platform/npm/.npmignore
@@ -0,0 +1,5 @@
+assets/
+coverage/
+tests/
+.eslintrc.json
+test.js
diff --git a/platform/npm/package-lock.json b/platform/npm/package-lock.json
new file mode 100644
index 0000000..b819bb0
--- /dev/null
+++ b/platform/npm/package-lock.json
@@ -0,0 +1,3038 @@
+{
+ "name": "@gorhill/ubo-core",
+ "version": "0.1.26",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@gorhill/ubo-core",
+ "version": "0.1.26",
+ "license": "GPL-3.0-or-later",
+ "devDependencies": {
+ "c8": "^7.12.0",
+ "eslint": "^8.29.0",
+ "esm-world": "0.1.3",
+ "mocha": "^10.2.0",
+ "scaling-palm-tree": "github:mjethani/scaling-palm-tree#15cf1ab37e038771e1ff8005edc46d95f176739f"
+ },
+ "engines": {
+ "node": ">=14.0.0",
+ "npm": ">=6.14.4"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz",
+ "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.4.0",
+ "globals": "^13.15.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.7",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
+ "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.17",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
+ "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "3.1.0",
+ "@jridgewell/sourcemap-codec": "1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
+ "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
+ "dev": true
+ },
+ "node_modules/acorn": {
+ "version": "8.8.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
+ "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "node_modules/browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "node_modules/c8": {
+ "version": "7.12.0",
+ "resolved": "https://registry.npmjs.org/c8/-/c8-7.12.0.tgz",
+ "integrity": "sha512-CtgQrHOkyxr5koX1wEUmN/5cfDa2ckbHRA4Gy5LAL0zaCFtVWJS5++n+w4/sr2GWGerBxgTjpKeDclk/Qk6W/A==",
+ "dev": true,
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@istanbuljs/schema": "^0.1.3",
+ "find-up": "^5.0.0",
+ "foreground-child": "^2.0.0",
+ "istanbul-lib-coverage": "^3.2.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-reports": "^3.1.4",
+ "rimraf": "^3.0.2",
+ "test-exclude": "^6.0.0",
+ "v8-to-istanbul": "^9.0.0",
+ "yargs": "^16.2.0",
+ "yargs-parser": "^20.2.9"
+ },
+ "bin": {
+ "c8": "bin/c8.js"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/c8/node_modules/yargs-parser": {
+ "version": "20.2.9",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "dev": true
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
+ "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
+ "dev": true
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "node_modules/chalk/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "node_modules/chalk/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "node_modules/chalk/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/chalk/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "dev": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "dev": true
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "node_modules/diff": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+ "dev": true
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true
+ },
+ "node_modules/eslint": {
+ "version": "8.29.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz",
+ "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==",
+ "dev": true,
+ "dependencies": {
+ "@eslint/eslintrc": "^1.3.3",
+ "@humanwhocodes/config-array": "^0.11.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.1.1",
+ "eslint-utils": "^3.0.0",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.4.0",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.15.0",
+ "grapheme-splitter": "^1.0.4",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-sdsl": "^4.1.4",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "regexpp": "^3.2.0",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+ "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "engines": {
+ "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=5"
+ }
+ },
+ "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+ "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/eslint/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/esm-world": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/esm-world/-/esm-world-0.1.3.tgz",
+ "integrity": "sha512-nzgmXAdSIuKf11R6Y1gFnnVkARCYvDobcPAdt85aKxw8lH5QOkwpdJ/2ezC/FIRSRxuebq/lsXnRVNRtJCyzDQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0",
+ "npm": ">=6.14.4"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
+ "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.8.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+ "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "node_modules/fastq": {
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
+ "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "node_modules/flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true
+ },
+ "node_modules/flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz",
+ "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==",
+ "dev": true
+ },
+ "node_modules/foreground-child": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz",
+ "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
+ "node_modules/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.19.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz",
+ "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true
+ },
+ "node_modules/ignore": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
+ "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "dev": true
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
+ "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
+ "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
+ "dev": true,
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^3.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-report/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-report/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz",
+ "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==",
+ "dev": true,
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/js-sdsl": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
+ "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==",
+ "dev": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/js-sdsl"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mocha": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
+ "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-colors": "4.1.1",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.5.3",
+ "debug": "4.3.4",
+ "diff": "5.0.0",
+ "escape-string-regexp": "4.0.0",
+ "find-up": "5.0.0",
+ "glob": "7.2.0",
+ "he": "1.2.0",
+ "js-yaml": "4.1.0",
+ "log-symbols": "4.1.0",
+ "minimatch": "5.0.1",
+ "ms": "2.1.3",
+ "nanoid": "3.3.3",
+ "serialize-javascript": "6.0.0",
+ "strip-json-comments": "3.1.1",
+ "supports-color": "8.1.1",
+ "workerpool": "6.2.1",
+ "yargs": "16.2.0",
+ "yargs-parser": "20.2.4",
+ "yargs-unparser": "2.0.0"
+ },
+ "bin": {
+ "_mocha": "bin/_mocha",
+ "mocha": "bin/mocha.js"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mochajs"
+ }
+ },
+ "node_modules/mocha/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/mocha/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "node_modules/mocha/node_modules/minimatch": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
+ "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mocha/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "node_modules/mocha/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
+ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
+ "dev": true,
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true
+ },
+ "node_modules/punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "node_modules/regexpp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+ "dev": true
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ },
+ "node_modules/scaling-palm-tree": {
+ "resolved": "git+ssh://git@github.com/mjethani/scaling-palm-tree.git#15cf1ab37e038771e1ff8005edc46d95f176739f",
+ "dev": true
+ },
+ "node_modules/serialize-javascript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+ "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+ "dev": true,
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
+ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
+ "dev": true
+ },
+ "node_modules/string-width": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ },
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/v8-to-istanbul": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz",
+ "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true
+ },
+ "node_modules/workerpool": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
+ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true
+ },
+ "node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "20.2.4",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "dev": true
+ },
+ "node_modules/yargs-unparser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^6.0.0",
+ "decamelize": "^4.0.0",
+ "flat": "^5.0.2",
+ "is-plain-obj": "^2.1.0"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true
+ }
+ },
+ "dependencies": {
+ "@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true
+ },
+ "@eslint/eslintrc": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz",
+ "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.4.0",
+ "globals": "^13.15.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ }
+ },
+ "@humanwhocodes/config-array": {
+ "version": "0.11.7",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
+ "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==",
+ "dev": true,
+ "requires": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.5"
+ }
+ },
+ "@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true
+ },
+ "@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true
+ },
+ "@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+ "dev": true
+ },
+ "@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+ "dev": true
+ },
+ "@jridgewell/trace-mapping": {
+ "version": "0.3.17",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
+ "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/resolve-uri": "3.1.0",
+ "@jridgewell/sourcemap-codec": "1.4.14"
+ }
+ },
+ "@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ }
+ },
+ "@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true
+ },
+ "@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ }
+ },
+ "@types/istanbul-lib-coverage": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
+ "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
+ "dev": true
+ },
+ "acorn": {
+ "version": "8.8.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
+ "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
+ "dev": true
+ },
+ "acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "requires": {}
+ },
+ "ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "anymatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "dev": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "c8": {
+ "version": "7.12.0",
+ "resolved": "https://registry.npmjs.org/c8/-/c8-7.12.0.tgz",
+ "integrity": "sha512-CtgQrHOkyxr5koX1wEUmN/5cfDa2ckbHRA4Gy5LAL0zaCFtVWJS5++n+w4/sr2GWGerBxgTjpKeDclk/Qk6W/A==",
+ "dev": true,
+ "requires": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@istanbuljs/schema": "^0.1.3",
+ "find-up": "^5.0.0",
+ "foreground-child": "^2.0.0",
+ "istanbul-lib-coverage": "^3.2.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-reports": "^3.1.4",
+ "rimraf": "^3.0.2",
+ "test-exclude": "^6.0.0",
+ "v8-to-istanbul": "^9.0.0",
+ "yargs": "^16.2.0",
+ "yargs-parser": "^20.2.9"
+ },
+ "dependencies": {
+ "yargs-parser": {
+ "version": "20.2.9",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "dev": true
+ }
+ }
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
+ "camelcase": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
+ "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "requires": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ }
+ },
+ "cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "dev": true
+ },
+ "cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "requires": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ }
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "dev": true
+ },
+ "deep-is": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "diff": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+ "dev": true
+ },
+ "doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true
+ },
+ "eslint": {
+ "version": "8.29.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz",
+ "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==",
+ "dev": true,
+ "requires": {
+ "@eslint/eslintrc": "^1.3.3",
+ "@humanwhocodes/config-array": "^0.11.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.1.1",
+ "eslint-utils": "^3.0.0",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.4.0",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.15.0",
+ "grapheme-splitter": "^1.0.4",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-sdsl": "^4.1.4",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "regexpp": "^3.2.0",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.3"
+ }
+ }
+ }
+ },
+ "eslint-scope": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+ "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ }
+ },
+ "eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+ "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+ "dev": true
+ },
+ "esm-world": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/esm-world/-/esm-world-0.1.3.tgz",
+ "integrity": "sha512-nzgmXAdSIuKf11R6Y1gFnnVkARCYvDobcPAdt85aKxw8lH5QOkwpdJ/2ezC/FIRSRxuebq/lsXnRVNRtJCyzDQ==",
+ "dev": true
+ },
+ "espree": {
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
+ "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
+ "dev": true,
+ "requires": {
+ "acorn": "^8.8.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.3.0"
+ }
+ },
+ "esquery": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+ "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.1.0"
+ }
+ },
+ "esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.2.0"
+ }
+ },
+ "estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "fastq": {
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
+ "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
+ "dev": true,
+ "requires": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^3.0.4"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true
+ },
+ "flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "requires": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ }
+ },
+ "flatted": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz",
+ "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==",
+ "dev": true
+ },
+ "foreground-child": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz",
+ "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
+ "get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "globals": {
+ "version": "13.19.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz",
+ "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.20.2"
+ }
+ },
+ "grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true
+ },
+ "he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true
+ },
+ "html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true
+ },
+ "ignore": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
+ "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
+ "dev": true
+ },
+ "import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true
+ },
+ "is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "dev": true
+ },
+ "is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "istanbul-lib-coverage": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
+ "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==",
+ "dev": true
+ },
+ "istanbul-lib-report": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
+ "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
+ "dev": true,
+ "requires": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^3.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "dependencies": {
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "istanbul-reports": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz",
+ "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==",
+ "dev": true,
+ "requires": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ }
+ },
+ "js-sdsl": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
+ "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ }
+ },
+ "locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^5.0.0"
+ }
+ },
+ "lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ }
+ },
+ "make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dev": true,
+ "requires": {
+ "semver": "^6.0.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "mocha": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
+ "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "4.1.1",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.5.3",
+ "debug": "4.3.4",
+ "diff": "5.0.0",
+ "escape-string-regexp": "4.0.0",
+ "find-up": "5.0.0",
+ "glob": "7.2.0",
+ "he": "1.2.0",
+ "js-yaml": "4.1.0",
+ "log-symbols": "4.1.0",
+ "minimatch": "5.0.1",
+ "ms": "2.1.3",
+ "nanoid": "3.3.3",
+ "serialize-javascript": "6.0.0",
+ "strip-json-comments": "3.1.1",
+ "supports-color": "8.1.1",
+ "workerpool": "6.2.1",
+ "yargs": "16.2.0",
+ "yargs-parser": "20.2.4",
+ "yargs-unparser": "2.0.0"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
+ "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "nanoid": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
+ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
+ "dev": true
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "requires": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ }
+ },
+ "p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "requires": {
+ "yocto-queue": "^0.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^3.0.2"
+ }
+ },
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true
+ },
+ "picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true
+ },
+ "prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true
+ },
+ "queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true
+ },
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "regexpp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+ "dev": true
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "requires": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ },
+ "scaling-palm-tree": {
+ "version": "git+ssh://git@github.com/mjethani/scaling-palm-tree.git#15cf1ab37e038771e1ff8005edc46d95f176739f",
+ "dev": true,
+ "from": "scaling-palm-tree@github:mjethani/scaling-palm-tree#15cf1ab37e038771e1ff8005edc46d95f176739f"
+ },
+ "serialize-javascript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+ "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true
+ },
+ "signal-exit": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
+ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ },
+ "test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "requires": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1"
+ }
+ },
+ "type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true
+ },
+ "uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "v8-to-istanbul": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz",
+ "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^1.6.0"
+ }
+ },
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true
+ },
+ "workerpool": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
+ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
+ "dev": true
+ },
+ "wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ }
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true
+ },
+ "yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "requires": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ }
+ },
+ "yargs-parser": {
+ "version": "20.2.4",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "dev": true
+ },
+ "yargs-unparser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^6.0.0",
+ "decamelize": "^4.0.0",
+ "flat": "^5.0.2",
+ "is-plain-obj": "^2.1.0"
+ }
+ },
+ "yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true
+ }
+ }
+}
diff --git a/platform/npm/package.json b/platform/npm/package.json
new file mode 100644
index 0000000..a505449
--- /dev/null
+++ b/platform/npm/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "@gorhill/ubo-core",
+ "version": "0.1.26",
+ "description": "To create a working instance of uBlock Origin's static network filtering engine",
+ "type": "module",
+ "main": "index.js",
+ "scripts": {
+ "build": "node build.js",
+ "lint": "eslint js/ *.js tests/*.js",
+ "test": "c8 --include=index.js --include=js/**/*.js node test.js --mocha",
+ "test-full-battery": "c8 --include=index.js --include=js/**/*.js node test.js --mocha --full-battery",
+ "check-leaks": "mocha --check-leaks tests/leaks.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/gorhill/uBlock.git"
+ },
+ "keywords": [
+ "uBlock",
+ "uBO",
+ "adblock",
+ "trie"
+ ],
+ "author": "Raymond Hill (https://github.com/gorhill)",
+ "license": "GPL-3.0-or-later",
+ "contributors": [
+ "Manish Jethani <code@manishjethani.io>"
+ ],
+ "bugs": {
+ "url": "https://github.com/uBlockOrigin/uBlock-issues/issues"
+ },
+ "homepage": "https://github.com/gorhill/uBlock#readme",
+ "engines": {
+ "node": ">=14.0.0",
+ "npm": ">=6.14.4"
+ },
+ "devDependencies": {
+ "c8": "^7.12.0",
+ "eslint": "^8.29.0",
+ "esm-world": "0.1.3",
+ "mocha": "^10.2.0",
+ "scaling-palm-tree": "github:mjethani/scaling-palm-tree#15cf1ab37e038771e1ff8005edc46d95f176739f"
+ }
+}
diff --git a/platform/npm/test.js b/platform/npm/test.js
new file mode 100644
index 0000000..5b4401e
--- /dev/null
+++ b/platform/npm/test.js
@@ -0,0 +1,59 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* eslint-disable-next-line no-redeclare */
+/* globals process */
+
+'use strict';
+
+/******************************************************************************/
+
+import { spawn } from "child_process";
+import { promisify } from 'util';
+
+/******************************************************************************/
+
+async function spawnMocha() {
+ const files = [
+ 'tests/wasm.js',
+ 'tests/snfe.js',
+ ];
+
+ const options = [];
+
+ if ( process.argv[3] === '--full-battery' ) {
+ files.push('tests/request-data.js');
+
+ options.push('--reporter', 'progress');
+ }
+
+ await promisify(spawn)('mocha', [ '--experimental-vm-modules', '--no-warnings', ...files, ...options ], { stdio: [ 'inherit', 'inherit', 'inherit' ] });
+}
+
+async function main() {
+ if ( process.argv[2] === '--mocha' ) {
+ await spawnMocha();
+ }
+}
+
+main();
+
+/******************************************************************************/
diff --git a/platform/npm/tests/.eslintrc.json b/platform/npm/tests/.eslintrc.json
new file mode 100644
index 0000000..4668ae7
--- /dev/null
+++ b/platform/npm/tests/.eslintrc.json
@@ -0,0 +1,5 @@
+{
+ "env": {
+ "mocha": true
+ }
+}
diff --git a/platform/npm/tests/_common.js b/platform/npm/tests/_common.js
new file mode 100644
index 0000000..c252113
--- /dev/null
+++ b/platform/npm/tests/_common.js
@@ -0,0 +1,34 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+/******************************************************************************/
+
+import process from 'process';
+
+process.on('warning', warning => {
+ // Ignore warnings about experimental features like
+ // --experimental-vm-modules
+ if ( warning.name !== 'ExperimentalWarning' ) {
+ console.warn(warning.stack);
+ }
+});
diff --git a/platform/npm/tests/data/bundle.tgz b/platform/npm/tests/data/bundle.tgz
new file mode 100644
index 0000000..e5e3c33
--- /dev/null
+++ b/platform/npm/tests/data/bundle.tgz
Binary files differ
diff --git a/platform/npm/tests/leaks.js b/platform/npm/tests/leaks.js
new file mode 100644
index 0000000..3285398
--- /dev/null
+++ b/platform/npm/tests/leaks.js
@@ -0,0 +1,30 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+/******************************************************************************/
+
+describe('Leaks', () => {
+ it('should not leak global variables', async () => {
+ await import('../index.js');
+ });
+});
diff --git a/platform/npm/tests/request-data.js b/platform/npm/tests/request-data.js
new file mode 100644
index 0000000..886fea3
--- /dev/null
+++ b/platform/npm/tests/request-data.js
@@ -0,0 +1,117 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+/******************************************************************************/
+
+import { strict as assert } from 'assert';
+import { readFile } from 'fs/promises';
+import { createRequire } from 'module';
+import { dirname, resolve } from 'path';
+import { fileURLToPath } from 'url';
+
+import { createWorld } from 'esm-world';
+
+import './_common.js';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+const require = createRequire(import.meta.url);
+
+const requests = require('scaling-palm-tree/requests.json');
+const results = require('./data/results.json');
+
+async function read(path) {
+ return readFile(resolve(__dirname, path), 'utf8');
+}
+
+describe('Request data', () => {
+ const typeMap = {
+ document: 'sub_frame',
+ stylesheet: 'stylesheet',
+ image: 'image',
+ media: 'media',
+ font: 'font',
+ script: 'script',
+ xhr: 'xmlhttprequest',
+ fetch: 'xmlhttprequest',
+ websocket: 'websocket',
+ ping: 'ping',
+
+ other: 'other',
+ eventsource: 'other',
+ manifest: 'other',
+ texttrack: 'other',
+ };
+
+ for ( let wasm of [ false, true ] ) {
+ context(`${wasm ? 'Wasm on' : 'Wasm off'}`, () => {
+ let engine = null;
+
+ before(async () => {
+ const { StaticNetFilteringEngine, enableWASM } = await createWorld('./index.js', { globals: global });
+
+ if ( wasm ) {
+ assert(await enableWASM());
+ }
+
+ engine = await StaticNetFilteringEngine.create();
+
+ await engine.useLists([
+ read('./data/assets/ublock/badware.txt')
+ .then(raw => ({ name: 'badware', raw })),
+ read('./data/assets/ublock/filters.txt')
+ .then(raw => ({ name: 'filters', raw })),
+ read('./data/assets/ublock/filters-2020.txt')
+ .then(raw => ({ name: 'filters-2020', raw })),
+ read('./data/assets/ublock/filters-2021.txt')
+ .then(raw => ({ name: 'filters-2021', raw })),
+ read('./data/assets/ublock/privacy.txt')
+ .then(raw => ({ name: 'privacy', raw })),
+ read('./data/assets/ublock/resource-abuse.txt')
+ .then(raw => ({ name: 'resource-abuse', raw })),
+ read('./data/assets/ublock/unbreak.txt')
+ .then(raw => ({ name: 'unbreak.txt', raw })),
+ read('./data/assets/thirdparties/easylist-downloads.adblockplus.org/easylist.txt')
+ .then(raw => ({ name: 'easylist', raw })),
+ read('./data/assets/thirdparties/easylist-downloads.adblockplus.org/easyprivacy.txt')
+ .then(raw => ({ name: 'easyprivacy', raw })),
+ read('./data/assets/thirdparties/pgl.yoyo.org/as/serverlist')
+ .then(raw => ({ name: 'PGL', raw })),
+ read('./data/assets/thirdparties/urlhaus-filter/urlhaus-filter-online.txt')
+ .then(raw => ({ name: 'urlhaus', raw })),
+ ]);
+ });
+
+ for ( let i = 0; i < requests.length; i++ ) {
+ const { url, frameUrl, cpt } = requests[i];
+ const request = { url, originURL: frameUrl, type: typeMap[cpt] };
+
+ const expected = results[i];
+
+ it(`should ${expected === 1 ? 'block' : 'allow'} ${request.type} URL ${request.url} from origin ${request.originURL}`, () => {
+ assert.equal(engine.matchRequest(request), expected);
+ });
+ }
+ });
+ }
+});
diff --git a/platform/npm/tests/snfe.js b/platform/npm/tests/snfe.js
new file mode 100644
index 0000000..f6f3158
--- /dev/null
+++ b/platform/npm/tests/snfe.js
@@ -0,0 +1,372 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+/******************************************************************************/
+
+import { strict as assert } from 'assert';
+
+import { createWorld } from 'esm-world';
+
+import './_common.js';
+
+describe('SNFE', () => {
+ for ( let wasm of [ false/*, true*/ ] ) {
+ context(`${wasm ? 'Wasm on' : 'Wasm off'}`, () => {
+ let module = null;
+ let engine = null;
+
+ beforeEach(async () => {
+ module = await createWorld('./index.js', { globals: global });
+
+ if ( wasm ) {
+ assert(await module.enableWASM());
+ }
+ });
+
+ afterEach(() => {
+ engine = null;
+ module = null;
+ });
+
+ describe('Initialization', () => {
+ it('should not reject on first attempt', async () => {
+ await module.StaticNetFilteringEngine.create();
+ });
+
+ it('should reject on second attempt', async () => {
+ await module.StaticNetFilteringEngine.create();
+ await assert.rejects(module.StaticNetFilteringEngine.create());
+ });
+
+ it('should reject on third attempt', async () => {
+ await module.StaticNetFilteringEngine.create();
+
+ try {
+ await module.StaticNetFilteringEngine.create();
+ } catch (error) {
+ }
+
+ await assert.rejects(module.StaticNetFilteringEngine.create());
+ });
+ });
+
+ describe('Filter loading', () => {
+ beforeEach(async () => {
+ engine = await module.StaticNetFilteringEngine.create();
+ });
+
+ it('should not reject on no lists', async () => {
+ await engine.useLists([]);
+ });
+
+ it('should not reject on one empty list', async () => {
+ await engine.useLists([
+ { name: 'easylist', raw: '' },
+ ]);
+ });
+
+ it('should not reject on one list containing one filter', async () => {
+ await engine.useLists([
+ { name: 'easylist', raw: '/foo^' },
+ ]);
+ });
+
+ it('should not reject on one list containing multiple filters', async () => {
+ await engine.useLists([
+ { name: 'easylist', raw: '/foo^\n||example.com^' },
+ ]);
+ });
+
+ it('should not reject on multiple lists containing multiple filters', async () => {
+ await engine.useLists([
+ { name: 'easylist', raw: '/foo^\n||example.com^' },
+ { name: 'easyprivacy', raw: '||example.net/bar/\n^bar.js?' },
+ ]);
+ });
+
+ it('should not reject on promised-based lists', async () => {
+ await engine.useLists([
+ Promise.resolve({ name: 'easylist', raw: '/foo^\n||example.com^' }),
+ Promise.resolve({ name: 'easyprivacy', raw: '||example.net/bar/\n^bar.js?' }),
+ ]);
+ });
+
+ it('should reject on promised-based lists in which a promise is rejected', async () => {
+ await assert.rejects(engine.useLists([
+ Promise.reject({ name: 'easylist', raw: '/foo^\n||example.com^' }),
+ Promise.resolve({ name: 'easyprivacy', raw: '||example.net/bar/\n^bar.js?' }),
+ ]));
+ });
+
+ it('should reject on promised-based lists in which all promises are rejected', async () => {
+ await assert.rejects(engine.useLists([
+ Promise.reject({ name: 'easylist', raw: '/foo^\n||example.com^' }),
+ Promise.reject({ name: 'easyprivacy', raw: '||example.net/bar/\n^bar.js?' }),
+ ]));
+ });
+
+ it('should not reject on second call in sequence', async () => {
+ await engine.useLists([
+ Promise.resolve({ name: 'easylist', raw: '/foo^\n||example.com^' }),
+ Promise.resolve({ name: 'easyprivacy', raw: '||example.net/bar/\n^bar.js?' }),
+ ]);
+
+ await engine.useLists([
+ Promise.resolve({ name: 'easylist', raw: '/foo^\n||example.com^' }),
+ Promise.resolve({ name: 'easyprivacy', raw: '||example.net/bar/\n^bar.js?' }),
+ ]);
+ });
+ });
+
+ describe('Serialization', () => {
+ beforeEach(async () => {
+ engine = await module.StaticNetFilteringEngine.create();
+ });
+
+ it('should not reject with no lists', async () => {
+ await engine.useLists([]);
+
+ await engine.serialize();
+ });
+
+ it('should not reject with one empty list', async () => {
+ await engine.useLists([
+ { name: 'easylist', raw: '' },
+ ]);
+
+ await engine.serialize();
+ });
+
+ it('should not reject with one list containing one filter', async () => {
+ await engine.useLists([
+ { name: 'easylist', raw: '/foo^' },
+ ]);
+
+ await engine.serialize();
+ });
+
+ it('should not reject with one list containing multiple filters', async () => {
+ await engine.useLists([
+ { name: 'easylist', raw: '/foo^\n||example.com^' },
+ ]);
+
+ await engine.serialize();
+ });
+
+ it('should not reject with multiple lists containing multiple filters', async () => {
+ await engine.useLists([
+ { name: 'easylist', raw: '/foo^\n||example.com^' },
+ { name: 'easyprivacy', raw: '||example.net/bar/\n^bar.js?' },
+ ]);
+
+ await engine.serialize();
+ });
+ });
+
+ describe('Deserialization', () => {
+ beforeEach(async () => {
+ engine = await module.StaticNetFilteringEngine.create();
+ });
+
+ it('should not reject with no lists', async () => {
+ await engine.useLists([]);
+
+ const serialized = await engine.serialize();
+ await engine.deserialize(serialized);
+ });
+
+ it('should not reject with one empty list', async () => {
+ await engine.useLists([
+ { name: 'easylist', raw: '' },
+ ]);
+
+ const serialized = await engine.serialize();
+ await engine.deserialize(serialized);
+ });
+
+ it('should not reject with one list containing one filter', async () => {
+ await engine.useLists([
+ { name: 'easylist', raw: '/foo^' },
+ ]);
+
+ const serialized = await engine.serialize();
+ await engine.deserialize(serialized);
+ });
+
+ it('should not reject with one list containing multiple filters', async () => {
+ await engine.useLists([
+ { name: 'easylist', raw: '/foo^\n||example.com^' },
+ ]);
+
+ const serialized = await engine.serialize();
+ await engine.deserialize(serialized);
+ });
+
+ it('should not reject with multiple lists containing multiple filters', async () => {
+ await engine.useLists([
+ { name: 'easylist', raw: '/foo^\n||example.com^' },
+ { name: 'easyprivacy', raw: '||example.net/bar/\n^bar.js?' },
+ ]);
+
+ const serialized = await engine.serialize();
+ await engine.deserialize(serialized);
+ });
+
+ // https://github.com/gorhill/uBlock/commit/8f461072f576cdf72c088a952ef342281a7c44d6
+ it('should correctly remove query parameter following deserialization', async () => {
+ await engine.useLists([
+ { name: 'custom', raw: '*$removeparam=/^utm_/' },
+ ]);
+ const request = {
+ originURL: 'https://www.example.com/?utm_source=1',
+ type: 'document',
+ url: 'https://www.example.com/?utm_source=1',
+ };
+ let result = engine.filterQuery(request);
+ assert.strictEqual(result.redirectURL, 'https://www.example.com/');
+ const serialized = await engine.serialize();
+ await engine.deserialize(serialized);
+ result = engine.filterQuery(request);
+ assert.strictEqual(result.redirectURL, 'https://www.example.com/');
+ });
+ });
+
+ describe('Filter matching', () => {
+ beforeEach(async () => {
+ engine = await module.StaticNetFilteringEngine.create();
+ });
+
+ it('should match pure-hostname block filter', async () => {
+ await engine.useLists([
+ { name: 'test', raw: '||example.net^' },
+ ]);
+ const r = engine.matchRequest({
+ originURL: 'https://www.example.com/',
+ type: 'image',
+ url: 'https://www.example.net/',
+ });
+ assert.strictEqual(r, 1);
+ });
+
+ it('should match pure-hostname exception filter', async () => {
+ await engine.useLists([
+ { name: 'test', raw: '||example.net^\n@@||example.net^' },
+ ]);
+ const r = engine.matchRequest({
+ originURL: 'https://www.example.com/',
+ type: 'image',
+ url: 'https://www.example.net/',
+ });
+ assert.strictEqual(r, 2);
+ });
+
+ it('should match pure-hostname block-important filter', async () => {
+ await engine.useLists([
+ { name: 'test', raw: '@@||example.net^\n||example.net^$important' },
+ ]);
+ const r = engine.matchRequest({
+ originURL: 'https://www.example.com/',
+ type: 'image',
+ url: 'https://www.example.net/',
+ });
+ assert.strictEqual(r, 1);
+ assert(engine.isBlockImportant());
+ });
+
+ it('should detect the filter is block-important', async () => {
+ await engine.useLists([
+ { name: 'test', raw: '||example.net^$important' },
+ ]);
+ engine.matchRequest({
+ originURL: 'https://www.example.com/',
+ type: 'image',
+ url: 'https://www.example.net/',
+ });
+ assert(engine.isBlockImportant());
+ });
+
+ it('should block all except stylesheets #1', async () => {
+ await engine.useLists([
+ { name: 'test', raw: '||example.com^$~stylesheet,all' },
+ ]);
+ const r = engine.matchRequest({
+ originURL: 'https://www.example.com/',
+ type: 'stylesheet',
+ url: 'https://www.example.com/',
+ });
+ assert.strictEqual(r, 0);
+ });
+
+ it('should block all except stylesheets #2', async () => {
+ await engine.useLists([
+ { name: 'test', raw: '||example.com^$all,~stylesheet' },
+ ]);
+ const r = engine.matchRequest({
+ originURL: 'https://www.example.com/',
+ type: 'stylesheet',
+ url: 'https://www.example.com/',
+ });
+ assert.strictEqual(r, 0);
+ });
+
+ // https://github.com/gorhill/uBlock/commit/d66cd1116c0e
+ it('should not match on localhost', async () => {
+ await engine.useLists([
+ { name: 'test', raw: '.js$domain=foo.*|bar.*\n/^/$domain=example.*|foo.*' },
+ ]);
+ const r = engine.matchRequest({
+ originURL: 'https://localhost/',
+ type: 'script',
+ url: 'https://localhost/baz.js',
+ });
+ assert.strictEqual(r, 0);
+ });
+
+ // https://github.com/AdguardTeam/AdguardFilters/issues/88067#issuecomment-1019518277
+ it('should match regex-based filter without `match-case` option', async () => {
+ await engine.useLists([
+ { name: 'test', raw: '/\.com\/[a-z]{9,}\/[a-z]{9,}\.js$/$script,1p' },
+ ]);
+ const r = engine.matchRequest({
+ originURL: 'https://example.com/',
+ type: 'script',
+ url: 'https://example.com/LQMDQSMLDAZAEHERE/LQMDQSMLDAZAEHERE.js',
+ });
+ assert.strictEqual(r, 1);
+ });
+
+ it('should not match regex-based filter with `match-case` option', async () => {
+ await engine.useLists([
+ { name: 'test', raw: '/\.com\/[a-z]{9,}\/[a-z]{9,}\.js$/$script,1p,match-case' },
+ ]);
+ const r = engine.matchRequest({
+ originURL: 'https://example.com/',
+ type: 'script',
+ url: 'https://example.com/LQMDQSMLDAZAEHERE/LQMDQSMLDAZAEHERE.js',
+ });
+ assert.strictEqual(r, 0);
+ });
+ });
+ });
+ }
+});
diff --git a/platform/npm/tests/wasm.js b/platform/npm/tests/wasm.js
new file mode 100644
index 0000000..5e7cbd2
--- /dev/null
+++ b/platform/npm/tests/wasm.js
@@ -0,0 +1,53 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+/* globals WebAssembly */
+
+'use strict';
+
+/******************************************************************************/
+
+import { strict as assert } from 'assert';
+
+import { createWorld } from 'esm-world';
+
+import './_common.js';
+
+describe('WASM', () => {
+ context('WebAssembly available', () => {
+ it('should fulfill with true', async () => {
+ const { enableWASM } = await createWorld('./index.js', { globals: { URL, WebAssembly } });
+
+ assert.equal(await enableWASM(), true);
+ });
+ });
+
+ context('WebAssembly not available', () => {
+ it('should fulfill with false', async () => {
+ // WebAssembly must be set to undefined explicitly; otherwise
+ // createWorld() ends up using the global WebAssembly object
+ // anyway.
+ const { enableWASM } = await createWorld('./index.js', { globals: { URL, WebAssembly: undefined } });
+
+ assert.equal(await enableWASM(), false);
+ });
+ });
+});
diff --git a/platform/opera/manifest.json b/platform/opera/manifest.json
new file mode 100644
index 0000000..abc0bda
--- /dev/null
+++ b/platform/opera/manifest.json
@@ -0,0 +1,116 @@
+{
+ "author": "Raymond Hill & contributors",
+ "background": {
+ "page": "background.html"
+ },
+ "browser_action": {
+ "default_icon": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png",
+ "64": "img/icon_64.png"
+ },
+ "default_popup": "popup-fenix.html",
+ "default_title": "uBlock Origin"
+ },
+ "commands": {
+ "launch-element-picker": {
+ "description": "__MSG_popupTipPicker__"
+ },
+ "launch-element-zapper": {
+ "description": "__MSG_popupTipZapper__"
+ },
+ "launch-logger": {
+ "description": "__MSG_popupTipLog__"
+ },
+ "open-dashboard": {
+ "description": "__MSG_popupTipDashboard__"
+ },
+ "relax-blocking-mode": {
+ "description": "__MSG_relaxBlockingMode__"
+ },
+ "toggle-cosmetic-filtering": {
+ "description": "__MSG_toggleCosmeticFiltering__"
+ }
+ },
+ "content_scripts": [
+ {
+ "matches": [
+ "http://*/*",
+ "https://*/*"
+ ],
+ "js": [
+ "js/vapi.js",
+ "js/vapi-client.js",
+ "js/contentscript.js"
+ ],
+ "all_frames": true,
+ "match_about_blank": true,
+ "run_at": "document_start"
+ },
+ {
+ "matches": [
+ "https://easylist.to/*",
+ "https://*.fanboy.co.nz/*",
+ "https://filterlists.com/*",
+ "https://forums.lanik.us/*",
+ "https://github.com/*",
+ "https://*.github.io/*",
+ "https://*.letsblock.it/*"
+ ],
+ "js": [
+ "/js/scriptlets/subscriber.js"
+ ],
+ "run_at": "document_idle",
+ "all_frames": false
+ },
+ {
+ "matches": [
+ "https://github.com/uBlockOrigin/*",
+ "https://ublockorigin.github.io/*",
+ "https://*.reddit.com/r/uBlockOrigin/*"
+ ],
+ "js": [
+ "/js/scriptlets/updater.js"
+ ],
+ "run_at": "document_idle",
+ "all_frames": false
+ }
+ ],
+ "default_locale": "en",
+ "description": "__MSG_extShortDesc__",
+ "icons": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png",
+ "64": "img/icon_64.png",
+ "128": "img/icon_128.png"
+ },
+ "incognito": "split",
+ "manifest_version": 2,
+ "minimum_opera_version": "60.0",
+ "name": "uBlock Origin",
+ "options_page": "dashboard.html",
+ "permissions": [
+ "contextMenus",
+ "privacy",
+ "storage",
+ "tabs",
+ "unlimitedStorage",
+ "webNavigation",
+ "webRequest",
+ "webRequestBlocking",
+ "<all_urls>"
+ ],
+ "short_name": "uBlock₀",
+ "sidebar_action": {
+ "default_icon": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png"
+ },
+ "default_panel": "logger-ui.html",
+ "default_title": "__MSG_statsPageName__"
+ },
+ "version": "1.14.23.17",
+ "web_accessible_resources": [
+ "/web_accessible_resources/*"
+ ]
+}
diff --git a/platform/safari/README.md b/platform/safari/README.md
new file mode 100644
index 0000000..769d245
--- /dev/null
+++ b/platform/safari/README.md
@@ -0,0 +1,16 @@
+# Safari platform
+
+The Safari platform does not support the WebExtensions
+framework and thus is no longer supported. Consequently
+the code base has been removed.
+
+Note that the code base here was before the
+[official fork Safari fork](https://github.com/el1t/uBlock-Safari) was
+created, so it does not correspond to the version of
+uBlock Origin which could be installed on Safari.
+
+The last commit which contains the code is
+917f3620e0c08b722bbd4d400bca2735d9f6975f.
+
+You can browse the last state of the removed code base at
+<https://github.com/gorhill/uBlock/tree/917f3620e0c08b722bbd4d400bca2735d9f6975f/platform/safari>.
diff --git a/platform/thunderbird/manifest.json b/platform/thunderbird/manifest.json
new file mode 100644
index 0000000..cb7ccca
--- /dev/null
+++ b/platform/thunderbird/manifest.json
@@ -0,0 +1,94 @@
+{
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "uBlock0@raymondhill.net",
+ "strict_min_version": "91.0"
+ }
+ },
+ "author": "Raymond Hill & contributors",
+ "background": {
+ "page": "background.html"
+ },
+ "browser_action": {
+ "browser_style": false,
+ "default_icon": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png"
+ },
+ "default_title": "uBlock Origin",
+ "default_popup": "popup-fenix.html"
+ },
+ "content_scripts": [
+ {
+ "matches": [
+ "http://*/*",
+ "https://*/*",
+ "file://*/*"
+ ],
+ "js": [
+ "/js/vapi.js",
+ "/js/vapi-client.js",
+ "/js/contentscript.js"
+ ],
+ "all_frames": true,
+ "match_about_blank": true,
+ "run_at": "document_start"
+ },
+ {
+ "matches": [
+ "https://easylist.to/*",
+ "https://*.fanboy.co.nz/*",
+ "https://filterlists.com/*",
+ "https://forums.lanik.us/*",
+ "https://github.com/*",
+ "https://*.github.io/*",
+ "https://*.letsblock.it/*"
+ ],
+ "js": [
+ "/js/scriptlets/subscriber.js"
+ ],
+ "run_at": "document_idle",
+ "all_frames": false
+ },
+ {
+ "matches": [
+ "https://github.com/uBlockOrigin/*",
+ "https://ublockorigin.github.io/*",
+ "https://*.reddit.com/r/uBlockOrigin/*"
+ ],
+ "js": [
+ "/js/scriptlets/updater.js"
+ ],
+ "run_at": "document_idle",
+ "all_frames": false
+ }
+ ],
+ "default_locale": "en",
+ "description": "__MSG_extShortDesc__",
+ "icons": {
+ "16": "img/ublock.svg",
+ "48": "img/ublock.svg",
+ "96": "img/ublock.svg"
+ },
+ "manifest_version": 2,
+ "name": "uBlock Origin",
+ "options_ui": {
+ "page": "dashboard.html",
+ "browser_style": false,
+ "open_in_tab": true
+ },
+ "permissions": [
+ "privacy",
+ "storage",
+ "tabs",
+ "webNavigation",
+ "webRequest",
+ "webRequestBlocking",
+ "<all_urls>"
+ ],
+ "short_name": "uBlock₀",
+ "version": "1.9.15.101",
+ "web_accessible_resources": [
+ "/web_accessible_resources/*"
+ ]
+}