privalint 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.de.md +89 -0
  2. package/README.md +29 -1
  3. package/dist/cli.js +8 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/i18n.d.ts +5 -0
  6. package/dist/i18n.js +3 -0
  7. package/dist/i18n.js.map +1 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +2 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/legal.d.ts +10 -0
  12. package/dist/legal.js +99 -0
  13. package/dist/legal.js.map +1 -0
  14. package/dist/reporters/markdown.js +14 -12
  15. package/dist/reporters/markdown.js.map +1 -1
  16. package/dist/reporters/messages.d.ts +21 -0
  17. package/dist/reporters/messages.js +62 -0
  18. package/dist/reporters/messages.js.map +1 -0
  19. package/dist/reporters/sarif.js +7 -1
  20. package/dist/reporters/sarif.js.map +1 -1
  21. package/dist/reporters/shared.d.ts +1 -3
  22. package/dist/reporters/shared.js +0 -17
  23. package/dist/reporters/shared.js.map +1 -1
  24. package/dist/reporters/text.js +17 -13
  25. package/dist/reporters/text.js.map +1 -1
  26. package/dist/rules/google-fonts.js +26 -9
  27. package/dist/rules/google-fonts.js.map +1 -1
  28. package/dist/rules/impressum-link.js +21 -8
  29. package/dist/rules/impressum-link.js.map +1 -1
  30. package/dist/rules/links.d.ts +2 -0
  31. package/dist/rules/links.js +2 -0
  32. package/dist/rules/links.js.map +1 -1
  33. package/dist/rules/pre-consent-trackers.js +20 -7
  34. package/dist/rules/pre-consent-trackers.js.map +1 -1
  35. package/dist/rules/privacy-policy-link.js +12 -5
  36. package/dist/rules/privacy-policy-link.js.map +1 -1
  37. package/dist/rules/reject-option.js +27 -7
  38. package/dist/rules/reject-option.js.map +1 -1
  39. package/dist/rules/third-party-cdns.js +10 -3
  40. package/dist/rules/third-party-cdns.js.map +1 -1
  41. package/dist/rules/third-party-embeds.js +10 -3
  42. package/dist/rules/third-party-embeds.js.map +1 -1
  43. package/dist/rules/tracker-hits.d.ts +4 -2
  44. package/dist/rules/tracker-hits.js +14 -3
  45. package/dist/rules/tracker-hits.js.map +1 -1
  46. package/dist/rules/trackers-after-reject.js +24 -12
  47. package/dist/rules/trackers-after-reject.js.map +1 -1
  48. package/dist/scanner.d.ts +6 -0
  49. package/dist/scanner.js +10 -2
  50. package/dist/scanner.js.map +1 -1
  51. package/dist/types.d.ts +8 -0
  52. package/package.json +11 -6
@@ -1,5 +1,6 @@
1
1
  import { styleText } from 'node:util';
2
- import { countBySeverity, describeConsent, groupByPage, plural } from './shared.js';
2
+ import { messagesFor } from './messages.js';
3
+ import { countBySeverity, groupByPage } from './shared.js';
3
4
  const MARKS = {
4
5
  error: ['✖', 'red'],
5
6
  warning: ['▲', 'yellow'],
@@ -7,45 +8,48 @@ const MARKS = {
7
8
  };
8
9
  export function formatText(result, color = false) {
9
10
  const paint = (style, text) => (color ? styleText(style, text) : text);
11
+ const m = messagesFor(result.lang);
10
12
  const lines = [];
11
13
  for (const [pageUrl, findings] of groupByPage(result.findings)) {
12
14
  lines.push('', paint('underline', pageUrl));
13
15
  for (const finding of findings) {
14
- lines.push(...formatFinding(finding, paint));
16
+ lines.push(...formatFinding(finding, paint, m));
15
17
  }
16
18
  }
17
19
  const failed = result.pages.filter((page) => page.error !== undefined);
18
20
  if (failed.length > 0) {
19
- lines.push('', paint('yellow', `Could not scan ${plural(failed.length, 'page')}:`));
21
+ lines.push('', paint('yellow', m.couldNotScan(failed.length)));
20
22
  for (const page of failed) {
21
23
  lines.push(` ${page.url} ${paint('dim', page.error ?? '')}`);
22
24
  }
23
25
  }
24
26
  const counts = countBySeverity(result.findings);
25
- const pages = plural(result.pages.length, 'page');
26
- const seconds = (result.durationMs / 1000).toFixed(1);
27
+ const pages = m.pages(result.pages.length);
28
+ const duration = m.duration(result.durationMs);
27
29
  lines.push('');
28
- const consent = describeConsent(result.consent);
29
- if (consent !== undefined) {
30
- lines.push(paint('dim', consent));
30
+ if (result.consent !== undefined) {
31
+ lines.push(paint('dim', m.consent(result.consent)));
31
32
  }
32
33
  if (result.findings.length === 0) {
33
- lines.push(paint('green', `✔ No problems found on ${pages} (${seconds}s).`));
34
+ lines.push(paint('green', `✔ ${m.noProblems(pages, duration)}`));
34
35
  }
35
36
  else {
36
- lines.push(`${paint('red', plural(counts.error, 'error'))}, ${paint('yellow', plural(counts.warning, 'warning'))} on ${pages} (${seconds}s).`);
37
+ lines.push(`${paint('red', m.errors(counts.error))}, ${paint('yellow', m.warnings(counts.warning))} ${m.on} ${pages} (${duration}).`);
37
38
  }
38
- lines.push(paint('dim', 'Automated checks only. A clean result is not proof of GDPR compliance.'));
39
+ lines.push(paint('dim', m.disclaimer));
39
40
  return lines.join('\n');
40
41
  }
41
- function formatFinding(finding, paint) {
42
+ function formatFinding(finding, paint, m) {
42
43
  const [mark, style] = MARKS[finding.severity];
43
44
  const lines = [` ${paint(style, mark)} ${finding.message} ${paint('dim', finding.ruleId)}`];
44
45
  for (const item of finding.evidence) {
45
46
  lines.push(paint('dim', ` ${item}`));
46
47
  }
47
48
  if (finding.fix) {
48
- lines.push(` ${paint('cyan', 'fix:')} ${finding.fix}`);
49
+ lines.push(` ${paint('cyan', m.fix)} ${finding.fix}`);
50
+ }
51
+ if (finding.references !== undefined && finding.references.length > 0) {
52
+ lines.push(paint('dim', ` ${m.legalBasis} ${finding.references.join('; ')}`));
49
53
  }
50
54
  return lines;
51
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/reporters/text.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/reporters/text.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,19 @@
1
+ import { say } from '../i18n.js';
1
2
  import { hostMatches, safeHost } from '../url.js';
2
3
  const ID = 'google-fonts';
3
4
  const GOOGLE_FONT_HOSTS = ['fonts.googleapis.com', 'fonts.gstatic.com'];
4
5
  /** dns-prefetch only resolves the name; these hints open a real connection. */
5
6
  const CONNECTING_HINTS = new Set(['preconnect', 'preload', 'prefetch', 'stylesheet']);
6
- const FIX = 'Self-host the font files. In Next.js, next/font/google downloads fonts at build time and ' +
7
- 'serves them from your own domain. Elsewhere, install the family from @fontsource or download ' +
8
- 'it via google-webfonts-helper, then remove every reference to fonts.googleapis.com and ' +
9
- 'fonts.gstatic.com.';
7
+ const FIX = {
8
+ en: 'Self-host the font files. In Next.js, next/font/google downloads fonts at build time and ' +
9
+ 'serves them from your own domain. Elsewhere, install the family from @fontsource or download ' +
10
+ 'it via google-webfonts-helper, then remove every reference to fonts.googleapis.com and ' +
11
+ 'fonts.gstatic.com.',
12
+ de: 'Die Schriftdateien selbst hosten. In Next.js lädt next/font/google die Schriften beim Build ' +
13
+ 'herunter und liefert sie von der eigenen Domain aus. Sonst die Schrift über @fontsource ' +
14
+ 'installieren oder mit dem google-webfonts-helper herunterladen und danach alle Verweise auf ' +
15
+ 'fonts.googleapis.com und fonts.gstatic.com entfernen.',
16
+ };
10
17
  const isGoogleFontUrl = (url) => {
11
18
  const host = safeHost(url);
12
19
  return host !== undefined && GOOGLE_FONT_HOSTS.some((domain) => hostMatches(host, domain));
@@ -17,17 +24,21 @@ export const googleFonts = {
17
24
  description: "Loading fonts from Google's CDN sends every visitor's IP address to Google without " +
18
25
  'consent. LG München I awarded a visitor damages for this in 2022.',
19
26
  references: ['Art. 6(1) GDPR', 'LG München I, 20.01.2022, Az. 3 O 17493/20'],
20
- check({ page }) {
27
+ check({ page, lang }) {
21
28
  const requests = page.requests.filter((request) => isGoogleFontUrl(request.url));
22
29
  if (requests.length > 0) {
30
+ const count = requests.length;
23
31
  return [
24
32
  {
25
33
  ruleId: ID,
26
34
  severity: 'error',
27
- message: `Fonts are loaded from Google (${requests.length} request${requests.length === 1 ? '' : 's'}).`,
35
+ message: say(lang, {
36
+ en: `Fonts are loaded from Google (${count} request${count === 1 ? '' : 's'}).`,
37
+ de: `Schriften werden von Google geladen (${count} ${count === 1 ? 'Anfrage' : 'Anfragen'}).`,
38
+ }),
28
39
  pageUrl: page.finalUrl,
29
40
  evidence: [...new Set(requests.map((request) => request.url))].slice(0, 5),
30
- fix: FIX,
41
+ fix: say(lang, FIX),
31
42
  },
32
43
  ];
33
44
  }
@@ -38,10 +49,16 @@ export const googleFonts = {
38
49
  {
39
50
  ruleId: ID,
40
51
  severity: 'warning',
41
- message: 'The page preconnects to Google Fonts. No font was fetched, but the browser still opens a connection and transmits the IP address.',
52
+ message: say(lang, {
53
+ en: 'The page preconnects to Google Fonts. No font was fetched, but the browser still opens a connection and transmits the IP address.',
54
+ de: 'Die Seite baut vorab eine Verbindung zu Google Fonts auf. Es wurde keine Schrift geladen, aber der Browser öffnet trotzdem eine Verbindung und überträgt die IP-Adresse.',
55
+ }),
42
56
  pageUrl: page.finalUrl,
43
57
  evidence: hints.map((hint) => `<link rel="${hint.rel}" href="${hint.href}">`),
44
- fix: 'Remove the leftover <link> tags pointing to Google Fonts.',
58
+ fix: say(lang, {
59
+ en: 'Remove the leftover <link> tags pointing to Google Fonts.',
60
+ de: 'Die übrig gebliebenen <link>-Tags zu Google Fonts entfernen.',
61
+ }),
45
62
  },
46
63
  ];
47
64
  }
@@ -1 +1 @@
1
- {"version":3,"file":"google-fonts.js","sourceRoot":"","sources":["../../src/rules/google-fonts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAElD,MAAM,EAAE,GAAG,cAAc,CAAC;AAC1B,MAAM,iBAAiB,GAAG,CAAC,sBAAsB,EAAE,mBAAmB,CAAC,CAAC;AAExE,+EAA+E;AAC/E,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;AAEtF,MAAM,GAAG,GACP,2FAA2F;IAC3F,+FAA+F;IAC/F,yFAAyF;IACzF,oBAAoB,CAAC;AAEvB,MAAM,eAAe,GAAG,CAAC,GAAW,EAAW,EAAE;IAC/C,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,IAAI,KAAK,SAAS,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;AAC7F,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAS;IAC/B,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,yCAAyC;IAChD,WAAW,EACT,qFAAqF;QACrF,mEAAmE;IACrE,UAAU,EAAE,CAAC,gBAAgB,EAAE,4CAA4C,CAAC;IAC5E,KAAK,CAAC,EAAE,IAAI,EAAE;QACZ,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QACjF,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO;gBACL;oBACE,MAAM,EAAE,EAAE;oBACV,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE,iCAAiC,QAAQ,CAAC,MAAM,WAAW,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI;oBACxG,OAAO,EAAE,IAAI,CAAC,QAAQ;oBACtB,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;oBAC1E,GAAG,EAAE,GAAG;iBACT;aACF,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CACrC,CAAC,IAAI,EAAE,EAAE,CACP,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;YAC1B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CACjE,CAAC;QACF,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO;gBACL;oBACE,MAAM,EAAE,EAAE;oBACV,QAAQ,EAAE,SAAS;oBACnB,OAAO,EACL,mIAAmI;oBACrI,OAAO,EAAE,IAAI,CAAC,QAAQ;oBACtB,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,IAAI,CAAC,GAAG,WAAW,IAAI,CAAC,IAAI,IAAI,CAAC;oBAC7E,GAAG,EAAE,2DAA2D;iBAC/C;aACpB,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,CAAC;IACZ,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"google-fonts.js","sourceRoot":"","sources":["../../src/rules/google-fonts.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { linkMatches } from './links.js';
1
+ import { say } from '../i18n.js';
2
+ import { linkMatches, linksChecked } from './links.js';
2
3
  const ID = 'impressum-link';
3
4
  /**
4
5
  * Link texts and URL slugs used for a legal notice across the EU.
@@ -14,7 +15,7 @@ export const impressumLink = {
14
15
  'directly reachable and always available, which in practice means a link on every page. ' +
15
16
  'Disable this rule for purely private sites or sites outside its scope.',
16
17
  references: ['§ 5 DDG', 'BGH, 20.07.2006, Az. I ZR 228/03'],
17
- check({ page }) {
18
+ check({ page, lang }) {
18
19
  if (page.links.some(linkMatches(LEGAL_NOTICE))) {
19
20
  return [];
20
21
  }
@@ -24,10 +25,16 @@ export const impressumLink = {
24
25
  {
25
26
  ruleId: ID,
26
27
  severity: 'info',
27
- message: 'No direct Impressum link, only a contact link.',
28
+ message: say(lang, {
29
+ en: 'No direct Impressum link, only a contact link.',
30
+ de: 'Kein direkter Impressum-Link, nur ein Kontakt-Link.',
31
+ }),
28
32
  pageUrl: page.finalUrl,
29
- evidence: [`contact link: ${contact.href}`],
30
- fix: 'Two clicks via "Kontakt" can be enough if the Impressum is clearly linked from there (BGH I ZR 228/03), but a direct "Impressum" link next to it is safer.',
33
+ evidence: [say(lang, { en: 'contact link: ', de: 'Kontakt-Link: ' }) + contact.href],
34
+ fix: say(lang, {
35
+ en: 'Two clicks via "Kontakt" can be enough if the Impressum is clearly linked from there (BGH I ZR 228/03), but a direct "Impressum" link next to it is safer.',
36
+ de: 'Zwei Klicks über „Kontakt“ können reichen, wenn das Impressum dort klar verlinkt ist (BGH I ZR 228/03). Sicherer ist ein direkter Link „Impressum“ daneben.',
37
+ }),
31
38
  },
32
39
  ];
33
40
  }
@@ -35,10 +42,16 @@ export const impressumLink = {
35
42
  {
36
43
  ruleId: ID,
37
44
  severity: 'warning',
38
- message: 'No link to an Impressum or legal notice was found on this page.',
45
+ message: say(lang, {
46
+ en: 'No link to an Impressum or legal notice was found on this page.',
47
+ de: 'Auf dieser Seite wurde kein Link zu einem Impressum gefunden.',
48
+ }),
39
49
  pageUrl: page.finalUrl,
40
- evidence: [`${page.links.length} links checked`],
41
- fix: 'Add a link labelled "Impressum" to a part of the layout that appears on every page, usually the footer.',
50
+ evidence: [linksChecked(page.links.length, lang)],
51
+ fix: say(lang, {
52
+ en: 'Add a link labelled "Impressum" to a part of the layout that appears on every page, usually the footer.',
53
+ de: 'Einen Link „Impressum“ in einen Bereich einbauen, der auf jeder Seite erscheint, meist den Footer.',
54
+ }),
42
55
  },
43
56
  ];
44
57
  },
@@ -1 +1 @@
1
- {"version":3,"file":"impressum-link.js","sourceRoot":"","sources":["../../src/rules/impressum-link.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,EAAE,GAAG,gBAAgB,CAAC;AAE5B;;;;GAIG;AACH,MAAM,YAAY,GAChB,6KAA6K,CAAC;AAEhL,MAAM,OAAO,GAAG,kBAAkB,CAAC;AAEnC,MAAM,CAAC,MAAM,aAAa,GAAS;IACjC,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,yBAAyB;IAChC,WAAW,EACT,wFAAwF;QACxF,yFAAyF;QACzF,wEAAwE;IAC1E,UAAU,EAAE,CAAC,SAAS,EAAE,kCAAkC,CAAC;IAC3D,KAAK,CAAC,EAAE,IAAI,EAAE;QACZ,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;QACtD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO;gBACL;oBACE,MAAM,EAAE,EAAE;oBACV,QAAQ,EAAE,MAAM;oBAChB,OAAO,EAAE,gDAAgD;oBACzD,OAAO,EAAE,IAAI,CAAC,QAAQ;oBACtB,QAAQ,EAAE,CAAC,iBAAiB,OAAO,CAAC,IAAI,EAAE,CAAC;oBAC3C,GAAG,EAAE,4JAA4J;iBAClK;aACF,CAAC;QACJ,CAAC;QAED,OAAO;YACL;gBACE,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,iEAAiE;gBAC1E,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,gBAAgB,CAAC;gBAChD,GAAG,EAAE,yGAAyG;aAC/G;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"impressum-link.js","sourceRoot":"","sources":["../../src/rules/impressum-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEvD,MAAM,EAAE,GAAG,gBAAgB,CAAC;AAE5B;;;;GAIG;AACH,MAAM,YAAY,GAChB,6KAA6K,CAAC;AAEhL,MAAM,OAAO,GAAG,kBAAkB,CAAC;AAEnC,MAAM,CAAC,MAAM,aAAa,GAAS;IACjC,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,yBAAyB;IAChC,WAAW,EACT,wFAAwF;QACxF,yFAAyF;QACzF,wEAAwE;IAC1E,UAAU,EAAE,CAAC,SAAS,EAAE,kCAAkC,CAAC;IAC3D,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE;QAClB,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;QACtD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO;gBACL;oBACE,MAAM,EAAE,EAAE;oBACV,QAAQ,EAAE,MAAM;oBAChB,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE;wBACjB,EAAE,EAAE,gDAAgD;wBACpD,EAAE,EAAE,qDAAqD;qBAC1D,CAAC;oBACF,OAAO,EAAE,IAAI,CAAC,QAAQ;oBACtB,QAAQ,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,gBAAgB,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;oBACpF,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE;wBACb,EAAE,EAAE,4JAA4J;wBAChK,EAAE,EAAE,6JAA6J;qBAClK,CAAC;iBACH;aACF,CAAC;QACJ,CAAC;QAED,OAAO;YACL;gBACE,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE;oBACjB,EAAE,EAAE,iEAAiE;oBACrE,EAAE,EAAE,+DAA+D;iBACpE,CAAC;gBACF,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,QAAQ,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACjD,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE;oBACb,EAAE,EAAE,yGAAyG;oBAC7G,EAAE,EAAE,oGAAoG;iBACzG,CAAC;aACH;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -1,6 +1,8 @@
1
+ import { type Lang } from '../i18n.js';
1
2
  import type { PageLink } from '../types.js';
2
3
  /**
3
4
  * Matches a link by its visible text or its URL path. Only the path counts,
4
5
  * so a domain like impressum-example.de doesn't satisfy every page.
5
6
  */
6
7
  export declare const linkMatches: (pattern: RegExp) => (link: PageLink) => boolean;
8
+ export declare const linksChecked: (count: number, lang: Lang) => string;
@@ -1,8 +1,10 @@
1
+ import { say } from '../i18n.js';
1
2
  /**
2
3
  * Matches a link by its visible text or its URL path. Only the path counts,
3
4
  * so a domain like impressum-example.de doesn't satisfy every page.
4
5
  */
5
6
  export const linkMatches = (pattern) => (link) => pattern.test(link.text) || pattern.test(pathOf(link.href));
7
+ export const linksChecked = (count, lang) => say(lang, { en: `${count} links checked`, de: `${count} Links geprüft` });
6
8
  function pathOf(href) {
7
9
  try {
8
10
  return decodeURIComponent(new URL(href).pathname);
@@ -1 +1 @@
1
- {"version":3,"file":"links.js","sourceRoot":"","sources":["../../src/rules/links.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC,CAAC,IAAc,EAAE,EAAE,CACjE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAE7D,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,OAAO,kBAAkB,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"links.js","sourceRoot":"","sources":["../../src/rules/links.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,GAAG,EAAE,MAAM,YAAY,CAAC;AAG5C;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC,CAAC,IAAc,EAAE,EAAE,CACjE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAE7D,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,IAAU,EAAE,EAAE,CACxD,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,KAAK,gBAAgB,EAAE,EAAE,EAAE,GAAG,KAAK,gBAAgB,EAAE,CAAC,CAAC;AAE5E,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,OAAO,kBAAkB,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -1,4 +1,5 @@
1
- import { findTrackerHits } from './tracker-hits.js';
1
+ import { say } from '../i18n.js';
2
+ import { categoryLabel, findTrackerHits } from './tracker-hits.js';
2
3
  const ID = 'pre-consent-trackers';
3
4
  export const preConsentTrackers = {
4
5
  id: ID,
@@ -12,19 +13,31 @@ export const preConsentTrackers = {
12
13
  'Art. 6(1)(a) GDPR',
13
14
  'CJEU C-673/17 (Planet49)',
14
15
  ],
15
- check({ page }) {
16
- return findTrackerHits(page.requests, page.cookies).map(({ tracker, evidence }) => {
16
+ check({ page, lang }) {
17
+ const hits = findTrackerHits(page.requests, page.cookies, lang);
18
+ return hits.map(({ tracker, evidence }) => {
19
+ const { vendor } = tracker;
17
20
  const isTagManager = tracker.category === 'tag-manager';
21
+ const category = categoryLabel(tracker.category, lang);
18
22
  return {
19
23
  ruleId: ID,
20
24
  severity: isTagManager ? 'warning' : 'error',
21
25
  message: isTagManager
22
- ? `${tracker.vendor} loads before consent and sends the visitor's IP address to a third party.`
23
- : `${tracker.vendor} (${tracker.category}) runs before the visitor has given consent.`,
26
+ ? say(lang, {
27
+ en: `${vendor} loads before consent and sends the visitor's IP address to a third party.`,
28
+ de: `${vendor} wird vor der Einwilligung geladen und überträgt die IP-Adresse an einen Dritten.`,
29
+ })
30
+ : say(lang, {
31
+ en: `${vendor} (${category}) runs before the visitor has given consent.`,
32
+ de: `${vendor} (${category}) läuft, bevor eine Einwilligung vorliegt.`,
33
+ }),
24
34
  pageUrl: page.finalUrl,
25
- subject: tracker.vendor,
35
+ subject: vendor,
26
36
  evidence,
27
- fix: `Only load ${tracker.vendor} after opt-in, for example by letting your consent manager inject it or by using it in a consent mode that sends no requests until consent is given.`,
37
+ fix: say(lang, {
38
+ en: `Only load ${vendor} after opt-in, for example by letting your consent manager inject it or by using it in a consent mode that sends no requests until consent is given.`,
39
+ de: `${vendor} erst nach der Einwilligung laden, zum Beispiel über das Consent-Tool, oder einen Consent-Modus nutzen, der vorher keine Anfragen sendet.`,
40
+ }),
28
41
  };
29
42
  });
30
43
  },
@@ -1 +1 @@
1
- {"version":3,"file":"pre-consent-trackers.js","sourceRoot":"","sources":["../../src/rules/pre-consent-trackers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,EAAE,GAAG,sBAAsB,CAAC;AAElC,MAAM,CAAC,MAAM,kBAAkB,GAAS;IACtC,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,6BAA6B;IACpC,WAAW,EACT,yFAAyF;QACzF,2FAA2F;QAC3F,mDAAmD;IACrD,UAAU,EAAE;QACV,mBAAmB;QACnB,8BAA8B;QAC9B,mBAAmB;QACnB,0BAA0B;KAC3B;IACD,KAAK,CAAC,EAAE,IAAI,EAAE;QACZ,OAAO,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAW,EAAE;YACzF,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,KAAK,aAAa,CAAC;YACxD,OAAO;gBACL,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO;gBAC5C,OAAO,EAAE,YAAY;oBACnB,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,4EAA4E;oBAC/F,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,QAAQ,8CAA8C;gBACxF,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,OAAO,EAAE,OAAO,CAAC,MAAM;gBACvB,QAAQ;gBACR,GAAG,EAAE,aAAa,OAAO,CAAC,MAAM,sJAAsJ;aACvL,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"pre-consent-trackers.js","sourceRoot":"","sources":["../../src/rules/pre-consent-trackers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEnE,MAAM,EAAE,GAAG,sBAAsB,CAAC;AAElC,MAAM,CAAC,MAAM,kBAAkB,GAAS;IACtC,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,6BAA6B;IACpC,WAAW,EACT,yFAAyF;QACzF,2FAA2F;QAC3F,mDAAmD;IACrD,UAAU,EAAE;QACV,mBAAmB;QACnB,8BAA8B;QAC9B,mBAAmB;QACnB,0BAA0B;KAC3B;IACD,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE;QAClB,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAChE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAW,EAAE;YACjD,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;YAC3B,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,KAAK,aAAa,CAAC;YACxD,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACvD,OAAO;gBACL,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO;gBAC5C,OAAO,EAAE,YAAY;oBACnB,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE;wBACR,EAAE,EAAE,GAAG,MAAM,4EAA4E;wBACzF,EAAE,EAAE,GAAG,MAAM,mFAAmF;qBACjG,CAAC;oBACJ,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE;wBACR,EAAE,EAAE,GAAG,MAAM,KAAK,QAAQ,8CAA8C;wBACxE,EAAE,EAAE,GAAG,MAAM,KAAK,QAAQ,4CAA4C;qBACvE,CAAC;gBACN,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,OAAO,EAAE,MAAM;gBACf,QAAQ;gBACR,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE;oBACb,EAAE,EAAE,aAAa,MAAM,sJAAsJ;oBAC7K,EAAE,EAAE,GAAG,MAAM,2IAA2I;iBACzJ,CAAC;aACH,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
@@ -1,4 +1,5 @@
1
- import { linkMatches } from './links.js';
1
+ import { say } from '../i18n.js';
2
+ import { linkMatches, linksChecked } from './links.js';
2
3
  const ID = 'privacy-policy-link';
3
4
  const PRIVACY_POLICY = /datenschutz|privacy|data[\s_-]?protection|confidentialit|privacidad|protezione[\s_-]?dei[\s_-]?dati|privacybeleid|polityka[\s_-]?prywatno/i;
4
5
  export const privacyPolicyLink = {
@@ -7,7 +8,7 @@ export const privacyPolicyLink = {
7
8
  description: 'Visitors must be told how their data is processed at the time it is collected. Since every ' +
8
9
  'page view already involves server logs, the privacy policy has to be reachable from every page.',
9
10
  references: ['Art. 12 GDPR', 'Art. 13 GDPR'],
10
- check({ page }) {
11
+ check({ page, lang }) {
11
12
  if (page.links.some(linkMatches(PRIVACY_POLICY))) {
12
13
  return [];
13
14
  }
@@ -15,10 +16,16 @@ export const privacyPolicyLink = {
15
16
  {
16
17
  ruleId: ID,
17
18
  severity: 'warning',
18
- message: 'No link to a privacy policy was found on this page.',
19
+ message: say(lang, {
20
+ en: 'No link to a privacy policy was found on this page.',
21
+ de: 'Auf dieser Seite wurde kein Link zur Datenschutzerklärung gefunden.',
22
+ }),
19
23
  pageUrl: page.finalUrl,
20
- evidence: [`${page.links.length} links checked`],
21
- fix: 'Add a link labelled "Datenschutz" or "Datenschutzerklärung" next to the Impressum link in the footer.',
24
+ evidence: [linksChecked(page.links.length, lang)],
25
+ fix: say(lang, {
26
+ en: 'Add a link labelled "Datenschutz" or "Datenschutzerklärung" next to the Impressum link in the footer.',
27
+ de: 'Einen Link „Datenschutz“ oder „Datenschutzerklärung“ neben dem Impressum-Link in den Footer setzen.',
28
+ }),
22
29
  },
23
30
  ];
24
31
  },
@@ -1 +1 @@
1
- {"version":3,"file":"privacy-policy-link.js","sourceRoot":"","sources":["../../src/rules/privacy-policy-link.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,EAAE,GAAG,qBAAqB,CAAC;AAEjC,MAAM,cAAc,GAClB,4IAA4I,CAAC;AAE/I,MAAM,CAAC,MAAM,iBAAiB,GAAS;IACrC,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,6BAA6B;IACpC,WAAW,EACT,6FAA6F;QAC7F,iGAAiG;IACnG,UAAU,EAAE,CAAC,cAAc,EAAE,cAAc,CAAC;IAC5C,KAAK,CAAC,EAAE,IAAI,EAAE;QACZ,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC;YACjD,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO;YACL;gBACE,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,qDAAqD;gBAC9D,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,gBAAgB,CAAC;gBAChD,GAAG,EAAE,uGAAuG;aAC7G;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"privacy-policy-link.js","sourceRoot":"","sources":["../../src/rules/privacy-policy-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEvD,MAAM,EAAE,GAAG,qBAAqB,CAAC;AAEjC,MAAM,cAAc,GAClB,4IAA4I,CAAC;AAE/I,MAAM,CAAC,MAAM,iBAAiB,GAAS;IACrC,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,6BAA6B;IACpC,WAAW,EACT,6FAA6F;QAC7F,iGAAiG;IACnG,UAAU,EAAE,CAAC,cAAc,EAAE,cAAc,CAAC;IAC5C,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE;QAClB,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC;YACjD,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO;YACL;gBACE,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE;oBACjB,EAAE,EAAE,qDAAqD;oBACzD,EAAE,EAAE,qEAAqE;iBAC1E,CAAC;gBACF,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,QAAQ,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACjD,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE;oBACb,EAAE,EAAE,uGAAuG;oBAC3G,EAAE,EAAE,qGAAqG;iBAC1G,CAAC;aACH;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { say } from '../i18n.js';
1
2
  const ID = 'reject-option';
2
3
  export const rejectOption = {
3
4
  id: ID,
@@ -13,21 +14,34 @@ export const rejectOption = {
13
14
  'Art. 7(3) GDPR',
14
15
  '§ 25 Abs. 1 TDDDG',
15
16
  ],
16
- check({ page }) {
17
+ check({ page, lang }) {
17
18
  const consent = page.consent;
18
19
  if (consent?.bannerFound !== true || consent.rejectLabel !== undefined) {
19
20
  return [];
20
21
  }
21
22
  const vendor = consent.cmp === undefined ? '' : ` (${consent.cmp})`;
23
+ const accept = consent.acceptLabel ?? say(lang, { en: 'accept', de: 'Zustimmen' });
24
+ const evidence = [
25
+ say(lang, {
26
+ en: `accept button: ${consent.acceptLabel ?? 'unlabelled'}`,
27
+ de: `Zustimmen-Button: ${consent.acceptLabel ?? 'ohne Beschriftung'}`,
28
+ }),
29
+ ];
22
30
  if (consent.payOrOk === true) {
23
31
  return [
24
32
  {
25
33
  ruleId: ID,
26
34
  severity: 'info',
27
- message: `The consent banner${vendor} offers "${consent.acceptLabel ?? 'accept'}" or a subscription instead of a reject button ("pay or OK").`,
35
+ message: say(lang, {
36
+ en: `The consent banner${vendor} offers "${accept}" or a subscription instead of a reject button ("pay or OK").`,
37
+ de: `Das Cookie-Banner${vendor} bietet „${accept}“ oder ein Abo statt eines Ablehnen-Buttons (Pur-Abo-Modell).`,
38
+ }),
28
39
  pageUrl: page.finalUrl,
29
- evidence: [`accept button: ${consent.acceptLabel ?? 'unlabelled'}`],
30
- fix: 'German regulators consider pay-or-OK generally permissible if the paid option is an equivalent, fairly priced alternative (DSK, 22.03.2023). For large platforms the EDPB is much stricter (Opinion 08/2024). privalint cannot judge this; have it assessed legally.',
40
+ evidence,
41
+ fix: say(lang, {
42
+ en: 'German regulators consider pay-or-OK generally permissible if the paid option is an equivalent, fairly priced alternative (DSK, 22.03.2023). For large platforms the EDPB is much stricter (Opinion 08/2024). privalint cannot judge this; have it assessed legally.',
43
+ de: 'Deutsche Aufsichtsbehörden halten Pur-Abo-Modelle grundsätzlich für zulässig, wenn das Bezahlangebot eine gleichwertige Alternative zu einem angemessenen Preis ist (DSK, 22.03.2023). Für große Plattformen ist der EDSA deutlich strenger (Stellungnahme 08/2024). privalint kann das nicht beurteilen; bitte rechtlich prüfen lassen.',
44
+ }),
31
45
  },
32
46
  ];
33
47
  }
@@ -35,10 +49,16 @@ export const rejectOption = {
35
49
  {
36
50
  ruleId: ID,
37
51
  severity: 'warning',
38
- message: `The consent banner${vendor} offers "${consent.acceptLabel ?? 'accept'}" but no reject button next to it.`,
52
+ message: say(lang, {
53
+ en: `The consent banner${vendor} offers "${accept}" but no reject button next to it.`,
54
+ de: `Das Cookie-Banner${vendor} bietet „${accept}“, aber keinen Ablehnen-Button daneben.`,
55
+ }),
39
56
  pageUrl: page.finalUrl,
40
- evidence: [`accept button: ${consent.acceptLabel ?? 'unlabelled'}`],
41
- fix: 'Add a button such as "Alle ablehnen" or "Nur notwendige" on the first layer, with the same size and prominence as the accept button.',
57
+ evidence,
58
+ fix: say(lang, {
59
+ en: 'Add a button such as "Alle ablehnen" or "Nur notwendige" on the first layer, with the same size and prominence as the accept button.',
60
+ de: 'Einen Button wie „Alle ablehnen“ oder „Nur notwendige“ auf die erste Ebene setzen, gleich groß und gleich auffällig wie den Zustimmen-Button.',
61
+ }),
42
62
  },
43
63
  ];
44
64
  },
@@ -1 +1 @@
1
- {"version":3,"file":"reject-option.js","sourceRoot":"","sources":["../../src/rules/reject-option.ts"],"names":[],"mappings":"AAEA,MAAM,EAAE,GAAG,eAAe,CAAC;AAE3B,MAAM,CAAC,MAAM,YAAY,GAAS;IAChC,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,mDAAmD;IAC1D,WAAW,EACT,+FAA+F;QAC/F,0FAA0F;QAC1F,2BAA2B;IAC7B,UAAU,EAAE;QACV,iDAAiD;QACjD,wEAAwE;QACxE,+CAA+C;QAC/C,iBAAiB;QACjB,gBAAgB;QAChB,mBAAmB;KACpB;IACD,KAAK,CAAC,EAAE,IAAI,EAAE;QACZ,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,OAAO,EAAE,WAAW,KAAK,IAAI,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YACvE,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,GAAG,GAAG,CAAC;QACpE,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC7B,OAAO;gBACL;oBACE,MAAM,EAAE,EAAE;oBACV,QAAQ,EAAE,MAAM;oBAChB,OAAO,EAAE,qBAAqB,MAAM,YAAY,OAAO,CAAC,WAAW,IAAI,QAAQ,+DAA+D;oBAC9I,OAAO,EAAE,IAAI,CAAC,QAAQ;oBACtB,QAAQ,EAAE,CAAC,kBAAkB,OAAO,CAAC,WAAW,IAAI,YAAY,EAAE,CAAC;oBACnE,GAAG,EAAE,sQAAsQ;iBAC5Q;aACF,CAAC;QACJ,CAAC;QACD,OAAO;YACL;gBACE,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,qBAAqB,MAAM,YAAY,OAAO,CAAC,WAAW,IAAI,QAAQ,oCAAoC;gBACnH,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,QAAQ,EAAE,CAAC,kBAAkB,OAAO,CAAC,WAAW,IAAI,YAAY,EAAE,CAAC;gBACnE,GAAG,EAAE,sIAAsI;aAC5I;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"reject-option.js","sourceRoot":"","sources":["../../src/rules/reject-option.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAGjC,MAAM,EAAE,GAAG,eAAe,CAAC;AAE3B,MAAM,CAAC,MAAM,YAAY,GAAS;IAChC,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,mDAAmD;IAC1D,WAAW,EACT,+FAA+F;QAC/F,0FAA0F;QAC1F,2BAA2B;IAC7B,UAAU,EAAE;QACV,iDAAiD;QACjD,wEAAwE;QACxE,+CAA+C;QAC/C,iBAAiB;QACjB,gBAAgB;QAChB,mBAAmB;KACpB;IACD,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE;QAClB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,OAAO,EAAE,WAAW,KAAK,IAAI,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YACvE,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,GAAG,GAAG,CAAC;QACpE,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;QACnF,MAAM,QAAQ,GAAG;YACf,GAAG,CAAC,IAAI,EAAE;gBACR,EAAE,EAAE,kBAAkB,OAAO,CAAC,WAAW,IAAI,YAAY,EAAE;gBAC3D,EAAE,EAAE,qBAAqB,OAAO,CAAC,WAAW,IAAI,mBAAmB,EAAE;aACtE,CAAC;SACH,CAAC;QACF,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC7B,OAAO;gBACL;oBACE,MAAM,EAAE,EAAE;oBACV,QAAQ,EAAE,MAAM;oBAChB,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE;wBACjB,EAAE,EAAE,qBAAqB,MAAM,YAAY,MAAM,+DAA+D;wBAChH,EAAE,EAAE,oBAAoB,MAAM,YAAY,MAAM,+DAA+D;qBAChH,CAAC;oBACF,OAAO,EAAE,IAAI,CAAC,QAAQ;oBACtB,QAAQ;oBACR,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE;wBACb,EAAE,EAAE,sQAAsQ;wBAC1Q,EAAE,EAAE,0UAA0U;qBAC/U,CAAC;iBACH;aACF,CAAC;QACJ,CAAC;QACD,OAAO;YACL;gBACE,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE;oBACjB,EAAE,EAAE,qBAAqB,MAAM,YAAY,MAAM,oCAAoC;oBACrF,EAAE,EAAE,oBAAoB,MAAM,YAAY,MAAM,yCAAyC;iBAC1F,CAAC;gBACF,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,QAAQ;gBACR,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE;oBACb,EAAE,EAAE,sIAAsI;oBAC1I,EAAE,EAAE,+IAA+I;iBACpJ,CAAC;aACH;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { findCdn } from '../data/third-parties.js';
2
+ import { say } from '../i18n.js';
2
3
  import { groupRequestsByVendor } from './third-party-hits.js';
3
4
  const ID = 'third-party-cdns';
4
5
  export const thirdPartyCdns = {
@@ -8,15 +9,21 @@ export const thirdPartyCdns = {
8
9
  'reasoning that made Google Fonts a problem applies, but there is no court ruling on CDNs ' +
9
10
  'specifically, so this is only a hint.',
10
11
  references: ['Art. 6(1) GDPR', 'LG München I, 20.01.2022, Az. 3 O 17493/20 (by analogy)'],
11
- check({ page }) {
12
+ check({ page, lang }) {
12
13
  return [...groupRequestsByVendor(page.requests, findCdn)].map(([vendor, evidence]) => ({
13
14
  ruleId: ID,
14
15
  severity: 'info',
15
- message: `Files are loaded from ${vendor}.`,
16
+ message: say(lang, {
17
+ en: `Files are loaded from ${vendor}.`,
18
+ de: `Dateien werden von ${vendor} geladen.`,
19
+ }),
16
20
  pageUrl: page.finalUrl,
17
21
  subject: vendor,
18
22
  evidence,
19
- fix: 'Install the library with your package manager and bundle it, or copy the files to your own server.',
23
+ fix: say(lang, {
24
+ en: 'Install the library with your package manager and bundle it, or copy the files to your own server.',
25
+ de: 'Die Bibliothek über den Paketmanager installieren und mitbündeln oder die Dateien auf den eigenen Server kopieren.',
26
+ }),
20
27
  }));
21
28
  },
22
29
  };
@@ -1 +1 @@
1
- {"version":3,"file":"third-party-cdns.js","sourceRoot":"","sources":["../../src/rules/third-party-cdns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAEnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,MAAM,EAAE,GAAG,kBAAkB,CAAC;AAE9B,MAAM,CAAC,MAAM,cAAc,GAAS;IAClC,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,4CAA4C;IACnD,WAAW,EACT,yFAAyF;QACzF,2FAA2F;QAC3F,uCAAuC;IACzC,UAAU,EAAE,CAAC,gBAAgB,EAAE,yDAAyD,CAAC;IACzF,KAAK,CAAC,EAAE,IAAI,EAAE;QACZ,OAAO,CAAC,GAAG,qBAAqB,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAC3D,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAW,EAAE,CAAC,CAAC;YAChC,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,yBAAyB,MAAM,GAAG;YAC3C,OAAO,EAAE,IAAI,CAAC,QAAQ;YACtB,OAAO,EAAE,MAAM;YACf,QAAQ;YACR,GAAG,EAAE,oGAAoG;SAC1G,CAAC,CACH,CAAC;IACJ,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"third-party-cdns.js","sourceRoot":"","sources":["../../src/rules/third-party-cdns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,MAAM,EAAE,GAAG,kBAAkB,CAAC;AAE9B,MAAM,CAAC,MAAM,cAAc,GAAS;IAClC,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,4CAA4C;IACnD,WAAW,EACT,yFAAyF;QACzF,2FAA2F;QAC3F,uCAAuC;IACzC,UAAU,EAAE,CAAC,gBAAgB,EAAE,yDAAyD,CAAC;IACzF,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE;QAClB,OAAO,CAAC,GAAG,qBAAqB,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAC3D,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAW,EAAE,CAAC,CAAC;YAChC,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE;gBACjB,EAAE,EAAE,yBAAyB,MAAM,GAAG;gBACtC,EAAE,EAAE,sBAAsB,MAAM,WAAW;aAC5C,CAAC;YACF,OAAO,EAAE,IAAI,CAAC,QAAQ;YACtB,OAAO,EAAE,MAAM;YACf,QAAQ;YACR,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE;gBACb,EAAE,EAAE,oGAAoG;gBACxG,EAAE,EAAE,oHAAoH;aACzH,CAAC;SACH,CAAC,CACH,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { findEmbed } from '../data/third-parties.js';
2
+ import { say } from '../i18n.js';
2
3
  import { groupRequestsByVendor } from './third-party-hits.js';
3
4
  const ID = 'third-party-embeds';
4
5
  export const thirdPartyEmbeds = {
@@ -8,15 +9,21 @@ export const thirdPartyEmbeds = {
8
9
  "which sends the visitor's IP address and often sets cookies. Load them only after the " +
9
10
  'visitor has agreed, typically with a click-to-load placeholder.',
10
11
  references: ['Art. 6(1) GDPR', '§ 25 Abs. 1 TDDDG'],
11
- check({ page }) {
12
+ check({ page, lang }) {
12
13
  return [...groupRequestsByVendor(page.requests, findEmbed)].map(([vendor, evidence]) => ({
13
14
  ruleId: ID,
14
15
  severity: 'warning',
15
- message: `${vendor} loads before the visitor has given consent.`,
16
+ message: say(lang, {
17
+ en: `${vendor} loads before the visitor has given consent.`,
18
+ de: `${vendor} wird geladen, bevor eine Einwilligung vorliegt.`,
19
+ }),
16
20
  pageUrl: page.finalUrl,
17
21
  subject: vendor,
18
22
  evidence,
19
- fix: `Show a placeholder and load ${vendor} only after a click or consent. youtube-nocookie.com alone is not enough, it still contacts Google on load.`,
23
+ fix: say(lang, {
24
+ en: `Show a placeholder and load ${vendor} only after a click or consent. youtube-nocookie.com alone is not enough, it still contacts Google on load.`,
25
+ de: `Einen Platzhalter zeigen und ${vendor} erst nach Klick oder Einwilligung laden. youtube-nocookie.com allein reicht nicht, es kontaktiert Google trotzdem beim Laden.`,
26
+ }),
20
27
  }));
21
28
  },
22
29
  };
@@ -1 +1 @@
1
- {"version":3,"file":"third-party-embeds.js","sourceRoot":"","sources":["../../src/rules/third-party-embeds.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,MAAM,EAAE,GAAG,oBAAoB,CAAC;AAEhC,MAAM,CAAC,MAAM,gBAAgB,GAAS;IACpC,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,uCAAuC;IAC9C,WAAW,EACT,6FAA6F;QAC7F,wFAAwF;QACxF,iEAAiE;IACnE,UAAU,EAAE,CAAC,gBAAgB,EAAE,mBAAmB,CAAC;IACnD,KAAK,CAAC,EAAE,IAAI,EAAE;QACZ,OAAO,CAAC,GAAG,qBAAqB,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAC7D,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAW,EAAE,CAAC,CAAC;YAChC,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,GAAG,MAAM,8CAA8C;YAChE,OAAO,EAAE,IAAI,CAAC,QAAQ;YACtB,OAAO,EAAE,MAAM;YACf,QAAQ;YACR,GAAG,EAAE,+BAA+B,MAAM,6GAA6G;SACxJ,CAAC,CACH,CAAC;IACJ,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"third-party-embeds.js","sourceRoot":"","sources":["../../src/rules/third-party-embeds.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,MAAM,EAAE,GAAG,oBAAoB,CAAC;AAEhC,MAAM,CAAC,MAAM,gBAAgB,GAAS;IACpC,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,uCAAuC;IAC9C,WAAW,EACT,6FAA6F;QAC7F,wFAAwF;QACxF,iEAAiE;IACnE,UAAU,EAAE,CAAC,gBAAgB,EAAE,mBAAmB,CAAC;IACnD,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE;QAClB,OAAO,CAAC,GAAG,qBAAqB,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAC7D,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAW,EAAE,CAAC,CAAC;YAChC,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE;gBACjB,EAAE,EAAE,GAAG,MAAM,8CAA8C;gBAC3D,EAAE,EAAE,GAAG,MAAM,kDAAkD;aAChE,CAAC;YACF,OAAO,EAAE,IAAI,CAAC,QAAQ;YACtB,OAAO,EAAE,MAAM;YACf,QAAQ;YACR,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE;gBACb,EAAE,EAAE,+BAA+B,MAAM,6GAA6G;gBACtJ,EAAE,EAAE,gCAAgC,MAAM,gIAAgI;aAC3K,CAAC;SACH,CAAC,CACH,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -1,4 +1,5 @@
1
- import { type Tracker } from '../data/trackers.js';
1
+ import { type Tracker, type TrackerCategory } from '../data/trackers.js';
2
+ import { type Lang } from '../i18n.js';
2
3
  import type { RecordedCookie, RecordedRequest } from '../types.js';
3
4
  export interface TrackerHit {
4
5
  tracker: Tracker;
@@ -6,4 +7,5 @@ export interface TrackerHit {
6
7
  evidence: string[];
7
8
  }
8
9
  /** Matches requests and cookies against the tracker list, grouped by vendor. */
9
- export declare function findTrackerHits(requests: readonly RecordedRequest[], cookies: readonly RecordedCookie[]): TrackerHit[];
10
+ export declare function findTrackerHits(requests: readonly RecordedRequest[], cookies: readonly RecordedCookie[], lang: Lang): TrackerHit[];
11
+ export declare const categoryLabel: (category: TrackerCategory, lang: Lang) => string;
@@ -1,7 +1,9 @@
1
- import { findTrackerByCookie, findTrackerByUrl } from '../data/trackers.js';
1
+ import { findTrackerByCookie, findTrackerByUrl, } from '../data/trackers.js';
2
+ import { say } from '../i18n.js';
2
3
  const MAX_EVIDENCE = 5;
3
4
  /** Matches requests and cookies against the tracker list, grouped by vendor. */
4
- export function findTrackerHits(requests, cookies) {
5
+ export function findTrackerHits(requests, cookies, lang) {
6
+ const requestLabel = say(lang, { en: 'request', de: 'Anfrage' });
5
7
  const hits = new Map();
6
8
  const record = (tracker, evidence) => {
7
9
  const hit = hits.get(tracker.vendor) ?? { tracker, evidence: new Set() };
@@ -11,7 +13,7 @@ export function findTrackerHits(requests, cookies) {
11
13
  for (const request of requests) {
12
14
  const tracker = findTrackerByUrl(request.url);
13
15
  if (tracker !== undefined) {
14
- record(tracker, `request ${request.url}`);
16
+ record(tracker, `${requestLabel} ${request.url}`);
15
17
  }
16
18
  }
17
19
  for (const cookie of cookies) {
@@ -26,4 +28,13 @@ export function findTrackerHits(requests, cookies) {
26
28
  evidence: [...evidence].slice(0, MAX_EVIDENCE),
27
29
  }));
28
30
  }
31
+ const CATEGORY_LABELS = {
32
+ analytics: { en: 'analytics', de: 'Analyse' },
33
+ advertising: { en: 'advertising', de: 'Werbung' },
34
+ 'session-replay': { en: 'session-replay', de: 'Session-Replay' },
35
+ social: { en: 'social', de: 'Social Media' },
36
+ 'marketing-automation': { en: 'marketing-automation', de: 'Marketing-Automatisierung' },
37
+ 'tag-manager': { en: 'tag-manager', de: 'Tag-Manager' },
38
+ };
39
+ export const categoryLabel = (category, lang) => say(lang, CATEGORY_LABELS[category]);
29
40
  //# sourceMappingURL=tracker-hits.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tracker-hits.js","sourceRoot":"","sources":["../../src/rules/tracker-hits.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAgB,MAAM,qBAAqB,CAAC;AAG1F,MAAM,YAAY,GAAG,CAAC,CAAC;AAQvB,gFAAgF;AAChF,MAAM,UAAU,eAAe,CAC7B,QAAoC,EACpC,OAAkC;IAElC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuD,CAAC;IAC5E,MAAM,MAAM,GAAG,CAAC,OAAgB,EAAE,QAAgB,EAAE,EAAE;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAU,EAAE,CAAC;QACjF,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,CAAC,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,CAAC,OAAO,EAAE,WAAW,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,OAAO,GACX,mBAAmB,CAAC,MAAM,CAAC,IAAI,CAAC;YAChC,gBAAgB,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;QACnE,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,CAAC,OAAO,EAAE,UAAU,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;QACxD,OAAO;QACP,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC;KAC/C,CAAC,CAAC,CAAC;AACN,CAAC"}
1
+ {"version":3,"file":"tracker-hits.js","sourceRoot":"","sources":["../../src/rules/tracker-hits.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,gBAAgB,GAGjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAa,GAAG,EAAa,MAAM,YAAY,CAAC;AAGvD,MAAM,YAAY,GAAG,CAAC,CAAC;AAQvB,gFAAgF;AAChF,MAAM,UAAU,eAAe,CAC7B,QAAoC,EACpC,OAAkC,EAClC,IAAU;IAEV,MAAM,YAAY,GAAG,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuD,CAAC;IAC5E,MAAM,MAAM,GAAG,CAAC,OAAgB,EAAE,QAAgB,EAAE,EAAE;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAU,EAAE,CAAC;QACjF,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,CAAC,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,CAAC,OAAO,EAAE,GAAG,YAAY,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,OAAO,GACX,mBAAmB,CAAC,MAAM,CAAC,IAAI,CAAC;YAChC,gBAAgB,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;QACnE,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,CAAC,OAAO,EAAE,UAAU,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;QACxD,OAAO;QACP,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC;KAC/C,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,eAAe,GAAkC;IACrD,SAAS,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,SAAS,EAAE;IAC7C,WAAW,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,SAAS,EAAE;IACjD,gBAAgB,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,gBAAgB,EAAE;IAChE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,cAAc,EAAE;IAC5C,sBAAsB,EAAE,EAAE,EAAE,EAAE,sBAAsB,EAAE,EAAE,EAAE,2BAA2B,EAAE;IACvF,aAAa,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,aAAa,EAAE;CACxD,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,QAAyB,EAAE,IAAU,EAAE,EAAE,CACrE,GAAG,CAAC,IAAI,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC"}