diff --git a/src/components/ComplaintLandingPage/AskComplaintType.astro b/src/components/ComplaintLandingPage/AskComplaintType.astro index 84daea7..b53a36f 100644 --- a/src/components/ComplaintLandingPage/AskComplaintType.astro +++ b/src/components/ComplaintLandingPage/AskComplaintType.astro @@ -24,7 +24,7 @@ const { token } = Astro.props;
{ - (['informal', 'formal'] as const).map((type) => ( + (['formal', 'informal'] as const).map((type) => (
+
diff --git a/src/components/ComplaintLandingPage/ReadyToSend.astro b/src/components/ComplaintLandingPage/ReadyToSend.astro index fb9d0f7..058f051 100644 --- a/src/components/ComplaintLandingPage/ReadyToSend.astro +++ b/src/components/ComplaintLandingPage/ReadyToSend.astro @@ -60,11 +60,11 @@ const userNetworkActivityJwt = await makeAttachmentJwt({
- {t('complaint-landing-readyToSend', 'agree-to-unencrypted-communication')} +
{ - (['yes', 'no'] as const).map((a) => ( + (['yes', 'no-letter'] as const).map((a) => (
)) @@ -93,35 +93,9 @@ const userNetworkActivityJwt = await makeAttachmentJwt({
-

- -

{t('complaint-landing-readyToSend', 'attachments')}

- - +

{t('complaint-landing-readyToSend', 'send-explanation')}

{t('complaint-landing-readyToSend', 'dpa-contact-details')}

-

{t('complaint-landing-readyToSend', 'dpa-contact-details-explanation')}

@@ -161,6 +135,33 @@ const userNetworkActivityJwt = await makeAttachmentJwt({
+

{t('complaint-landing-readyToSend', 'attachments')}

+ +
{t('complaint-landing-readyToSend', 'attachment-note')}
+ + +
+
diff --git a/src/components/ProceedingLandingPage/ComplaintSent.astro b/src/components/ProceedingLandingPage/ComplaintSent.astro index ceee303..b421d7f 100644 --- a/src/components/ProceedingLandingPage/ComplaintSent.astro +++ b/src/components/ProceedingLandingPage/ComplaintSent.astro @@ -1,6 +1,7 @@ --- import { t } from '../../i18n/server'; import Base from '../../layouts/base.astro'; +import TextRaw from '../TextRaw.astro'; interface Props { complaintType: 'formal' | 'informal'; @@ -10,5 +11,5 @@ const { complaintType } = Astro.props; --- - {t('proceeding-landing-complaintSent', `explanation-${complaintType}`)} + diff --git a/src/components/ProceedingLandingPage/InitialAnalysisFoundNothing.astro b/src/components/ProceedingLandingPage/InitialAnalysisFoundNothing.astro index 6fca976..1219181 100644 --- a/src/components/ProceedingLandingPage/InitialAnalysisFoundNothing.astro +++ b/src/components/ProceedingLandingPage/InitialAnalysisFoundNothing.astro @@ -32,7 +32,5 @@ if (!proceeding) throw new Error('This should never happen.');

{t('proceeding-landing-initialAnalysisFoundNothing', `what-heading`)}

-

- {t('proceeding-landing-initialAnalysisFoundNothing', `what-explanation`)} -

+ diff --git a/src/components/ProceedingLandingPage/NeedsInitialAnalysis.astro b/src/components/ProceedingLandingPage/NeedsInitialAnalysis.astro index c3f5a84..2116242 100644 --- a/src/components/ProceedingLandingPage/NeedsInitialAnalysis.astro +++ b/src/components/ProceedingLandingPage/NeedsInitialAnalysis.astro @@ -15,6 +15,8 @@ const tokenUrl = `/p/${token}`; --- + + diff --git a/src/components/ProceedingLandingPage/NeedsSecondAnalysis.astro b/src/components/ProceedingLandingPage/NeedsSecondAnalysis.astro index b99fa49..7443a5b 100644 --- a/src/components/ProceedingLandingPage/NeedsSecondAnalysis.astro +++ b/src/components/ProceedingLandingPage/NeedsSecondAnalysis.astro @@ -15,6 +15,8 @@ const tokenUrl = `/p/${token}`; --- + + diff --git a/src/i18n/en.json b/src/i18n/en.json index 448000a..8a8161b 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -65,28 +65,28 @@ }, "proceeding-landing-needsInitialAnalysis": { "heading": "Analysing app…", - "explanation": "

We are currently analysing the app to detect whether it performs tracking. This process will take a while.

Try checking back in ~10 minutes at this page. Make sure to bookmark or save this link so you don’t lose access to your proceeding.

" + "explanation": "

We are currently analysing the app to detect whether it performs tracking. This process will take a while.

Try checking back in ~10 minutes at this page. Make sure to bookmark or save this link so you don’t lose access to your proceeding:

" }, "proceeding-landing-analysisFailed": { "heading": "Analysis failed", - "explanation": "Unfortunately, the analysis of the app failed for unknown reasons. Please try again later." + "explanation": "Unfortunately, the analysis of the app failed unrecoverably for unknown reasons. You can try with another app." }, "proceeding-landing-initialAnalysisFoundNothing": { "heading": "Analysis of “{{appName}}” found nothing", "explanation": "The analysis of the app has finished. We did not detect any tracking data transmissions without consent.", "what-heading": "What does that mean?", - "what-explanation": "This could mean that the app genuinely doesn’t perform any tracking. However, it is also possible that we missed something." + "what-explanation": "

This could mean that the app genuinely doesn’t perform any tracking. However, it is also possible that we missed something.

Mind you, our analysis process doesn’t enter anything into the apps or accept any prompts. If tracking companies are only contacted after certain inputs, we are unable to detect that.

On top of that, we have made the decision to rather miss something than produce false positives, which is why we are only detecting tracking endpoints that we have researched and classified as such. This means that we will miss some tracking data transmissions but it enables us to say for sure that tracking actually occurred if we do detect something, which is important for the complaints.

" }, "proceeding-landing-awaitingControllerNotice": { - "heading": "Analysis of “{{appName}}”", + "heading": "Analysis of “{{appName}}” detected tracking", "explanation": "

The analysis of “{{appName}}” has finished. We have detected tracking data transmissions without consent, which you can see below.

This could be in violation of applicable data protection law. You can now send a notice to the developer giving them the opportunity to remedy the problem.

", "send-notice-button": "Send notice to developer" }, "proceeding-landing-awaitingControllerResponse": { "heading": "Waiting for the developer of “{{appName}}” to respond…", - "explanation": "The developer of “{{appName}}” has until {{deadline}} to respond to your notice. Please upload all correspondence you have with them in the meantime so that we can attach it to a potential complaint in the future.", + "explanation": "The developer of “{{appName}}” has until {{deadline}} to respond to your notice. Please upload all correspondence you have with them in the meantime so that we can attach it to a potential complaint in the future.", "uploaded-messages": "Uploaded messages", - "next-steps": "If your correspondence with the developer is done or the deadline has expired, you can continue. Make sure that you have uploaded all messages before you continue.", + "next-steps": "If your correspondence with the developer is done or the deadline has expired, you can continue. Make sure that you have uploaded all messages beforehand.", "continue": "Continue with the process" }, "proceeding-landing-needsSecondAnalysis": { @@ -99,18 +99,19 @@ }, "proceeding-landing-awaitingComplaint": { "heading": "Analysis of “{{appName}}”", - "explanation": "

The analysis of “{{appName}}” has finished. We have still detected tracking data transmissions without consent, which you can see below.

You can now raise this issue with the data protection authorities to have them check whether the app violates data protection law and order the developer to stop if so.

", + "explanation": "

The analysis of “{{appName}}” has finished. We have still detected tracking data transmissions without consent, which you can see below.

You can now raise this issue with the competent data protection authority to have them check whether the app violates data protection law. If so, they can order the developer to stop the tracking.

", "contact-button": "Contact data protection authorities" }, "proceeding-landing-complaintSent": { "heading": "What’s next?", - "explanation-formal": "You have told us that you have sent the complaint. Now it’s time to wait for the DPA to process it. According to Art. 78(2) GDPR, the DPA has to inform you on the progress or outcome of the complaint within three months. However, in our experience, it is unfortunately common that it takes them much longer to do so. If after six months or a year you still haven’t received a meaningful response, it makes sense to send them a reminder.", - "explanation-informal": "You have told us that you have sent the complaint. Now it’s time to wait for the DPA to process it. Since you have only sent an informal complaint, the DPA is unfortunately not legally required to inform you about what they do regarding your complaint. You probably shouldn’t expect a meaningful response from them." + "explanation-formal": "

You have told us that you have sent the complaint. Now it’s time to wait for the DPA to process it. According to Art. 78(2) GDPR, the DPA has to inform you on the progress or outcome of the complaint within three months. However, in our experience, it is unfortunately common that it takes them much longer to do so. If after six months or a year you still haven’t received a meaningful response, it makes sense to send them a reminder.

", + "explanation-informal": "

You have told us that you have sent the complaint. Now it’s time to wait for the DPA to process it. Since you have only sent an informal complaint, the DPA is unfortunately not legally required to inform you about what they do regarding your complaint. You probably shouldn’t expect a meaningful response from them.

However, our goal is to raise awareness and draw the attention of the DPAs to these violations. It is important that the authorities are attentive to our grievances to facilitate change in the long run. We hope that this whole process can be part of a necessary political shift that prioritises data protection and prohibits the unwanted tracking activities of companies.

Thank you for taking the time to help us on our mission.

" }, "send-notice": { "heading": "Send notice to developer", - "intro": "

We have generated the message that you can send to the developer to inform them of the tracking data transmissions that we have detected and give them the opportunity to remedy them. You’ll send the message using your own email.
Make sure to attach the files listed below to the email!

", + "intro": "We have generated the message that you can send to the developer to inform them of the tracking data transmissions that we have detected and give them the opportunity to remedy them. You’ll send the message using your own email.", + "attachment-note": "Make sure to attach the files listed below to the email!", "subject": "Subject", "recipient": "Recipient", "verify-recipient": "Please verify this email address in the app’s privacy policy.", @@ -120,13 +121,14 @@ "template-subject": "Data protection violations in your {{platform}} app “{{appName}}”", "template-text": "To Whom It May Concern:\n\nI unfortunately had to notice that your {{platform}} app “{{appName}}” ({{appId}}) performs tracking data transmissions without consent. I believe that this is in violation of applicable data protection law.\n\nPlease refer to the attached notice and technical report for further details. Please especially note the voluntary 60 day grace period that I am giving you before filing a complaint with the data protection authorities.\n\nYours sincerely,", "attachments": "Attachments", + "open-email-program": "Open email program", "notice": "Notice (PDF)", "technical-report": "Technical report (PDF)", "traffic-recording": "Traffic recording (HAR)", - "upload-heading": "Upload sent message", + "upload-heading": "To continue: Upload sent message", "upload-explanation": "After you've sent the email, upload it here as an EML or PDF file. Why do we need this? If the developer doesn’t actually remove the tracking within the 60 day deadline you have given them, you can send a complaint to the data protection authorities and ask them to intervene. In this case, you will need to attach the previous communication with the developer.", "choose-file": "Choose file", - "upload": "Upload" + "upload": "Upload and continue" }, "evaluate-response": { "heading": "How did the developer react?", @@ -134,7 +136,9 @@ "explanation-deadline": "If the developer has not responded yet, please wait until the deadline has expired ({{deadline}}) before continuing.", "reponse-promise": "The developer told me that they removed the tracking.", "reponse-denial": "The developer has denied that there are violations.", - "reponse-none": "The developer has not responded (concerning the tracking)." + "reponse-none": "The developer has not responded (concerning the tracking).", + "why-not-yet": "Why can I not select this?", + "why-not-yet-explanation": "You have given the developer until {{deadline}} to respond. Please wait until the deadline has expired or the developer has responded before continuing." }, "complaint-landing-askIsUserOfApp": { @@ -143,19 +147,19 @@ }, "complaint-landing-askAuthority": { "heading": "Which authority do you want to contact?", - "question": "

Please select the data protection authority that you want to contact about this matter. You can choose the authority at the your place of habitual residence (i.e. where you live), your place of work, or the place of the alleged infringement (i.e. where the developer is based).

You’ll note that the list below is not complete. Unfortunately, we can only support authorities that support complaints in languages we have templates for.

" + "question": "

Please select the data protection authority (DPA) that you want to contact about this matter. You can choose the authority at the your place of habitual residence (i.e. where you live), your place of work, or the place of the alleged infringement (i.e. where the developer is based).

You’ll note that the list below is not complete. Unfortunately, we can only support authorities that support complaints in languages we have templates for.

" }, "complaint-landing-askComplaintType": { "heading": "How to contact the DPA about the app?", "explanation": "There are two ways you can ask the data protection authorities to look into the tracking data transmissions you have found in the app:", - "formal-complaint-explanation": "

You can send a formal complaint. The DPA will have to investigate this complaint and inform you of the outcome.

However, to do so, you will need to show that you are personally affected by the tracking. We can guide you through this process, but it will required a bit of effort and technical knowledge.

", + "formal-complaint-explanation": "

You can send a formal complaint. The DPA will have to investigate this complaint and inform you of the outcome.

However, to do so, you will need to show that you are personally affected by the tracking. We can guide you through this process, but it will require a bit of effort and technical knowledge.

", "informal-complaint-explanation": "Alternatively, you can informally ask the DPA to investigate the app. This does not require any additional effort by you but the DPA will also not be legally required to investigate or tell you what they did.", - "informal-complaint-button": "Informally ask the DPA to look in the app", + "informal-complaint-button": "Informally ask the DPA to look into the app", "formal-complaint-button": "Check whether I am personally affected to send a complaint" }, "complaint-landing-askUserNetworkActivity": { "heading": "Prove that you are affected by the tracking", - "explanation-android": "

To prove that you are personally affected by the tracking, we are going to use the open-source TrackerControl app to check whether the app contacts the same tracking servers on your device as we found in our analysis. To do that:

\n\n
    \n
  1. Install the Slim version of app on your device. It can be downloaded from the Google Play Store or using the Aurora Store.
    The full version that is available on F-Droid blocks tracking requests by default.
  2. \n
  3. Open the TrackerControl app and activate the toggle at the top left, next to the text “TC Slim”.
  4. \n
  5. You will be informed that TrackerControl uses a local VPN. Click “OK” in that prompt and also confirm the following connection request with “OK”.
  6. \n
  7. Next, you will be ask to disable battery optimisations for consistent results. Click “OK” in that prompt. In the “Battery optimisations” window that opens, choose “All apps” in the dropdown at the top, look for and click on the “TrackerControl” app, choose “Don’t optimise”, and click “Done”.
  8. \n
  9. Start the app you want to analyse and use it for a while as you normally would.
  10. \n
  11. After you’re done, go back to TrackerControl. In the list of apps, choose your app, and click the three dots in the top right corner. Click “Export as CSV” and save the export file to your device.
  12. \n
  13. Upload the export file here.
    If you’re accessing Tweasel from a computer, you can open this page on your phone for the upload, and then come back to your computer and reload the page to continue.
  14. \n
", + "explanation-android": "

To prove that you are personally affected by the tracking, we are going to use the open-source TrackerControl app to check whether the app contacts the same tracking servers on your device as we found in our analysis. To do that:

\n\n
    \n
  1. Install the Slim version of the app on your device. It can be downloaded from the Google Play Store or using the Aurora Store.
    The full version that is available on F-Droid blocks tracking requests by default.
  2. \n
  3. Open the TrackerControl app and activate the toggle at the top left, next to the text “TC Slim”.
  4. \n
  5. You will be informed that TrackerControl uses a local VPN. Click “OK” in that prompt and also confirm the following connection request with “OK”.
  6. \n
  7. Next, you will be ask to disable battery optimisations for consistent results. Click “OK” in that prompt. In the “Battery optimisations” window that opens, choose “All apps” in the dropdown at the top, look for and click on the “TrackerControl” app, choose “Don’t optimise”, and click “Done”.
  8. \n
  9. Start the app you want to analyse and use it for a while as you normally would.
  10. \n
  11. After you’re done, go back to TrackerControl. In the list of apps, choose your app, and click the three dots in the top right corner. Click “Export as CSV” and save the export file to your device.
  12. \n
  13. Upload the export file here.
    If you’re accessing Tweasel from a computer, you can open this page on your phone for the upload, and then come back to your computer and reload the page to continue.
  14. \n
", "explanation-ios": "

To prove that you are personally affected by the tracking, we are going to use the App Privacy Report integrated into iOS to check whether the app contacts the same tracking servers on your device as we found in our analysis. To do that:

\n\n
    \n
  1. Go into the Settings, then click on “Privacy & Security”.
  2. \n
  3. Click on “App Privacy Report” and then click “Turn on App Privacy Report”.
  4. \n
  5. Start the app you want to analyse and use it for a while as you normally would.
  6. \n
  7. After you’re done, go back into the App Privacy Report and click the share icon in the top right corner. Save the export file to your device.
  8. \n
  9. Upload the export file here.
    If you’re accessing Tweasel from a computer, you can open this page on your phone for the upload, and then come back to your computer and reload the page to continue.
    Note: The export file may unfortunately also contain information about sensor access and network activity of your other apps, especially if you already had the App Privacy Report enabled for a while. To mitigate this, it makes sense to disable and immediately re-enable the App Privacy Report right before your open the app to analyse. This will delete the old data.
  10. \n
", "choose-file": "Choose file", "upload": "Upload" @@ -167,10 +171,10 @@ "no": "I installed the app another way and/or I am not logged in with my personal account" }, "complaint-landing-askDeviceHasRegisteredSimCard": { - "heading": "Do you have a SIM card in your phone?", - "explanation": "If you have a SIM card that is registered to your name in your phone, this can also help us establish that you are personally affected.", - "yes": "I have a SIM card that is registered to my name in my phone", - "no": "I don’t have a SIM card in my phone or it is not registered to my name" + "heading": "Do you have a SIM card in your device?", + "explanation": "If you have a SIM card that is registered to your name in your device, this can also help us establish that you are personally affected.", + "yes": "I have a SIM card that is registered to my name in my device", + "no": "I don’t have a SIM card in my device or it is not registered to my name" }, "complaint-landing-askDeveloperAddress": { "heading": "Who is responsible for the app?", @@ -188,17 +192,20 @@ "explanation": "You’re almost done. The DPA will need to know who the complainant is—that’s you. Since this is quite sensitive information, we will only use what you enter on this page to generate your complaint but not store it on our servers.", "complainant-explanation": "You will at least need to provide your name. Some authorities also require you to provide your full address.", "contact-details-explanation": "Additionally, you also need to provide the authority with a way to contact you. This can be an email address or a postal address.", - "agree-to-unencrypted-communication": "Finally, do you agree to being contacted via unencrypted email?", + "agree-to-unencrypted-communication": "Finally, do you agree to being contacted via unencrypted email?", + "yes": "yes", + "no-letter": "no (you will likely receive a letter)", "generate-complaint": "Generate complaint", - "send-explanation": "After you’ve downloaded the generated complaint, you can send it to the DPA yourself.
Make sure to attach the files listed below to the email!", + "send-explanation": "After you’ve downloaded the generated complaint, you will need to send it to the DPA yourself.", "attachments": "Attachments", + "attachment-note": "Make sure to attach the complaint as well as the files listed below to your message to the DPA!", "technical-report": "Technical report (PDF)", "traffic-recording": "Traffic recording (HAR)", "developer-communication": "Communication with the developer (ZIP)", "user-network-activity-android": "TrackerControl export (CSV)", "user-network-activity-ios": "App Privacy Report export (NDJSON)", "dpa-contact-details": "Contact details of the DPA", - "dpa-contact-details-explanation": "Here’s how you can contact the DPA to send them your complaint.", + "dpa-contact-details-explanation": "You can choose your preferred way of contacting the DPA.", "address": "Address:", "email": "Email address:", "webform": "Webform:", diff --git a/src/layouts/base.astro b/src/layouts/base.astro index e56b2ec..f92ea14 100644 --- a/src/layouts/base.astro +++ b/src/layouts/base.astro @@ -27,6 +27,7 @@ const i18nDefinition = Object.fromEntries( {title ? `${title} · Tweasel` : 'Tweasel'} + @@ -50,22 +51,20 @@ const i18nDefinition = Object.fromEntries(
-
-
-

{title}

-
-
+
 
+ -
-
- -
-
+
+
+

{title}

- - + +
+
+ + diff --git a/src/lib/dpas.ts b/src/lib/dpas.ts index 9714ef0..faae534 100644 --- a/src/lib/dpas.ts +++ b/src/lib/dpas.ts @@ -627,7 +627,7 @@ const dpaData = { fitst: { slug: 'fitst', 'relevant-countries': ['fi'], - name: 'Tietosuojavaltuutetun toimisto (Office of the Data Protection Ombudsman of Denmark)', + name: 'Tietosuojavaltuutetun toimisto (Office of the Data Protection Ombudsman of Finland)', address: 'PL 800\n00531 Helsinki\nFinland', phone: '+358 29 566 6700', fax: '+358 29 566 6735', diff --git a/src/pages/p/[token]/evaluate-response/index.astro b/src/pages/p/[token]/evaluate-response/index.astro index 495d236..238241a 100644 --- a/src/pages/p/[token]/evaluate-response/index.astro +++ b/src/pages/p/[token]/evaluate-response/index.astro @@ -24,35 +24,41 @@ if (proceeding?.state !== 'awaitingControllerResponse' || !proceeding.noticeSent const deadline = calculateDeadline(proceeding.noticeSent); const deadlineExpired = new Date() > deadline; +const deadlineFormatted = formatDate(deadline, { language: Astro.currentLocale, includeTime: true }); ---

{t('evaluate-response', 'explanation')}

- { - !deadlineExpired && ( -

- {t('evaluate-response', 'explanation-deadline', { - deadline: formatDate(deadline, { language: Astro.currentLocale, includeTime: true }), - })} -

- ) - } - -
-
- { - (['promise', 'denial', 'none'] as const).map((r) => ( -
- + +
+ +
+ {t('evaluate-response', 'why-not-yet-explanation', { deadline: deadlineFormatted })} +
+
+
+ ) : ( + - - )) - } -
+ )} + + )) + }
diff --git a/src/pages/p/[token]/send-notice.astro b/src/pages/p/[token]/send-notice.astro index cc088c3..c14386a 100644 --- a/src/pages/p/[token]/send-notice.astro +++ b/src/pages/p/[token]/send-notice.astro @@ -46,18 +46,11 @@ const fields = { --- - +

{t('send-notice', 'intro')}

-
- - +
{t('send-notice', 'attachment-note')}
+

{ !privacyPolicyUrl ? ( @@ -72,6 +65,24 @@ const fields = { ) }
+ +
+ + {t('send-notice', 'attachments')} +
+