diff --git a/.eslintrc.js b/.eslintrc.js index 8ff448efd..f91394cce 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,19 +2,18 @@ module.exports = { root: true, env: { browser: true, - node: true + node: true, }, parserOptions: { - parser: 'babel-eslint' + parser: 'babel-eslint', }, extends: [ '@nuxtjs', 'prettier', 'prettier/vue', 'plugin:prettier/recommended', - 'plugin:nuxt/recommended' - ], - plugins: [ - 'prettier' + 'plugin:nuxt/recommended', + 'plugin:json/recommended', ], + plugins: ['prettier'], } diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 000000000..e9041ab8f --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,25 @@ +name: Validate +on: + push: + pull_request: + +jobs: + validate: + name: Validate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.3.3 + - uses: actions/setup-node@v2.1.2 + with: + node-version: '14' + - name: Restore npm cache + uses: actions/cache@v2 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} + restore-keys: | + ${{ runner.os }}-node- + - name: Install dependencies + run: npm install + - name: Validate + run: npm run validate diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..253e4a11f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contributing + +Wappalyzer is an [MIT-licensed](https://github.com/aliasio/wappalyzer/blob/master/LICENSE), open source project written in JavaScript. Anyone is welcome to contribute. + +## Getting started + +To get started, see the [README](https://github.com/aliasio/wappalyzer/blob/master/README.md). + +## Submitting changes + +- First, run `yarn run validate` to identify any issues. +- Use descriptive commit messages, e.g. 'Add WordPress detection'. +- Push your commits to a new branch on your own fork. +- Finally, submit a [pull request](https://help.github.com/articles/about-pull-requests/) and describe your changes. + +## Adding a new technology + +Wappalyzer uses [regular expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) to fingerprint technologies. Refer to the [specification](https://github.com/AliasIO/wappalyzer/blob/master/README.md#specification) for detail. + +- Add a new block to [`src/technologies.json`](https://github.com/aliasio/wappalyzer/blob/master/src/technologies.json). +- Add an icon to [`src/drivers/webextension/images/icons`](https://github.com/aliasio/wappalyzer/tree/master/src/drivers/webextension/images/icons). The image must be square, either SVG or PNG (32 x 32 pixels). + +Only widely used technologies are accepted. When creating a pull request, include ten or more links to websites that use the application, a GitHub page with at least 1,000 stars or anything that will help establish the size of the user base. + +## Adding a new category + +Please [open an issue on GitHub](https://github.com/aliasio/wappalyzer/issues) first to discuss the need for a new category. + +To add a category, edit [`src/technologies.json`](https://github.com/aliasio/wappalyzer/blob/master/src/technologies.json) and update every [locale](https://github.com/aliasio/wappalyzer/tree/master/src/drivers/webextension/_locales). You may use the English category name in all of them. + +## Adding a new translation + +To add a new translation, copy the `en` folder in [`src/drivers/webextension/_locales`](https://github.com/aliasio/wappalyzer/tree/master/src/drivers/webextension/_locales), rename it to the relevant two-letter country code and update the containing `messages.json` file. + +## Adding a new feature + +Please [open an issue on GitHub](https://github.com/aliasio/wappalyzer/issues) first. New features and large changes are rarely accepted without prior discussion. diff --git a/README.md b/README.md index 97fdd7fc3..7694a4e59 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ # Wappalyzer [![Travis](https://travis-ci.org/aliasio/wappalyzer.svg?branch=master)](https://travis-ci.org/aliasio/wappalyzer/) -[Wappalyzer](https://www.wappalyzer.com) identifies technologies on websites. -It detects content management systems, ecommerce platforms, JavaScript frameworks, -analytics tools and [much more](https://www.wappalyzer.com/technologies). +[Wappalyzer](https://www.wappalyzer.com) identifies technologies on websites, including content management systems, ecommerce platforms, JavaScript frameworks, analytics tools and [much more](https://www.wappalyzer.com/technologies). * [wappalyzer on NPM](https://www.npmjs.com/package/wappalyzer) * [wappalyzer-core on NPM](https://www.npmjs.com/package/wappalyzer-core) * [Chrome extension](https://chrome.google.com/webstore/detail/wappalyzer/gppongmhjkpfnbhagpmjfkannfbllamg) * [Firefox add-on](https://addons.mozilla.org/en-US/firefox/addon/wappalyzer/) * [Edge extension](https://microsoftedge.microsoft.com/addons/detail/mnbndgmknlpdjdnjfmfcdjoegcckoikn) -* [Bookmarklet](https://www.wappalyzer.com/download) -* [wappalyzer/cli on Docker Hub](https://hub.docker.com/r/wappalyzer/cli/) +* [Safari extension](https://apps.apple.com/app/wappalyzer/id1520333300) +* [All apps and integrations](https://www.wappalyzer.com/api/download) * [Wappalyzer REST APIs](https://www.wappalyzer.com/api/) -## Documentation +## Prerequisites -Please read the [developer documentation](https://www.wappalyzer.com/docs). +- [Git](https://git-scm.com) +- [Node.js](https://nodejs.org) version 12 or higher +- [Yarn](https://yarnpkg.com) ## Quick start @@ -46,3 +46,430 @@ node src/drivers/npm/cli.js https://example.com * Go go `about:debugging#/runtime/this-firefox` * Click 'Load Temporary Add-on' * Select `src/drivers/webextension/manifest.json` + +## Specification + +A long list of [regular expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) is used to identify technologies on web pages. Wappalyzer inspects HTML code, as well as JavaScript variables, response headers and more. + +Patterns (regular expressions) are kept in [`src/technologies.json`](https://github.com/aliasio/wappalyzer/blob/master/src/technologies.json). The following is an example of an application fingerprint. + +#### Example + +```json +"Example": { + "description": "A short description of the technology.", + "cats": [ + "1" + ], + "cookies": { + "cookie_name": "Example" + }, + "dom": { + "#example-id": { + "attributes": { + "class": "example-class" + }, + "properties": { + "example-property": "" + }, + "text": "Example text content" + } + }, + "dns": { + "MX": [ + "example\\.com" + ] + }, + "js": { + "Example.method": "" + }, + "excludes": "Example", + "headers": { + "X-Powered-By": "Example" + }, + "html": "]example\\.css", + "css": "\\.example-class", + "robots": "Disallow: /unique-path/", + "implies": "PHP\\;confidence:50", + "meta": { + "generator": "(?:Example|Another Example)" + }, + "script": "example-([0-9.]+)\\.js\\;confidence:50\\;version:\\1", + "url": "example\\.com", + "xhr": "example\\.com", + "oss": true, + "saas": true, + "pricing": ["medium", "freemium", "recurring"], + "website": "https://example.com", +} +``` + +## JSON fields + +Find the JSON schema at [`schema.json`](https://github.com/aliasio/wappalyzer/blob/master/schema.json). + +### Required properties + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescriptionExample
catsArray + One or more category IDs. + [1, 6]
websiteStringURL of the application's website. + "https://example.com" +
+ +### Optional properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescriptionExample
descriptionString + A short description of the technology in British English (max. + 250 characters). Write in a neutral, factual tone; not like an + ad. + "A short description."
iconStringApplication icon filename."WordPress.svg"
cpeString + The + CPE + is a structured naming scheme for applications, see the + specification. + "cpe:/a:apache:http_server"
saasBoolean + The technology is offered as a Software-as-a-Service (SaaS), i.e. hosted or cloud-based. + true
ossBoolean + The technology has an open-source license. + true
pricingArray +Cost indicator (based on a typical plan or average monthly price) and available pricing models. For paid products only. + +One of: +
    +
  • low Up to US 100 / mo
  • +
  • mid Up US 1,000 / mo
  • +
  • high More than 10,000 / mo
  • +
+ +Plus any of: +
    +
  • freemium Free plan available
  • +
  • onetime One-time payments accepted
  • +
  • recurring Subscriptions available
  • +
  • poa Price on asking
  • +
  • payg Pay as you go (e.g. commissions or usage-based fees)
  • +
+
["low", "freemium"]
+ +### Implies and excludes (optional) + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescriptionExample
impliesString | Array + The presence of one application can imply the presence of + another, e.g. WordpPress means PHP is also in use. + "PHP"
excludesString | Array + Opposite of implies. The presence of one application can exclude + the presence of another. + "Apache"
+ +### Patterns (optional) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescriptionExample
cookiesObjectCookies.{ "cookie_name": "Cookie value" }
domObject + Uses a + query selector + to inspect element properties, attributes and text content. + + { "#example-id": { "property": { "example-prop": "" } } + } +
dnsObject + DNS records: supports MX, TXT, SOA and NS (NPM driver only). + + { "MX": "example\\.com" } +
jsObject + JavaScript properties (case sensitive). Avoid short property + names to prevent matching minified code. + { "jQuery.fn.jquery": "" }
headersObjectHTTP response headers.{ "X-Powered-By": "^WordPress$" }
htmlString | Array + HTML source code. Patterns must include an HTML opening tag to + avoid matching plain text. For performance reasons, avoid + html where possible and use + dom instead. + "<a [^>]*href=\"index.html"
cssString | Array + CSS rules. Unavailable when a website enforces a same-origin + policy. For performance reasons, only a portion of the available + CSS rules are used to find matches. + "\\.example-class"
robotsString | Array + Robots.txt contents. + "Disallow: /unique-path/"
urlStringFull URL of the page."^https?//.+\\.wordpress\\.com"
xhrStringHostnames of XHR requests."cdn\\.netlify\\.com"
metaObjectHTML meta tags, e.g. generator.{ "generator": "^WordPress$" }
scriptsString | Array + URLs of JavaScript files included on the page. + "jquery\\.js"
+ +## Patterns + +Patterns are essentially JavaScript regular expressions written as strings, but with some additions. + +### Quirks and pitfalls + +- Because of the string format, the escape character itself must be escaped when using special characters such as the dot (`\\.`). Double quotes must be escaped only once (`\"`). Slashes do not need to be escaped (`/`). +- Flags are not supported. Regular expressions are treated as case-insensitive. +- Capture groups (`()`) are used for version detection. In other cases, use non-capturing groups (`(?:)`). +- Use start and end of string anchors (`^` and `$`) where possible for optimal performance. +- Short or generic patterns can cause applications to be identified incorrectly. Try to find unique strings to match. + +### Tags + +Tags (a non-standard syntax) can be appended to patterns (and implies and excludes, separated by `\\;`) to store additional information. + + + + + + + + + + + + + + + + + + + + + + +
TagDescriptionExample
confidence + Indicates a less reliable pattern that may cause false + positives. The aim is to achieve a combined confidence of 100%. + Defaults to 100% if not specified. + + "js": { "Mage": "\\;confidence:50" } +
version + Gets the version number from a pattern match using a special + syntax. + + "scripts": "jquery-([0-9.]+)\.js\\;version:\\1" +
+ +### Version syntax + +Application version information can be obtained from a pattern using a capture group. A condition can be evaluated using the ternary operator (`?:`). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExampleDescription
\\1Returns the first match.
\\1?a: + Returns a if the first match contains a value, nothing + otherwise. +
\\1?a:b + Returns a if the first match contains a value, b otherwise. +
\\1?:b + Returns nothing if the first match contains a value, b + otherwise. +
foo\\1 + Returns foo with the first match appended. +
diff --git a/bin/convert.js b/bin/convert.js new file mode 100644 index 000000000..735630988 --- /dev/null +++ b/bin/convert.js @@ -0,0 +1,137 @@ +const fs = require('fs') +const path = require('path') +const { convertFile } = require('convert-svg-to-png') + +const appPaths = () => { + const fileDir = path.dirname(require.main.filename).split('/') + // Remove current bin directory + fileDir.pop() + const appDir = fileDir.join('/') + + return { + basePath: fileDir, + appPath: appDir, + iconPath: appDir + '/src/drivers/webextension/images/icons', + convertPath: appDir + '/src/drivers/webextension/images/icons/converted', + } +} + +/** + * Copy files from source to destination. + * @param source + * @param destination + */ +function copyFiles(source, destination) { + // File destination will be created or overwritten by default. + fs.copyFileSync(source, destination) + // console.log(`${source} -> ${destination}`) +} + +/** + * Get extension of image file. + * @returns {string} + */ +function getFileExtension(filePath) { + return path.extname(filePath) +} + +/** + * Get base name of image file. + * @returns {string} + */ +function getFileName(filePath) { + return path.basename(filePath, getFileExtension(filePath)) +} + +function getConvertFileName(filePath) { + const name = getFileName(filePath) + return `${appPaths().convertPath}/${name}.png` +} + +/** + * Check if converted image exists + * @returns {boolean} + */ +function checkFileExists(imagePath) { + const fileExists = fs.existsSync(imagePath) + return fileExists +} + +function checkIfFile(filePath) { + return fs.statSync(filePath).isFile() +} + +function diffFiles(fileOne, fileTwo) { + const f1 = fs.readFileSync(fileOne) + const f2 = fs.readFileSync(fileTwo) + return f1.equals(f2) +} + +function dateModified(file) { + return fs.statSync(file).mtime +} + +function dateDiff(file) { + const now = new Date().getTime() + const then = dateModified(file).getTime() + return Math.round(Math.abs((then - now) / 86400000)) +} + +// Main script +fs.readdirSync(appPaths().iconPath).forEach((fileName) => { + const image = { + id: fileName, + path: `${appPaths().iconPath}/${fileName}`, + convertPath: `${appPaths().convertPath}/${fileName}`, + async convertAndCopy() { + await convertFile(this.path, { + height: 32, + width: 32, + outputFilePath: this.convertPath, + }).then((outputFile) => { + console.log(`SVG Converted: ${outputFile}`) + }) + }, + processFile() { + // Setup variables. + const ext = getFileExtension(this.path) + + // If SVG, run checks. + if (ext === '.svg') { + // Check if converted file exists. + const convertFilePath = getConvertFileName(this.path) + if (checkFileExists(convertFilePath)) { + // If file has changed in past 7 days. + if (dateDiff(this.path) > 8) { + console.log(`File exists, skipping: ${this.id}`) + return null + } + } + // Convert and copy file. + this.convertAndCopy() + } else { + // If PNG or other, just copy the file as-is. + // eslint-disable-next-line no-lonely-if + if (checkIfFile(this.path)) { + copyFiles(this.path, this.convertPath) + } else { + console.info('Not a file, skipping...') + } + } + }, + } + + image.processFile() +}) + +/** + +cd ; cp *.svg converted ; cd converted ; convert-svg-to-png *.svg --width 32 --height 32 ; rm *.svg +(async() => { + const inputFilePath = '/path/to/my-image.svg'; + const outputFilePath = await convertFile(inputFilePath); + + console.log(outputFilePath); + //=> "/path/to/my-image.png" +})(); +*/ diff --git a/bin/validate.js b/bin/validate.js index 2b9274734..7c9c9b238 100755 --- a/bin/validate.js +++ b/bin/validate.js @@ -6,171 +6,167 @@ const { technologies, categories } = JSON.parse( fs.readFileSync('./src/technologies.json') ) -try { - Object.keys(technologies).forEach((name) => { - const technology = technologies[name] - - // Validate regular expressions - ;['url', 'html', 'meta', 'headers', 'cookies', 'script', 'js'].forEach( - (type) => { - if (technology[type]) { - const keyed = - typeof technology[type] === 'string' || - Array.isArray(technology[type]) - ? { _: technology[type] } - : technology[type] - - Object.keys(keyed).forEach((key) => { - const patterns = Array.isArray(keyed[key]) - ? keyed[key] - : [keyed[key]] - - patterns.forEach((pattern, index) => { - const id = `${name}: ${type}[${key === '_' ? `${index}` : key}]` - - const [regex, ...flags] = pattern.split('\\;') - - let maxGroups = 0 - - flags.forEach((flag) => { - const [key, value] = flag.split(':') - - if (key === 'version') { - const refs = value.match(/\\(\d+)/g) - - if (refs) { - maxGroups = refs.reduce((max, ref) => - Math.max(max, parseInt(refs[1] || 0)) - ) - } - } else if (key === 'confidence') { - if ( - !/^\d+$/.test(value) || - parseInt(value, 10) < 0 || - parseInt(value, 10) > 99 - ) { - throw new Error( - `Confidence value must a number between 0 and 99: ${value} (${id})` - ) - } - } else { - throw new Error(`Invalid flag: ${key} (${id})`) - } - }) - - // Validate regular expression - try { - // eslint-disable-next-line no-new - new RegExp(regex) - } catch (error) { - throw new Error(`${error.message} (${id})`) - } - - // Count capture groups - const groups = new RegExp(`${regex}|`).exec('').length - 1 +Object.keys(technologies).forEach((name) => { + const technology = technologies[name] - if (groups > maxGroups) { - throw new Error( - `Too many non-capturing groups, expected ${maxGroups}: ${regex} (${id})` - ) - } + // Validate regular expressions + ;['url', 'html', 'meta', 'headers', 'cookies', 'script', 'js'].forEach( + (type) => { + if (technology[type]) { + const keyed = + typeof technology[type] === 'string' || + Array.isArray(technology[type]) + ? { _: technology[type] } + : technology[type] - if (type === 'html' && !/[<>]/.test(regex)) { - throw new Error( - `HTML pattern must include < or >: ${regex} (${id})` - ) - } - }) - }) - } - } - ) + Object.keys(keyed).forEach((key) => { + const patterns = Array.isArray(keyed[key]) ? keyed[key] : [keyed[key]] - // Validate categories - technology.cats.forEach((id) => { - if (!categories[id]) { - throw new Error(`No such category: ${id} (${name})`) - } - }) + patterns.forEach((pattern, index) => { + const id = `${name}: ${type}[${key === '_' ? `${index}` : key}]` - // Validate icons - if (technology.icon && !fs.existsSync(`${iconPath}/${technology.icon}`)) { - throw new Error(`No such icon: ${technology.icon} (${name})`) - } + const [regex, ...flags] = pattern.split('\\;') - // Validate website URLs - try { - // eslint-disable-next-line no-new - const { protocol } = new URL(technology.website) + let maxGroups = 0 - if (protocol !== 'http:' && protocol !== 'https:') { - throw new Error('Invalid protocol') - } - } catch (error) { - throw new Error(`Invalid website URL: ${technology.website} (${name})`) - } + flags.forEach((flag) => { + const [key, value] = flag.split(':') - // Validate implies and excludes - const { implies, excludes } = technology + if (key === 'version') { + const refs = value.match(/\\(\d+)/g) - if (implies) { - ;(Array.isArray(implies) ? implies : [implies]).forEach((implied) => { - const [_name, ...flags] = implied.split('\\;') + if (refs) { + maxGroups = refs.reduce((max, ref) => + Math.max(max, parseInt(refs[1] || 0)) + ) + } + } else if (key === 'confidence') { + if ( + !/^\d+$/.test(value) || + parseInt(value, 10) < 0 || + parseInt(value, 10) > 99 + ) { + throw new Error( + `Confidence value must a number between 0 and 99: ${value} (${id})` + ) + } + } else { + throw new Error(`Invalid flag: ${key} (${id})`) + } + }) - const id = `${name}: implies[${implied}]` + // Validate regular expression + try { + // eslint-disable-next-line no-new + new RegExp(regex) + } catch (error) { + throw new Error(`${error.message} (${id})`) + } - if (!technologies[_name]) { - throw new Error(`Implied technology does not exist: ${_name} (${id})`) - } + // Count capture groups + const groups = new RegExp(`${regex}|`).exec('').length - 1 - flags.forEach((flag) => { - const [key, value] = flag.split(':') + if (groups > maxGroups) { + throw new Error( + `Too many non-capturing groups, expected ${maxGroups}: ${regex} (${id})` + ) + } - if (key === 'confidence') { - if ( - !/^\d+$/.test(value) || - parseInt(value, 10) < 0 || - parseInt(value, 10) > 99 - ) { + if (type === 'html' && !/[<>]/.test(regex)) { throw new Error( - `Confidence value must a number between 0 and 99: ${value} (${id})` + `HTML pattern must include < or >: ${regex} (${id})` ) } - } else { - throw new Error(`Invalid flag: ${key} (${id})`) - } + }) }) - }) + } } + ) - if (excludes) { - ;(Array.isArray(excludes) ? excludes : [excludes]).forEach((excluded) => { - const id = `${name}: excludes[${excluded}]` - - if (!technologies[excluded]) { - throw new Error( - `Excluded technology does not exist: ${excluded} (${id})` - ) - } - }) + // Validate categories + technology.cats.forEach((id) => { + if (!categories[id]) { + throw new Error(`No such category: ${id} (${name})`) } }) // Validate icons - fs.readdirSync(iconPath).forEach((file) => { - const filePath = `${iconPath}/${file}` + if (technology.icon && !fs.existsSync(`${iconPath}/${technology.icon}`)) { + throw new Error(`No such icon: ${technology.icon} (${name})`) + } + + // Validate website URLs + try { + // eslint-disable-next-line no-new + const { protocol } = new URL(technology.website) + + if (protocol !== 'http:' && protocol !== 'https:') { + throw new Error('Invalid protocol') + } + } catch (error) { + throw new Error(`Invalid website URL: ${technology.website} (${name})`) + } + + // Validate implies and excludes + const { implies, excludes } = technology + + if (implies) { + ;(Array.isArray(implies) ? implies : [implies]).forEach((implied) => { + const [_name, ...flags] = implied.split('\\;') + + const id = `${name}: implies[${implied}]` - if (fs.statSync(filePath).isFile() && !file.startsWith('.')) { - if (!/^(png|svg)$/i.test(file.split('.').pop())) { - throw new Error(`Incorrect file type, expected PNG or SVG: ${filePath}`) + if (!technologies[_name]) { + throw new Error(`Implied technology does not exist: ${_name} (${id})`) } - if (!Object.values(technologies).some(({ icon }) => icon === file)) { - throw new Error(`Extraneous file: ${filePath}}`) + flags.forEach((flag) => { + const [key, value] = flag.split(':') + + if (key === 'confidence') { + if ( + !/^\d+$/.test(value) || + parseInt(value, 10) < 0 || + parseInt(value, 10) > 99 + ) { + throw new Error( + `Confidence value must a number between 0 and 99: ${value} (${id})` + ) + } + } else { + throw new Error(`Invalid flag: ${key} (${id})`) + } + }) + }) + } + + if (excludes) { + ;(Array.isArray(excludes) ? excludes : [excludes]).forEach((excluded) => { + const id = `${name}: excludes[${excluded}]` + + if (!technologies[excluded]) { + throw new Error( + `Excluded technology does not exist: ${excluded} (${id})` + ) } + }) + } +}) + +// Validate icons +fs.readdirSync(iconPath).forEach((file) => { + const filePath = `${iconPath}/${file}` + + if (fs.statSync(filePath).isFile() && !file.startsWith('.')) { + if (!/^(png|svg)$/i.test(file.split('.').pop())) { + throw new Error(`Incorrect file type, expected PNG or SVG: ${filePath}`) } - }) -} catch (error) { - // eslint-disable-next-line no-console - console.error(error.message) -} + + if ( + !Object.values(technologies).some(({ icon }) => icon === file) && + file !== 'default.svg' + ) { + throw new Error(`Extraneous file: ${filePath}}`) + } + } +}) diff --git a/package.json b/package.json index c24bd1c32..b9b66f450 100644 --- a/package.json +++ b/package.json @@ -8,17 +8,19 @@ "@nuxtjs/eslint-config": "^3.1.0", "@nuxtjs/eslint-module": "^2.0.0", "babel-eslint": "^10.1.0", - "eslint": "^7.7.0", - "eslint-config-prettier": "^6.11.0", + "eslint": "^7.13.0", + "eslint-config-prettier": "^6.15.0", + "eslint-plugin-json": "^2.1.2", "eslint-plugin-nuxt": "^1.0.0", "eslint-plugin-prettier": "^3.1.4", - "prettier": "^2.1.0" + "prettier": "^2.1.2" }, "scripts": { "link": "node ./bin/link.js", - "lint": "eslint --fix src/**/*.js", + "lint": "eslint src/**/*.{js,json}", + "lint:fix": "eslint --fix src/**/*.{js,json}", "validate": "yarn run lint && jsonlint -qV ./schema.json ./src/technologies.json && node ./bin/validate.js", - "convert": "cd ./src/drivers/webextension/images/icons ; cp *.svg converted ; cd converted ; convert-svg-to-png *.svg --width 32 --height 32 ; rm *.svg", + "convert": "node ./bin/convert.js", "prettify": "jsonlint -si --trim-trailing-commas --enforce-double-quotes ./src/technologies.json", "build": "yarn run link && yarn run validate && yarn run prettify && yarn run convert && node ./bin/build.js", "build:safari": "xcrun safari-web-extension-converter --swift --project-location build --force src/drivers/webextension" diff --git a/schema.json b/schema.json index 21ad68335..ad056b728 100644 --- a/schema.json +++ b/schema.json @@ -41,6 +41,19 @@ "type": "string", "pattern": "^.{0,500}$" }, + "oss": { + "type": "boolean" + }, + "saas": { + "type": "boolean" + }, + "pricing": { + "type": "array", + "items": { + "type": "string", + "pattern": "^(low|mid|high|freemium|poa|payg|onetime|recurring)$" + } + }, "cats": { "type": "array", "items": { @@ -73,6 +86,22 @@ } } }, + "dom": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^.+$": { + } + } + }, + "dns": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^.+$": { + } + } + }, "headers": { "type": "object", "additionalProperties": false, @@ -194,6 +223,9 @@ }, "icon": { "$ref": "#/definitions/non-empty-non-blank-string" + }, + "xhr": { + "$ref": "#/definitions/non-empty-non-blank-string" } } } diff --git a/src/drivers/npm/README.md b/src/drivers/npm/README.md index 9bba60c30..7c1d950e6 100644 --- a/src/drivers/npm/README.md +++ b/src/drivers/npm/README.md @@ -66,9 +66,9 @@ const options = { htmlMaxRows: 2000, }; -;(async function() { - const wappalyzer = await new Wappalyzer(options) +const wappalyzer = new Wappalyzer(options) +;(async function() { try { await wappalyzer.init() @@ -98,9 +98,9 @@ const Wappalyzer = require('wappalyzer'); const urls = ['https://www.wappalyzer.com', 'https://www.example.com'] -;(async function() { - const wappalyzer = await new Wappalyzer() +const wappalyzer = new Wappalyzer() +;(async function() { try { await wappalyzer.init() diff --git a/src/drivers/npm/cli.js b/src/drivers/npm/cli.js index 1cab50af9..9e2644bdb 100755 --- a/src/drivers/npm/cli.js +++ b/src/drivers/npm/cli.js @@ -77,7 +77,7 @@ Options: } ;(async function () { - const wappalyzer = await new Wappalyzer(options) + const wappalyzer = new Wappalyzer(options) try { await wappalyzer.init() diff --git a/src/drivers/npm/driver.js b/src/drivers/npm/driver.js index a68696437..9404e727d 100644 --- a/src/drivers/npm/driver.js +++ b/src/drivers/npm/driver.js @@ -1,5 +1,6 @@ const { URL } = require('url') const fs = require('fs') +const dns = require('dns').promises const path = require('path') const http = require('http') const https = require('https') @@ -50,6 +51,8 @@ const { technologies, categories } = JSON.parse( setTechnologies(technologies) setCategories(categories) +const xhrDebounce = [] + function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)) } @@ -67,12 +70,41 @@ function analyzeJs(js) { ) } +function analyzeDom(dom) { + return Array.prototype.concat.apply( + [], + dom.map(({ name, selector, text, property, attribute, value }) => { + const technology = Wappalyzer.technologies.find( + ({ name: _name }) => name === _name + ) + + if (text) { + return analyzeManyToMany(technology, 'dom.text', { [selector]: [text] }) + } + + if (property) { + return analyzeManyToMany(technology, `dom.properties.${property}`, { + [selector]: [value], + }) + } + + if (attribute) { + return analyzeManyToMany(technology, `dom.attributes.${attribute}`, { + [selector]: [value], + }) + } + + return [] + }) + ) +} + function get(url) { if (['http:', 'https:'].includes(url.protocol)) { const { get } = url.protocol === 'http:' ? http : https return new Promise((resolve, reject) => - get(url.href, (response) => { + get(url, { rejectUnauthorized: false }, (response) => { if (response.statusCode >= 400) { return reject( new Error(`${response.statusCode} ${response.statusMessage}`) @@ -86,7 +118,7 @@ function get(url) { response.on('data', (data) => (body += data)) response.on('error', (error) => reject(new Error(error.message))) response.on('end', () => resolve(body)) - }) + }).on('error', (error) => reject(new Error(error.message))) ) } else { throw new Error(`Invalid protocol: ${url.protocol}`) @@ -127,6 +159,8 @@ class Driver { try { this.browser = await puppeteer.launch({ + ignoreHTTPSErrors: true, + acceptInsecureCerts: true, args: chromiumArgs, executablePath: await chromiumBin, }) @@ -135,7 +169,11 @@ class Driver { this.log('Browser disconnected') if (!this.destroyed) { - await this.init() + try { + await this.init() + } catch (error) { + this.log(error.toString()) + } } }) } catch (error) { @@ -173,7 +211,11 @@ class Driver { class Site { constructor(url, headers = {}, driver) { - ;({ options: this.options, browser: this.browser } = driver) + ;({ + options: this.options, + browser: this.browser, + init: this.initDriver, + } = driver) this.options.headers = { ...this.options.headers, @@ -194,6 +236,8 @@ class Site { this.listeners = {} this.pages = [] + + this.dns = [] } log(message, source = 'driver', type = 'log') { @@ -225,12 +269,26 @@ class Site { } } - timeout() { - return new Promise((resolve, reject) => - setTimeout(() => { - reject(new Error('The website took too long to respond')) - }, this.options.maxWait) - ) + promiseTimeout( + promise, + errorMessage = 'The website took too long to respond' + ) { + let timeout = null + + return Promise.race([ + new Promise((resolve, reject) => { + timeout = setTimeout(() => { + clearTimeout(timeout) + + reject(new Error(errorMessage)) + }, this.options.maxWait) + }), + promise.then((value) => { + clearTimeout(timeout) + + return value + }), + ]) } async goto(url) { @@ -249,7 +307,11 @@ class Site { } if (!this.browser) { - throw new Error('Browser closed') + await this.initDriver() + + if (!this.browser) { + throw new Error('Browser closed') + } } const page = await this.browser.newPage() @@ -268,6 +330,26 @@ class Site { page.on('request', async (request) => { try { + if (request.resourceType() === 'xhr') { + let hostname + + try { + ;({ hostname } = new URL(request.url())) + } catch (error) { + return + } + + if (!xhrDebounce.includes(hostname)) { + xhrDebounce.push(hostname) + + setTimeout(() => { + xhrDebounce.splice(xhrDebounce.indexOf(hostname), 1) + + this.onDetect(analyze({ xhr: hostname })) + }, 1000) + } + } + if ( (responseReceived && request.isNavigationRequest()) || request.frame() !== page.mainFrame() || @@ -308,8 +390,6 @@ class Site { ] }) - this.contentType = headers['content-type'] || null - if (response.status() >= 300 && response.status() < 400) { if (headers.location) { url = new URL(headers.location.slice(-1), url) @@ -323,7 +403,7 @@ class Site { this.onDetect(analyze({ headers, certIssuer })) - await this.emit('response', { page, response }) + await this.emit('response', { page, response, headers, certIssuer }) } } } catch (error) { @@ -337,99 +417,103 @@ class Site { ) try { - await Promise.race([ - this.timeout(), - page.goto(url.href, { waitUntil: 'domcontentloaded' }), - ]) + await this.promiseTimeout( + page.goto(url.href, { waitUntil: 'domcontentloaded' }) + ) await sleep(1000) + // page.on('console', (message) => this.log(message.text())) + // Links - const links = await ( - await Promise.race([ - this.timeout(), - page.evaluateHandle(() => - Array.from(document.getElementsByTagName('a')).map( - ({ hash, hostname, href, pathname, protocol, rel }) => ({ - hash, - hostname, - href, - pathname, - protocol, - rel, - }) + const links = await this.promiseTimeout( + ( + await this.promiseTimeout( + page.evaluateHandle(() => + Array.from(document.getElementsByTagName('a')).map( + ({ hash, hostname, href, pathname, protocol, rel }) => ({ + hash, + hostname, + href, + pathname, + protocol, + rel, + }) + ) ) - ), - ]) - ).jsonValue() + ) + ).jsonValue() + ) // CSS - const css = await ( - await Promise.race([ - this.timeout(), - page.evaluateHandle((maxRows) => { - const css = [] - - try { - if (!document.styleSheets.length) { - return '' - } + const css = await this.promiseTimeout( + ( + await this.promiseTimeout( + page.evaluateHandle((maxRows) => { + const css = [] + + try { + if (!document.styleSheets.length) { + return '' + } - for (const sheet of Array.from(document.styleSheets)) { - for (const rules of Array.from(sheet.cssRules)) { - css.push(rules.cssText) + for (const sheet of Array.from(document.styleSheets)) { + for (const rules of Array.from(sheet.cssRules)) { + css.push(rules.cssText) - if (css.length >= maxRows) { - break + if (css.length >= maxRows) { + break + } } } + } catch (error) { + return '' } - } catch (error) { - return '' - } - return css.join('\n') - }, this.options.htmlMaxRows), - ]) - ).jsonValue() + return css.join('\n') + }, this.options.htmlMaxRows) + ) + ).jsonValue() + ) // Script tags - const scripts = await ( - await Promise.race([ - this.timeout(), - page.evaluateHandle(() => - Array.from(document.getElementsByTagName('script')) - .map(({ src }) => src) - .filter((src) => src) - ), - ]) - ).jsonValue() + const scripts = await this.promiseTimeout( + ( + await this.promiseTimeout( + page.evaluateHandle(() => + Array.from(document.getElementsByTagName('script')) + .map(({ src }) => src) + .filter((src) => src) + ) + ) + ).jsonValue() + ) // Meta tags - const meta = await ( - await Promise.race([ - this.timeout(), - page.evaluateHandle(() => - Array.from(document.querySelectorAll('meta')).reduce( - (metas, meta) => { - const key = - meta.getAttribute('name') || meta.getAttribute('property') - - if (key) { - metas[key.toLowerCase()] = [meta.getAttribute('content')] - } + const meta = await this.promiseTimeout( + ( + await this.promiseTimeout( + page.evaluateHandle(() => + Array.from(document.querySelectorAll('meta')).reduce( + (metas, meta) => { + const key = + meta.getAttribute('name') || meta.getAttribute('property') + + if (key) { + metas[key.toLowerCase()] = [meta.getAttribute('content')] + } - return metas - }, - {} + return metas + }, + {} + ) ) - ), - ]) - ).jsonValue() + ) + ).jsonValue() + ) // JavaScript - const js = await Promise.race([ - this.timeout(), + const js = await this.promiseTimeout( page.evaluate( (technologies) => { return technologies.reduce((technologies, { name, chains }) => { @@ -461,14 +545,91 @@ class Site { Wappalyzer.technologies .filter(({ js }) => Object.keys(js).length) .map(({ name, js }) => ({ name, chains: Object.keys(js) })) - ), - ]) + ) + ) + + // DOM + const dom = await this.promiseTimeout( + page.evaluate( + (technologies) => { + return technologies.reduce((technologies, { name, dom }) => { + const toScalar = (value) => + typeof value === 'string' || typeof value === 'number' + ? value + : !!value + + Object.keys(dom).forEach((selector) => { + const nodes = document.querySelectorAll(selector) + + if (!nodes.length) { + return + } + + dom[selector].forEach(({ text, properties, attributes }) => { + nodes.forEach((node) => { + if (text) { + const value = node.textContent.trim() + + if (value) { + technologies.push({ + name, + selector, + text: value, + }) + } + } + + if (properties) { + Object.keys(properties).forEach((property) => { + if ( + Object.prototype.hasOwnProperty.call(node, property) + ) { + const value = node[property] + + if (typeof value !== 'undefined') { + technologies.push({ + name, + selector, + property, + value: toScalar(value), + }) + } + } + }) + } + + if (attributes) { + Object.keys(attributes).forEach((attribute) => { + if (node.hasAttribute(attribute)) { + const value = node.getAttribute(attribute) + + technologies.push({ + name, + selector, + attribute, + value: toScalar(value), + }) + } + }) + } + }) + }) + }) + + return technologies + }, []) + }, + Wappalyzer.technologies + .filter(({ dom }) => dom) + .map(({ name, dom }) => ({ name, dom })) + ) + ) // Cookies const cookies = (await page.cookies()).reduce( (cookies, { name, value }) => ({ ...cookies, - [name]: [value], + [name.toLowerCase()]: [value], }), {} ) @@ -497,8 +658,63 @@ class Site { html = batches.join('\n') } + // DNS + if (!Object.keys(this.dns).length) { + const records = {} + const resolve = (func, hostname) => { + return this.promiseTimeout( + func(hostname).catch((error) => { + if (error.code !== 'ENODATA') { + this.error(error) + } + + return [] + }) + ) + } + + const domain = url.hostname.replace(/^www\./, '') + + ;[ + records.cname, + records.ns, + records.mx, + records.txt, + records.soa, + ] = await Promise.all([ + resolve(dns.resolveCname, url.hostname), + resolve(dns.resolveNs, domain), + resolve(dns.resolveMx, domain), + resolve(dns.resolveTxt, domain), + resolve(dns.resolveSoa, domain), + ]) + + this.dns = Object.keys(records).reduce((dns, type) => { + dns[type] = dns[type] || [] + + Array.prototype.push.apply( + dns[type], + Array.isArray(records[type]) + ? records[type].map((value) => { + return typeof value === 'object' + ? Object.values(value).join(' ') + : value + }) + : [Object.values(records[type]).join(' ')] + ) + + return dns + }, {}) + + this.onDetect(analyze({ dns: this.dns })) + } + // Validate response - if (url.protocol !== 'file:' && !this.analyzedUrls[url.href].status) { + if ( + url.protocol !== 'file:' && + this.analyzedUrls[url.href] && + !this.analyzedUrls[url.href].status + ) { await page.close() this.log('Page closed') @@ -506,8 +722,8 @@ class Site { throw new Error('No response from server') } + this.onDetect(analyzeDom(dom)) this.onDetect(analyzeJs(js)) - this.onDetect( analyze({ url, @@ -530,9 +746,8 @@ class Site { ) && link.protocol && link.protocol.match(/https?:/) && - link.rel !== 'nofollow' && link.hostname === url.hostname && - extensions.test(link.pathname) + extensions.test(link.pathname.slice(-5)) ) { results.push(new URL(link.href.split('#')[0])) } @@ -551,6 +766,7 @@ class Site { meta, js, links: reducedLinks, + dns: this.dns, }) await page.close() @@ -559,7 +775,15 @@ class Site { return reducedLinks } catch (error) { - this.error(error) + if (error.constructor.name === 'TimeoutError') { + throw new Error('The website took too long to respond') + } + + if (error.message.includes('net::ERR_NAME_NOT_RESOLVED')) { + throw new Error('Hostname could not be resolved') + } + + throw new Error(error.message) } } diff --git a/src/drivers/npm/package.json b/src/drivers/npm/package.json index 59fb46b9d..19b45e17d 100644 --- a/src/drivers/npm/package.json +++ b/src/drivers/npm/package.json @@ -13,7 +13,7 @@ "software" ], "homepage": "https://www.wappalyzer.com/", - "version": "6.3.2", + "version": "6.5.27", "author": "Wappalyzer", "license": "MIT", "repository": { @@ -25,7 +25,7 @@ "url": "https://github.com/sponsors/aliasio" }, { - "url": "https://paypal.me/aliasio" + "url": "https://paypal.me/elbertalias" } ], "main": "driver.js", @@ -40,6 +40,6 @@ "wappalyzer": "./cli.js" }, "dependencies": { - "puppeteer": "^5.2.1" + "puppeteer": "^5.3.0" } -} +} \ No newline at end of file diff --git a/src/drivers/npm/yarn.lock b/src/drivers/npm/yarn.lock index 96f84a2a0..1ebd145e4 100644 --- a/src/drivers/npm/yarn.lock +++ b/src/drivers/npm/yarn.lock @@ -76,10 +76,10 @@ debug@4, debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" -devtools-protocol@0.0.781568: - version "0.0.781568" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.781568.tgz#4cdca90a952d2c77831096ff6cd32695d8715a04" - integrity sha512-9Uqnzy6m6zEStluH9iyJ3iHyaQziFnMnLeC8vK0eN6smiJmIx7+yB64d67C2lH/LZra+5cGscJAJsNXO+MdPMg== +devtools-protocol@0.0.799653: + version "0.0.799653" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.799653.tgz#86fc95ce5bf4fdf4b77a58047ba9d2301078f119" + integrity sha512-t1CcaZbvm8pOlikqrsIM9GOa7Ipp07+4h/q9u0JXBWjPCjHdBl9KkddX87Vv9vBHoBGtwV79sYQNGnQM6iS5gg== end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" @@ -264,13 +264,13 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -puppeteer@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-5.2.1.tgz#7f0564f0a5384f352a38c8cc42af875cd87f4ea6" - integrity sha512-PZoZG7u+T6N1GFWBQmGVG162Ak5MAy8nYSVpeeQrwJK2oYUlDWpHEJPcd/zopyuEMTv7DiztS1blgny1txR2qw== +puppeteer@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-5.3.0.tgz#0abf83d0f2d1273baf2b56885a813f8052903e33" + integrity sha512-GjqMk5GRro3TO0sw3QMsF1H7n+/jaK2OW45qMvqjYUyJ7y4oA//9auy969HHhTG3HZXaMxY/NWXF/NXlAFIvtw== dependencies: debug "^4.1.0" - devtools-protocol "0.0.781568" + devtools-protocol "0.0.799653" extract-zip "^2.0.0" https-proxy-agent "^4.0.0" mime "^2.0.3" diff --git a/src/drivers/webextension/_locales/ca/messages.json b/src/drivers/webextension/_locales/ca/messages.json index c5053ff24..6c155e8a4 100644 --- a/src/drivers/webextension/_locales/ca/messages.json +++ b/src/drivers/webextension/_locales/ca/messages.json @@ -1,24 +1,26 @@ { - "github": { "message": "Fork Wappalyzer a GitHub!" }, + "github": { "message": "Bifurcar Wappalyzer a GitHub" }, "twitter": { "message": "Seguir Wappalyzer a Twitter" }, "website": { "message": "Anar a wappalyzer.com" }, "options": { "message": "Opcions" }, "optionsSave": { "message": "Desar opcions" }, "optionsSaved": { "message": "Desat" }, - "optionUpgradeMessage": { "message": "Avisar-me quan hi hagi una actualització disponible" }, + "optionUpgradeMessage": { "message": "Notificar les actualitzacions disponibles" }, "optionDynamicIcon": { "message": "Utilitzar la icona de la tecnologia enlloc del logotip de Wappalyzer" }, "optionTracking": { "message": "Enviar les tecnologies identificades de forma anònima a wappalyzer.com" }, - "optionThemeMode": { "message": "Habilitar la compatibilitat de la manera fosc." }, - "optionBadge": { "message": "Show the number of identified technologies on the icon" }, - "disableOnDomain": { "message": "Disable on this website" }, - "clearCache": { "message": "Clear cached detections" }, + "optionThemeMode": { "message": "Habilitar la compatibilitat de l'aspecte fosc" }, + "optionBadge": { "message": "Mostrar el nombre de tecnologies identificades en la icona" }, + "disableOnDomain": { "message": "Desactivar en aquest web" }, + "clearCache": { "message": "Esborrar la memòria cau de les deteccions" }, "nothingToDo": { "message": "Res a fer aquí." }, "noAppsDetected": { "message": "No s'ha detectat cap tecnologia." }, "categoryPin": { "message": "Mostrar sempre la icona" }, - "termsAccept": { "message": "Acceptar" }, + "termsAccept": { "message": "M'està bé" }, + "termsDecline": { "message": "Desactivar" }, "termsContent": { "message": "Aquesta extensió envia informació anònima sobre els llocs web que visiteu, inclosos el nom de domini i les tecnologies identificades a wappalyzer.com. Això pot desactivar-se a Opcions." }, "privacyPolicy": { "message": "Política de privadesa" }, - "createAlert": { "message": "Create an alert for this website" }, + "createAlert": { "message": "Crear una alerta per aquest web" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Taulers de missatgeria" }, "categoryName3": { "message": "Gestor de bases de dades" }, @@ -35,7 +37,7 @@ "categoryName14": { "message": "Reproductors de vídeo" }, "categoryName15": { "message": "Sistemes de comentaris" }, "categoryName16": { "message": "Security" }, - "categoryName17": { "message": "Font Script" }, + "categoryName17": { "message": "Tipografies" }, "categoryName18": { "message": "Marcs web" }, "categoryName19": { "message": "Miscel·lània" }, "categoryName20": { "message": "Editors" }, @@ -50,7 +52,7 @@ "categoryName29": { "message": "Motors de cerca" }, "categoryName30": { "message": "Correu web" }, "categoryName31": { "message": "CDN" }, - "categoryName32": { "message": "Marketing Automation" }, + "categoryName32": { "message": "Automatitzacions de màrqueting" }, "categoryName33": { "message": "Extensions del servidor web" }, "categoryName34": { "message": "Bases de dades" }, "categoryName35": { "message": "Mapes" }, @@ -64,7 +66,7 @@ "categoryName43": { "message": "Paywall" }, "categoryName44": { "message": "Sistemes Build/CI" }, "categoryName45": { "message": "Sistemes SCADA" }, - "categoryName46": { "message": "Accés remot" }, + "categoryName46": { "message": "Accessos remots" }, "categoryName47": { "message": "Eines de desenvolupament" }, "categoryName48": { "message": "Emmagatzematge de xarxa" }, "categoryName49": { "message": "Lectors de canals" }, @@ -76,7 +78,7 @@ "categoryName55": { "message": "Comptabilitat" }, "categoryName56": { "message": "Cryptominer" }, "categoryName57": { "message": "Generadors de llocs estàtics" }, - "categoryName58": { "message": "User Onboarding" }, + "categoryName58": { "message": "Incorporacions d'usuaris" }, "categoryName59": { "message": "Llibreries JavaScript" }, "categoryName60": { "message": "Contenidors" }, "categoryName61": { "message": "SaaS" }, @@ -84,9 +86,14 @@ "categoryName63": { "message": "IaaS" }, "categoryName64": { "message": "Proxys invers" }, "categoryName65": { "message": "Balanceigs de càrrega" }, - "categoryName66": { "message": "UI Frameworks" }, + "categoryName66": { "message": "Marcs UI" }, "categoryName67": { "message": "Cookie compliance" }, - "categoryName68": { "message": "Accessibility"}, - "categoryName69": { "message": "Social login"}, - "categoryName70": { "message": "SSL/TLS certificate authority"} + "categoryName68": { "message": "Accesibilitat" }, + "categoryName69": { "message": "Inicis de sessió socials" }, + "categoryName70": { "message": "Autoritats de certificació SSL/TLS" }, + "categoryName71": { "message": "Programes d'afiliació" }, + "categoryName72": { "message": "Programacions de cites" }, + "categoryName73": { "message": "Enquestes" }, + "categoryName74": { "message": "Testeigs A/B" }, + "categoryName75": { "message": "Correus electrònics" } } diff --git a/src/drivers/webextension/_locales/de/messages.json b/src/drivers/webextension/_locales/de/messages.json index 631d7010d..1ebb87c26 100644 --- a/src/drivers/webextension/_locales/de/messages.json +++ b/src/drivers/webextension/_locales/de/messages.json @@ -1,92 +1,99 @@ { - "github": { "message": "Forke Wappalyzer bei GitHub!" }, - "twitter": { "message": "Folge Wappalyzer bei Twitter" }, - "website": { "message": "Gehe zu wappalyzer.com" }, - "options": { "message": "Optionen" }, - "optionsSave": { "message": "Optionen speichern" }, - "optionsSaved": { "message": "Gespeichert" }, - "optionUpgradeMessage": { "message": "Benachrichtige mich bei Upgrades" }, - "optionDynamicIcon": { "message": "Applikations Icon anstatt des Wappalyzer Icons verwenden" }, - "optionTracking": { "message": "Anonyme Statistiken an wappalyzer.com übermitteln" }, - "optionThemeMode": { "message": "Aktivieren dunklen Modus Kompatibilität." }, - "optionBadge": { "message": "Show the number of identified technologies on the icon" }, - "disableOnDomain": { "message": "Disable on this website" }, - "clearCache": { "message": "Clear cached detections" }, - "nothingToDo": { "message": "Nichts zu tun." }, - "noAppsDetected": { "message": "Keine Applikation entdeckt." }, - "categoryPin": { "message": "Immer Icon anzeigen" }, - "termsAccept": { "message": "Accept" }, - "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, - "privacyPolicy": { "message": "Privacy policy" }, - "createAlert": { "message": "Create an alert for this website" }, - "categoryName1": { "message": "CMS" }, - "categoryName2": { "message": "Nachrichten Board" }, - "categoryName3": { "message": "Datenbankverwaltung" }, - "categoryName4": { "message": "Dokumentations Tool" }, - "categoryName5": { "message": "Widget" }, - "categoryName6": { "message": "Ecommerce" }, - "categoryName7": { "message": "Fotogalerien" }, - "categoryName8": { "message": "Wikis" }, - "categoryName9": { "message": "Hosting-Panels" }, - "categoryName10": { "message": "Statistiken" }, - "categoryName11": { "message": "Blog" }, - "categoryName12": { "message": "JavaScript Framework" }, - "categoryName13": { "message": "Fehlertracker" }, - "categoryName14": { "message": "Videospieler" }, - "categoryName15": { "message": "Kommentarsystem" }, - "categoryName16": { "message": "Security" }, - "categoryName17": { "message": "Schrift Script" }, - "categoryName18": { "message": "Web Framework" }, - "categoryName19": { "message": "Sonstiges" }, - "categoryName20": { "message": "Editor" }, - "categoryName21": { "message": "LMS" }, - "categoryName22": { "message": "Web Server" }, - "categoryName23": { "message": "Cache Tool" }, - "categoryName24": { "message": "Rich Text Editor" }, - "categoryName25": { "message": "JavaScript Graphics" }, - "categoryName26": { "message": "Mobile Framework" }, - "categoryName27": { "message": "Programmiersprache" }, - "categoryName28": { "message": "Betriebssystem" }, - "categoryName29": { "message": "Suchmaschine" }, - "categoryName30": { "message": "Webmail" }, - "categoryName31": { "message": "CDN" }, - "categoryName32": { "message": "Marketing Automation" }, - "categoryName33": { "message": "Web Server Erweiterung" }, - "categoryName34": { "message": "Datenbank" }, - "categoryName35": { "message": "Map" }, - "categoryName36": { "message": "Werbenetzwerk" }, - "categoryName37": { "message": "Netzwerkdienst" }, - "categoryName38": { "message": "Medienserver" }, - "categoryName39": { "message": "Webcam" }, - "categoryName40": { "message": "Drucker" }, - "categoryName41": { "message": "Zahlungsverarbeiter" }, - "categoryName42": { "message": "Schlagwort Manager" }, - "categoryName43": { "message": "Bezahlblockade" }, - "categoryName44": { "message": "Build/CI-System" }, - "categoryName45": { "message": "SCADA System" }, - "categoryName46": { "message": "Fernzugriff" }, - "categoryName47": { "message": "Entwicklungswerkzeug" }, - "categoryName48": { "message": "Netzwerkspeicher" }, - "categoryName49": { "message": "Feedleser" }, - "categoryName50": { "message": "Dokumentmanagementsysteme" }, - "categoryName51": { "message": "Startseitenersteller" }, - "categoryName52": { "message": "Live-Chat" }, - "categoryName53": { "message": "CRM" }, + "github": { "message": "Forke Wappalyzer bei GitHub!" }, + "twitter": { "message": "Folge Wappalyzer bei Twitter" }, + "website": { "message": "Gehe zu wappalyzer.com" }, + "options": { "message": "Optionen" }, + "optionsSave": { "message": "Optionen speichern" }, + "optionsSaved": { "message": "Gespeichert" }, + "optionUpgradeMessage": { "message": "Benachrichtige mich bei Upgrades" }, + "optionDynamicIcon": { "message": "Applikations Icon anstatt des Wappalyzer Icons verwenden" }, + "optionTracking": { "message": "Anonyme Statistiken an wappalyzer.com übermitteln" }, + "optionThemeMode": { "message": "Dunkel-Modus aktivieren" }, + "optionBadge": { "message": "Anzahl der identifizierten Optionen am Icon anzeigen" }, + "disableOnDomain": { "message": "Auf dieser Website deaktivieren" }, + "clearCache": { "message": "Cache leeren" }, + "nothingToDo": { "message": "Nichts zu tun." }, + "noAppsDetected": { "message": "Keine Applikationen gefunden" }, + "categoryPin": { "message": "Icon immer anzeigen" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, + "termsContent": { "message": "Diese Erweiterung sendet anonyme Informationen über Websites, die Sie besuchen, einschließlich der Domain und der identifizierten Technologien, an wappalyzer.com. Dies kann in den Einstellungen deaktiviert werden." }, + "privacyPolicy": { "message": "Datenschutzerklärung" }, + "createAlert": { "message": "Alarm für diese Website erstellen" }, + "leadLists": { "message": "Lead generation tools" }, + "categoryName1": { "message": "CMS" }, + "categoryName2": { "message": "Nachrichten Board" }, + "categoryName3": { "message": "Datenbankverwaltung" }, + "categoryName4": { "message": "Dokumentations Tool" }, + "categoryName5": { "message": "Widget" }, + "categoryName6": { "message": "E-Commerce" }, + "categoryName7": { "message": "Fotogalerien" }, + "categoryName8": { "message": "Wikis" }, + "categoryName9": { "message": "Hosting-Panels" }, + "categoryName10": { "message": "Statistiken" }, + "categoryName11": { "message": "Blog" }, + "categoryName12": { "message": "JavaScript Frameworks" }, + "categoryName13": { "message": "Ticketsysteme" }, + "categoryName14": { "message": "Videoplayer" }, + "categoryName15": { "message": "Kommentarsystem" }, + "categoryName16": { "message": "Security" }, + "categoryName17": { "message": "Schrift Script" }, + "categoryName18": { "message": "Web Frameworks" }, + "categoryName19": { "message": "Sonstiges" }, + "categoryName20": { "message": "Editor" }, + "categoryName21": { "message": "LMS" }, + "categoryName22": { "message": "Web Server" }, + "categoryName23": { "message": "Cache Tool" }, + "categoryName24": { "message": "Rich Text Editor" }, + "categoryName25": { "message": "JavaScript Graphics" }, + "categoryName26": { "message": "Mobile Framework" }, + "categoryName27": { "message": "Programmiersprache" }, + "categoryName28": { "message": "Betriebssysteme" }, + "categoryName29": { "message": "Suchmaschinen" }, + "categoryName30": { "message": "Webmail" }, + "categoryName31": { "message": "CDN" }, + "categoryName32": { "message": "Marketing Automation" }, + "categoryName33": { "message": "Web Server Erweiterungen" }, + "categoryName34": { "message": "Datenbanken" }, + "categoryName35": { "message": "Karten" }, + "categoryName36": { "message": "Werbenetzwerke" }, + "categoryName37": { "message": "Netzwerkdienste" }, + "categoryName38": { "message": "Medienserver" }, + "categoryName39": { "message": "Web-Kameras" }, + "categoryName40": { "message": "Drucker" }, + "categoryName41": { "message": "Zahlungsverarbeiter" }, + "categoryName42": { "message": "Tag Manager" }, + "categoryName43": { "message": "Bezahlblockade" }, + "categoryName44": { "message": "CI-Systeme" }, + "categoryName45": { "message": "SCADA System" }, + "categoryName46": { "message": "Fernzugriff" }, + "categoryName47": { "message": "Entwicklungswerkzeuge" }, + "categoryName48": { "message": "Netzwerkspeicher" }, + "categoryName49": { "message": "Feedleser" }, + "categoryName50": { "message": "Dokumentmanagementsysteme" }, + "categoryName51": { "message": "Website Baukästen" }, + "categoryName52": { "message": "Live-Chat" }, + "categoryName53": { "message": "CRM" }, "categoryName54": { "message": "SEO" }, - "categoryName55": { "message": "Buchhaltung" }, - "categoryName56": { "message": "Cryptominer" }, - "categoryName57": { "message": "Statischer Seitengenerator" }, - "categoryName58": { "message": "Benutzer-Einbindung" }, - "categoryName59": { "message": "JavaScript Bibliotheken" }, - "categoryName60": { "message": "Containers" }, - "categoryName61": { "message": "SaaS" }, - "categoryName62": { "message": "PaaS" }, - "categoryName63": { "message": "IaaS" }, - "categoryName64": { "message": "Reverse Proxy" }, - "categoryName65": { "message": "Load Balancer" }, - "categoryName66": { "message": "UI Frameworks" }, - "categoryName67": { "message": "Cookie compliance" }, - "categoryName68": { "message": "Accessibility"}, - "categoryName69": { "message": "Social login"}, - "categoryName70": { "message": "SSL/TLS certificate authority"} + "categoryName55": { "message": "Buchhaltung" }, + "categoryName56": { "message": "Cryptominer" }, + "categoryName57": { "message": "Statischer Seitengenerator" }, + "categoryName58": { "message": "Benutzer-Onboarding" }, + "categoryName59": { "message": "JavaScript Bibliotheken" }, + "categoryName60": { "message": "Container" }, + "categoryName61": { "message": "SaaS" }, + "categoryName62": { "message": "PaaS" }, + "categoryName63": { "message": "IaaS" }, + "categoryName64": { "message": "Reverse Proxies" }, + "categoryName65": { "message": "Load Balancer" }, + "categoryName66": { "message": "UI Frameworks" }, + "categoryName67": { "message": "Cookie compliance" }, + "categoryName68": { "message": "Barrierefreiheit" }, + "categoryName69": { "message": "Social Login" }, + "categoryName70": { "message": "SSL/TLS certificate authority" }, + "categoryName71": { "message": "Partnerprogram" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/el/messages.json b/src/drivers/webextension/_locales/el/messages.json index d96216826..da4d31f66 100644 --- a/src/drivers/webextension/_locales/el/messages.json +++ b/src/drivers/webextension/_locales/el/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Καμία ενέργεια." }, "noAppsDetected": { "message": "Δεν ανιχνεύθηκαν εφαρμογές." }, "categoryPin": { "message": "Always show icon" }, - "termsAccept": { "message": "Accept" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Διαδικτυακό Φόρουμ" }, "categoryName3": { "message": "Διαχειριστής Βάσης Δεδομένων" }, @@ -83,6 +85,11 @@ "categoryName66": { "message": "UI Frameworks" }, "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/en/messages.json b/src/drivers/webextension/_locales/en/messages.json index 453b284bb..411afba68 100644 --- a/src/drivers/webextension/_locales/en/messages.json +++ b/src/drivers/webextension/_locales/en/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Nothing to do here." }, "noAppsDetected": { "message": "No technologies detected." }, "categoryPin": { "message": "Always show icon" }, - "termsAccept": { "message": "Accept" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Message boards" }, "categoryName3": { "message": "Database managers" }, @@ -85,6 +87,12 @@ "categoryName66": { "message": "UI frameworks" }, "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" }, + "categoryName76": { "message": "Personalization" } } diff --git a/src/drivers/webextension/_locales/es/messages.json b/src/drivers/webextension/_locales/es/messages.json index b073fb5aa..8862e3e0c 100644 --- a/src/drivers/webextension/_locales/es/messages.json +++ b/src/drivers/webextension/_locales/es/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Nada que hacer aquí." }, "noAppsDetected": { "message": "Aplicaciones no detectadas." }, "categoryPin": { "message": "Always show icon" }, - "termsAccept": { "message": "Accept" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "Gestor de Contenido" }, "categoryName2": { "message": "Foro" }, "categoryName3": { "message": "Gestor de Bases de Datos" }, @@ -55,7 +57,7 @@ "categoryName34": { "message": "Base de Datos" }, "categoryName35": { "message": "Mapa" }, "categoryName36": { "message": "Red de Publicidad" }, - "categoryName37": { "message": "Network Sevice" }, + "categoryName37": { "message": "Network Service" }, "categoryName38": { "message": "Media Server" }, "categoryName39": { "message": "Webcam" }, "categoryName40": { "message": "Printer" }, @@ -87,6 +89,11 @@ "categoryName66": { "message": "UI Frameworks" }, "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/fa/messages.json b/src/drivers/webextension/_locales/fa/messages.json index 8a221fefb..b4db06e3b 100644 --- a/src/drivers/webextension/_locales/fa/messages.json +++ b/src/drivers/webextension/_locales/fa/messages.json @@ -10,15 +10,17 @@ "optionTracking": { "message": "ارسال فن آوری های شناسایی شده به صورت ناشناس به wappalyzer.com" }, "optionThemeMode": { "message": "فعال کردن حالت سازگاری تاریک." }, "nothingToDo": { "message": "هیچ چیز برای انجام اینجا نیست." }, - "optionBadge": { "message": "Show the number of identified technologies on the icon" }, - "disableOnDomain": { "message": "Disable on this website" }, - "clearCache": { "message": "Clear cached detections" }, + "optionBadge": { "message": "نمایش تعداد فناوری های شناسایی شده روی آیکون" }, + "disableOnDomain": { "message": "غیرفعال کردن در این وبسایت" }, + "clearCache": { "message": "پاکسازی شناسایی های کش شده" }, "noAppsDetected": { "message": "هیچ فن‌آوری شناسایی نشده است." }, "categoryPin": { "message": "همیشه نماد را نشان بده" }, - "termsAccept": { "message": "قبول" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "این افزونه اطلاعات وب‌سایت‌های بازدید شده توسط شما را به صورت ناشناس ارسال می‌کند، مانند آدرس سایت و تکنولوژی‌های استفاده شده در آن سایت را ارسال می‌کند. اطلاعات بیشتر در wappalyzer.com. شما می‌توانید این افزونه را غیرفعال کنید." }, - "privacyPolicy": { "message": "Privacy policy" }, - "createAlert": { "message": "Create an alert for this website" }, + "privacyPolicy": { "message": "سیاست حفظ حریم خصوصی" }, + "createAlert": { "message": "ساخت یک هشدار برای این وبسایت" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "سیستم مدیریت محتوا" }, "categoryName2": { "message": "انجمن پیام" }, "categoryName3": { "message": "مدیریت پایگاه داده" }, @@ -34,7 +36,7 @@ "categoryName13": { "message": "ردیاب مشکل" }, "categoryName14": { "message": "پخش کننده ویدیویی" }, "categoryName15": { "message": "سیستم نظرسنجی" }, - "categoryName16": { "message": "Security" }, + "categoryName16": { "message": "امنیت" }, "categoryName17": { "message": "اسکریپ فونت" }, "categoryName18": { "message": "چارچوب وب" }, "categoryName19": { "message": "متفرقه" }, @@ -83,10 +85,15 @@ "categoryName62": { "message": "PaaS" }, "categoryName63": { "message": "IaaS" }, "categoryName64": { "message": "پروکسی معکوس" }, - "categoryName65": { "message": "Load Balancer" }, - "categoryName66": { "message": "UI Frameworks" }, + "categoryName65": { "message": "لودبالانسر" }, + "categoryName66": { "message": "فریم‌ورکهای رابط کاربری" }, "categoryName67": { "message": "Cookie compliance" }, - "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName68": { "message": "دسترسی" }, + "categoryName69": { "message": "ورود به شبکه های اجتماعی" }, + "categoryName70": { "message": "صادر کننده SSL/TLS" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/fr/messages.json b/src/drivers/webextension/_locales/fr/messages.json index 9f58d9b7f..80ce42fbd 100644 --- a/src/drivers/webextension/_locales/fr/messages.json +++ b/src/drivers/webextension/_locales/fr/messages.json @@ -1,5 +1,5 @@ { - "github": { "message": "Forker Wappalyzer sur GitHub!" }, + "github": { "message": "Forker Wappalyzer sur GitHub !" }, "noAppsDetected": { "message": "Pas d'applications détectées." }, "nothingToDo": { "message": "Rien à faire ici." }, "optionTracking": { "message": "Envoyer anonymement des rapports sur les applications détectées à wappalyzer.com pour la recherche" }, @@ -9,16 +9,18 @@ "options": { "message": "Options" }, "optionsSave": { "message": "Sauvegarder les options" }, "optionsSaved": { "message": "Sauvegardé" }, - "optionBadge": { "message": "Show the number of identified technologies on the icon" }, - "disableOnDomain": { "message": "Disable on this website" }, - "clearCache": { "message": "Clear cached detections" }, + "optionBadge": { "message": "Montrer le nombre de technologies identifiées sur l'icône" }, + "disableOnDomain": { "message": "Désactiver sur ce site web" }, + "clearCache": { "message": "Effacer les détections mises en cache" }, "twitter": { "message": "Suivre Wappalyzer sur Twitter" }, "website": { "message": "Aller sur wappalyzer.com" }, "categoryPin": { "message": " Toujours afficher l'icône" }, - "termsAccept": { "message": "Accept" }, + "termsAccept": { "message": "Je suis OK avec ça" }, + "termsDecline": { "message": "Désactiver" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, - "privacyPolicy": { "message": "Privacy policy" }, - "createAlert": { "message": "Create an alert for this website" }, + "privacyPolicy": { "message": "Politique de confidentialité" }, + "createAlert": { "message": "Créer une alerte pour ce site web" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Forum" }, "categoryName3": { "message": "Gestionnaire de base de données" }, @@ -34,7 +36,7 @@ "categoryName13": { "message": "Outil de suivi de problèmes" }, "categoryName14": { "message": "Lecteur de vidéos" }, "categoryName15": { "message": "Système de commentaires" }, - "categoryName16": { "message": "Security" }, + "categoryName16": { "message": "Sécurité" }, "categoryName17": { "message": "Script de police" }, "categoryName18": { "message": "Framework web" }, "categoryName19": { "message": "Divers" }, @@ -68,7 +70,7 @@ "categoryName47": { "message": "Outil de développement" }, "categoryName48": { "message": "Stockage réseau" }, "categoryName49": { "message": "Lecteur de flux RSS" }, - "categoryName51": { "message": "Créateur de Landing Page" }, + "categoryName51": { "message": "Créateur de \"Landing Page\"" }, "categoryName50": { "message": "Système de gestion de documents" }, "categoryName52": { "message": "Chat en direct" }, "categoryName53": { "message": "CRM" }, @@ -77,16 +79,21 @@ "categoryName56": { "message": "Crypto-mineur" }, "categoryName57": { "message": "Générateur de site statique" }, "categoryName58": { "message": "User Onboarding" }, - "categoryName59": { "message": "JavaScript Libraries" }, - "categoryName60": { "message": "Containers" }, + "categoryName59": { "message": "Librairies JavaScript" }, + "categoryName60": { "message": "Conteneurs" }, "categoryName61": { "message": "SaaS" }, "categoryName62": { "message": "PaaS" }, "categoryName63": { "message": "IaaS" }, - "categoryName64": { "message": "Reverse Proxy" }, + "categoryName64": { "message": "Proxy inversé" }, "categoryName65": { "message": "Load Balancer" }, - "categoryName66": { "message": "UI Frameworks" }, + "categoryName66": { "message": "Frameworks UI" }, "categoryName67": { "message": "Cookie compliance" }, - "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName68": { "message": "Accessibilité" }, + "categoryName69": { "message": "Connexion avec les réseaux sociaux" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Programmes affiliés" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/gl_ES/messages.json b/src/drivers/webextension/_locales/gl_ES/messages.json index fb8fa26fd..fd48da1b0 100644 --- a/src/drivers/webextension/_locales/gl_ES/messages.json +++ b/src/drivers/webextension/_locales/gl_ES/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Nada que facer por aquí." }, "noAppsDetected": { "message": "Non se identificaron aplicativos." }, "categoryPin": { "message": "Amosar sempre icono" }, - "termsAccept": { "message": "Aceptar" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "Esta extensión envía anonimamente información acerca das webs que visitas, incluindo dominio e aplicativos identificados, a wappalyzer.com. Isto pode ser desactivado nas preferencias." }, "privacyPolicy": { "message": "Política de privacidade" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Taboleiro de mensaxes" }, "categoryName3": { "message": "Xestor de base de datos" }, @@ -87,6 +89,11 @@ "categoryName66": { "message": "UI Frameworks" }, "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/gr/messages.json b/src/drivers/webextension/_locales/gr/messages.json index fcd10ad82..e46f38736 100644 --- a/src/drivers/webextension/_locales/gr/messages.json +++ b/src/drivers/webextension/_locales/gr/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Καμία ενέργεια." }, "noAppsDetected": { "message": "Δεν ανιχνεύθηκαν εφαρμογές." }, "categoryPin": { "message": "Always show icon" }, - "termsAccept": { "message": "Accept" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Διαδικτυακό Φόρουμ" }, "categoryName3": { "message": "Διαχειριστής Βάσης Δεδομένων" }, @@ -83,6 +85,11 @@ "categoryName66": { "message": "UI Frameworks" }, "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/id/messages.json b/src/drivers/webextension/_locales/id/messages.json index a0d1b2897..bee46f188 100644 --- a/src/drivers/webextension/_locales/id/messages.json +++ b/src/drivers/webextension/_locales/id/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Tak ada yang dilakukan disini." }, "noAppsDetected": { "message": "Tidak ada aplikasi yang terdeteksi." }, "categoryPin": { "message": "Always show icon" }, - "termsAccept": { "message": "Accept" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "Sistem Pengelola Konten" }, "categoryName2": { "message": "Papan Pesan" }, "categoryName3": { "message": "Pengelola Basis Data" }, @@ -87,6 +89,11 @@ "categoryName66": { "message": "UI Frameworks" }, "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/it/messages.json b/src/drivers/webextension/_locales/it/messages.json index a80798521..2a066531b 100644 --- a/src/drivers/webextension/_locales/it/messages.json +++ b/src/drivers/webextension/_locales/it/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Niente da fare qui." }, "noAppsDetected": { "message": "Nessuna applicazione rilevata." }, "categoryPin": { "message": "Always show icon" }, - "termsAccept": { "message": "Accept" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Forum" }, "categoryName3": { "message": "Gestore di Database" }, @@ -88,5 +90,10 @@ "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName70": { "message": "SSL/TLS certificate authority" }, + "categoryName71": { "message": "Affiliate program" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/ja/messages.json b/src/drivers/webextension/_locales/ja/messages.json index 656901314..c74f77a5e 100644 --- a/src/drivers/webextension/_locales/ja/messages.json +++ b/src/drivers/webextension/_locales/ja/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "ここでは特定出来ません。" }, "noAppsDetected": { "message": "技術は検出されませんでした。" }, "categoryPin": { "message": "常にアイコンを表示" }, - "termsAccept": { "message": "受諾する" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "この拡張機能は、ドメイン名や特定された技術など、アクセスしたWebサイトに関する匿名情報をwappalyzer.comに送信します。これは設定で無効にできます。" }, "privacyPolicy": { "message": "プライバシーポリシー" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "メッセージボード" }, "categoryName3": { "message": "データベースマネージャー" }, @@ -88,5 +90,10 @@ "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName70": { "message": "SSL/TLS certificate authority" }, + "categoryName71": { "message": "Affiliate program" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/ko/messages.json b/src/drivers/webextension/_locales/ko/messages.json index 86a217896..d981343b6 100644 --- a/src/drivers/webextension/_locales/ko/messages.json +++ b/src/drivers/webextension/_locales/ko/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "여기에는 할 일이 없네요." }, "noAppsDetected": { "message": "식별된 기술이 없습니다." }, "categoryPin": { "message": "항상 아이콘 보이기" }, - "termsAccept": { "message": "수락" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "이 확장 기능은 사이트의 도메인과 식별된 기술을 포함한 익명 정보를 wappalyzer.com에 전송합니다. 이 기능은 설정에서 비활성화 할 수 있습니다." }, "privacyPolicy": { "message": "개인정보처리방침" }, "createAlert": { "message": "이 웹 사이트에 대한 알림 받기" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "포럼 소프트웨어" }, "categoryName3": { "message": "데이터베이스 관리 도구" }, @@ -86,5 +88,10 @@ "categoryName67": { "message": "쿠키 동의" }, "categoryName68": { "message": "접근성" }, "categoryName69": { "message": "소셜 로그인" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/pl/messages.json b/src/drivers/webextension/_locales/pl/messages.json index d000e321f..98c81a5c7 100644 --- a/src/drivers/webextension/_locales/pl/messages.json +++ b/src/drivers/webextension/_locales/pl/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Nic tu nie ma." }, "noAppsDetected": { "message": "Nie wykryto żadnych aplikacji." }, "categoryPin": { "message": "Zawsze pokazuj tą ikonę" }, - "termsAccept": { "message": "Akceptuj" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "To rozszerzenie wysyła anonimowe informacje o stronach, które odwiedzasz, uwzględniając nazwy domen i zidentyfikowane technologie do wappalyzer.com. Opcja może zostać wyłączona w ustawieniach." }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "System zarządzania treścią" }, "categoryName2": { "message": "Forum" }, "categoryName3": { "message": "Menedżer baz danych" }, @@ -87,6 +89,11 @@ "categoryName66": { "message": "UI Frameworks" }, "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/pt/messages.json b/src/drivers/webextension/_locales/pt/messages.json index c4c41277d..38569e6ea 100644 --- a/src/drivers/webextension/_locales/pt/messages.json +++ b/src/drivers/webextension/_locales/pt/messages.json @@ -15,10 +15,12 @@ "twitter": { "message": "Seguir Wappalyzer no Twitter" }, "website": { "message": "Ir para wappalyzer.com" }, "categoryPin": { "message": "Mostrar sempre ícone" }, - "termsAccept": { "message": "Aceitar" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "Esta extensão envia informações anónimas sobre os sites que visitas, incluindo o nome de domínio e as tecnologias identificadas, para o wappalyzer.com. Isso pode ser desativado nas configurações." }, "privacyPolicy": { "message": "Políticas de Privacidade" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Fórum" }, "categoryName3": { "message": "Gestor de Base de Dados" }, @@ -87,6 +89,11 @@ "categoryName66": { "message": "UI Frameworks" }, "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/pt_BR/messages.json b/src/drivers/webextension/_locales/pt_BR/messages.json index 36f90a93f..5d36f4b55 100644 --- a/src/drivers/webextension/_locales/pt_BR/messages.json +++ b/src/drivers/webextension/_locales/pt_BR/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Nada a fazer aqui." }, "noAppsDetected": { "message": "Nenhuma tecnologia identificada." }, "categoryPin": { "message": "Sempre mostrar ícone" }, - "termsAccept": { "message": "Aceitar" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "Esta extensão envia informações anônimas sobre os sites que você visita, incluindo domínio e tecnologias identificadas para wappalyzer.com. Este comportamento pode ser desativado nas configurações." }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Fórum" }, "categoryName3": { "message": "Gestão de Banco de Dados" }, @@ -87,6 +89,11 @@ "categoryName66": { "message": "UI Frameworks" }, "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/ro/messages.json b/src/drivers/webextension/_locales/ro/messages.json index fb621d40c..7bea53eee 100644 --- a/src/drivers/webextension/_locales/ro/messages.json +++ b/src/drivers/webextension/_locales/ro/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Nimic de făcut pe pagina curentă." }, "noAppsDetected": { "message": "Nici o aplicație detectată." }, "categoryPin": { "message": "Afișează icon tot timpul" }, - "termsAccept": { "message": "Accept" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Forum de discuții" }, "categoryName3": { "message": "Manager baze de date" }, @@ -83,6 +85,11 @@ "categoryName66": { "message": "UI Frameworks" }, "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/ru/messages.json b/src/drivers/webextension/_locales/ru/messages.json index e1fd536ee..f4eff0f2a 100644 --- a/src/drivers/webextension/_locales/ru/messages.json +++ b/src/drivers/webextension/_locales/ru/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Здесь нечего делать" }, "noAppsDetected": { "message": "Не удалось определить ни одну технологию" }, "categoryPin": { "message": "Всегда отображать эту категорию иконкой"}, - "termsAccept": { "message": "Принять" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "Расширение отправляет обезличенную статистику посещенных сайтов, включая доменное имя и распознанные технологии на wappalyzer.com. Это можно отключить в настройках." }, "privacyPolicy": { "message": "Политика конфиденциальности" }, "createAlert": { "message": "Отправить жалобу на этот сайт" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Форум" }, "categoryName3": { "message": "Менеджер БД" }, @@ -85,6 +87,12 @@ "categoryName66": { "message": "UI Фреймворк" }, "categoryName67": { "message": "Соответствие cookie" }, "categoryName68": { "message": "Доступность" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Логин через социальные сети" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Партнерская программы" }, + "categoryName72": { "message": "Сервисы расписания и бронирования" }, + "categoryName73": { "message": "Опросы" }, + "categoryName74": { "message": "A/B тестирование" }, + "categoryName75": { "message": "Email" }, + "categoryName76": { "message": "Персонализация" } } diff --git a/src/drivers/webextension/_locales/sk/messages.json b/src/drivers/webextension/_locales/sk/messages.json index f5095fa72..d65f4b933 100644 --- a/src/drivers/webextension/_locales/sk/messages.json +++ b/src/drivers/webextension/_locales/sk/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Nie je tu čo robiť." }, "noAppsDetected": { "message": "Žiadne aplikácie neboli zistené." }, "categoryPin": { "message": "Always show icon" }, - "termsAccept": { "message": "Accept" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Message Board" }, "categoryName3": { "message": "Správca databáz" }, @@ -87,6 +89,11 @@ "categoryName66": { "message": "UI Frameworks" }, "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/tr/messages.json b/src/drivers/webextension/_locales/tr/messages.json index 832a58bc3..a69f197f9 100644 --- a/src/drivers/webextension/_locales/tr/messages.json +++ b/src/drivers/webextension/_locales/tr/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Burada yapacak birşey yok." }, "noAppsDetected": { "message": "Uygulamalar tespit edilemedi." }, "categoryPin": { "message": "Her zaman bu kategorinin ikonunu kullan" }, - "termsAccept": { "message": "Kabul Ediyorum" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "Bu eklenti, ziyaret ettiğiniz web site bilgilerini, alan adları ve tespit edilen teknolojiler ile beraber anonim olarak wappalyzer.com'a gönderir. Bunu, eklenti ayarlarından değiştirebilirsiniz." }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "İçerik Yönetim Sistemi" }, "categoryName2": { "message": "Mesaj Tahtası" }, "categoryName3": { "message": "Veritabanı Yöneticisi" }, @@ -87,6 +89,11 @@ "categoryName66": { "message": "UI Frameworks" }, "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs"}, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/uk/messages.json b/src/drivers/webextension/_locales/uk/messages.json index a6f7d5daa..29dc12412 100644 --- a/src/drivers/webextension/_locales/uk/messages.json +++ b/src/drivers/webextension/_locales/uk/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Тут нічого робити." }, "noAppsDetected": { "message": "Нічого не знайдено." }, "categoryPin": { "message": "Завжди показувати іконку Wappalyzer" }, - "termsAccept": { "message": "Доступ" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "Це розширення надсилає на Wapplayzer.com анонімну інформацію про відвідувані вами веб-сайти, включаючи доменні імена та визначені технології. Це можна відключити в налаштуваннях." }, "privacyPolicy": { "message": "Політика приватності" }, "createAlert": { "message": "Поскаржитись на цей сайт" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS" }, "categoryName2": { "message": "Форум" }, "categoryName3": { "message": "Менеджер БД" }, @@ -87,6 +89,11 @@ "categoryName66": { "message": "UI Каркаси" }, "categoryName67": { "message": "Відповідність файлам cookie" }, "categoryName68": { "message": "Доступність" }, - "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/uz/messages.json b/src/drivers/webextension/_locales/uz/messages.json index af62b2567..7f1989fa2 100644 --- a/src/drivers/webextension/_locales/uz/messages.json +++ b/src/drivers/webextension/_locales/uz/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "Bu yerda tekshirib bolmaydi." }, "noAppsDetected": { "message": "Hech qanday dastur aniqlanmadi." }, "categoryPin": { "message": "Always show icon" }, - "termsAccept": { "message": "Accept" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "This extension sends anonymous information about websites you visit, including domain name and identified technologies, to wappalyzer.com. This can be disabled in the settings." }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "CMS (KBT)" }, "categoryName2": { "message": "Forum" }, "categoryName3": { "message": "MB boshqaruvi" }, @@ -86,7 +88,12 @@ "categoryName65": { "message": "Load Balancer" }, "categoryName66": { "message": "UI Frameworks" }, "categoryName67": { "message": "Cookie compliance" }, - "categoryName68": { "message": "Accessibility"}, - "categoryName69": { "message": "Social login"}, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName68": { "message": "Accessibility" }, + "categoryName69": { "message": "Social logins" }, + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/zh_CN/messages.json b/src/drivers/webextension/_locales/zh_CN/messages.json index 8a37b95ca..280d0bdf3 100644 --- a/src/drivers/webextension/_locales/zh_CN/messages.json +++ b/src/drivers/webextension/_locales/zh_CN/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "这里无事可做。" }, "noAppsDetected": { "message": "未检测到任何技术。" }, "categoryPin": { "message": "总是显示图标" }, - "termsAccept": { "message": "接受" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "此扩展程序会匿名发送您访问的网站信息至 wappalyzer.com,包含域名和检测到的技术。这可以在设置中禁用。" }, "privacyPolicy": { "message": "隐私政策" }, "createAlert": { "message": "为该网站创建提醒" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "内容管理系统(CMS)" }, "categoryName2": { "message": "信息板" }, "categoryName3": { "message": "数据库管理器" }, @@ -84,7 +86,10 @@ "categoryName65": { "message": "负载均衡" }, "categoryName66": { "message": "用户界面(UI)框架" }, "categoryName67": { "message": "Cookie 合规" }, - "categoryName68": { "message": "辅助功能"}, - "categoryName69": { "message": "社交登录"}, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName68": { "message": "辅助功能" }, + "categoryName69": { "message": "社交登录" }, + "categoryName70": { "message": "SSL/TLS certificate authority" }, + "categoryName71": { "message": "Affiliate program" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/_locales/zh_TW/messages.json b/src/drivers/webextension/_locales/zh_TW/messages.json index 575a241f0..3a1950994 100644 --- a/src/drivers/webextension/_locales/zh_TW/messages.json +++ b/src/drivers/webextension/_locales/zh_TW/messages.json @@ -15,10 +15,12 @@ "nothingToDo": { "message": "這裡什麼也沒有。" }, "noAppsDetected": { "message": "未識別到技術。" }, "categoryPin": { "message": "永遠顯示圖示" }, - "termsAccept": { "message": "接受" }, + "termsAccept": { "message": "I'm ok with that" }, + "termsDecline": { "message": "Disable" }, "termsContent": { "message": "這個擴充功能將你所造訪網站的網域名稱和識別到的技術等資訊,匿名傳送至 wappalyzer.com。你可以在選項中停用。" }, "privacyPolicy": { "message": "Privacy policy" }, "createAlert": { "message": "Create an alert for this website" }, + "leadLists": { "message": "Lead generation tools" }, "categoryName1": { "message": "內容管理系統(CMS)" }, "categoryName2": { "message": "留言板/討論區" }, "categoryName3": { "message": "資料庫管理" }, @@ -88,5 +90,10 @@ "categoryName67": { "message": "Cookie compliance" }, "categoryName68": { "message": "Accessibility" }, "categoryName69": { "message": "Social login" }, - "categoryName70": { "message": "SSL/TLS certificate authority" } + "categoryName70": { "message": "SSL/TLS certificate authorities" }, + "categoryName71": { "message": "Affiliate programs" }, + "categoryName72": { "message": "Appointment scheduling" }, + "categoryName73": { "message": "Surveys" }, + "categoryName74": { "message": "A/B testing" }, + "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/css/styles.css b/src/drivers/webextension/css/styles.css index 45bd52ab3..7546e8946 100644 --- a/src/drivers/webextension/css/styles.css +++ b/src/drivers/webextension/css/styles.css @@ -44,22 +44,51 @@ a:hover { border-bottom: 1px solid var(--color-secondary); display: flex; height: 4.5rem; + justify-content: space-between; + padding: 0 1.5rem; } .header__logo { height: 2.5rem; - margin: .5rem 1.5rem 0 1.5rem; + margin-top: .5rem; } .header__logo--dark { display: none; } +.header__icon { + color: var(--color-primary); + cursor: pointer; + height: 1.1rem; + margin-left: 1rem; + vertical-align: middle; + width: 1.1rem; +} + +.header__switch { + height: 1.5rem; + width: 1.5rem; +} + +.header__switch--hidden { + display: none; +} + +.header__switch--disabled { + color: var(--color-text); +} + +.spacer { + flex-grow:1; +} + .footer { align-items: center; background: #fff; bottom: 0; border-top: 1px solid var(--color-secondary); + font-size: .8rem; height: 3rem; display: flex; padding: 0 1.5rem; @@ -67,46 +96,26 @@ a:hover { width: 100%; } -.alerts { +.footer__links { white-space: nowrap; } -.alerts--hidden { - visibility: hidden; +.footer__link { + display: inline-block; + padding: 0 .3rem; } -.alerts__icon { - color: var(--color-primary); - height: 1.1rem; - margin-right: .5rem; - vertical-align: text-bottom; - width: 1.1rem; +.footer__link:first-child { + padding-left: 0; } -.spacer { - flex-grow:1; -} - -.footer__icon { - color: var(--color-primary); - cursor: pointer; - height: 1.1rem; - margin-left: 1rem; - vertical-align: middle; - width: 1.1rem; -} - -.footer__switch { - height: 1.5rem; - width: 1.5rem; -} - -.footer__switch--hidden { - display: none; +.footer__link a { + border-bottom: 1px solid var(--color-secondary); + text-decoration: none; } -.footer__switch--disabled { - color: var(--color-text); +.footer__link a:hover { + border-bottom: 1px solid var(--color-primary); } .detections { @@ -195,6 +204,21 @@ a:hover { .technology__link { color: var(--color-text); + display: block; + width: 100%; +} + +.technology__link .technology__name { + border-bottom: 1px solid var(--color-secondary); +} + +.technology__link:hover { + text-decoration: none; +} + +.technology__link:hover .technology__name { + border-bottom: 1px solid var(--color-primary); + color: var(--color-primary); } .technology__confidence { @@ -208,7 +232,7 @@ a:hover { border-radius: 3px; font-size: .7rem; padding: .1rem .3rem; - margin-left: .4rem; + margin-left: .2rem; vertical-align: middle; } @@ -218,7 +242,7 @@ a:hover { flex-direction: column; justify-content: center; padding: 1.5rem 1.5rem 1rem 1.5rem; - height: 12rem; + height: 14rem; width: 36rem; } @@ -244,18 +268,25 @@ a:hover { width: 80%; } -.terms__accept { +.terms__buttons { +} + +.terms__button { background-color: #4608ad; border: none; border-radius: 3px; color: white; cursor: pointer; font-size: .9rem; - padding: .8rem 3rem; + padding: 0 2rem; + margin: 0 .2rem; + height: 3rem; } -.terms__accept:hover { - background-color: #4107a1; +.terms__button--decline { + background-color: white; + border: 1px solid #4608ad; + color: #4608ad; } .terms__privacy { @@ -297,6 +328,14 @@ a:hover { display: inline-block; } + .theme-mode .header__settings { + color: var(--color-text-dark); + } + + .theme-mode .header__icon { + color: #fff; + } + .theme-mode .category__link { color: #fff; } @@ -305,6 +344,11 @@ a:hover { color: #fff } + .theme-mode .technology__link:hover .technology__name { + border-bottom: 1px solid var(--color-text-dark); + color: var(--color-text-dark); + } + .theme-mode .technology__confidence { } @@ -317,11 +361,11 @@ a:hover { border-color: var(--color-secondary-dark) } - .theme-mode .footer__settings { - color: var(--color-text-dark); + .theme-mode .footer__open-in-new { + color: #fff; } - .theme-mode .alerts__icon { - color:var(--color-text-dark); + .theme-mode .technology__open-in-new { + color: #fff; } } diff --git a/src/drivers/webextension/html/options.html b/src/drivers/webextension/html/options.html index 3dfc531d4..b07b09cab 100644 --- a/src/drivers/webextension/html/options.html +++ b/src/drivers/webextension/html/options.html @@ -43,7 +43,7 @@   - + diff --git a/src/drivers/webextension/html/popup.html b/src/drivers/webextension/html/popup.html index 2d3d2b84e..a7639f56f 100644 --- a/src/drivers/webextension/html/popup.html +++ b/src/drivers/webextension/html/popup.html @@ -13,10 +13,27 @@
- + + +
+ + + + + + + + + + + + + + +
@@ -26,9 +43,12 @@
- +
+ + +
- +
@@ -51,43 +71,32 @@
+ - +   - -   - + +   + -   +   +